Springboot习惯大于配置原理

Springboot简介

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。用我的话来理解,就是spring boot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架。

准备工作

引入springboot相关依赖1.5.3.RELEASE。

创建启动类

@SpringBootApplication
public class HelloApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloApplication.class, args);
    }

是的。就这样,你的springboot项目就完成了。下面可以直接run这个main方法启动springboot了。

源码分析

从上面可以看到springboot的入口就是SpringApplication.run()方法。所以我们跟着run方法看下他到底做了什么。
可以看到他调用到了SpringApplication.class的如下方法:

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
		return new SpringApplication(sources).run(args);
	}

new SpringApplication(sources)

这个主要是调用springapplication的initialize方法

private void initialize(Object[] sources) {
		if (sources != null && sources.length > 0) {
			this.sources.addAll(Arrays.asList(sources));
		}
		this.webEnvironment = deduceWebEnvironment();
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

deduceWebEnvironment()
{ “javax.servlet.Servlet”,“org.springframework.web.context.ConfigurableWebApplicationContext” }

  •          判断类路径中是否有这两个类存在,推断是否是web环境
    

setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class))
从类路径以及jar包里面的路径META-INF/spring.factories里面查找ApplicationContextInitializer的值。将这些值对应的类实例化后排序并保存到initializers集合中。

getSpringFactoriesInstances()方法的作用就是找到META-INF/spring.factories里面指定的入参的类型的类并实例化。

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

  •          同上,找到META-INF/spring.factories中指定的所有ApplicationListener并实例化保存到listeners集合中。
    

把启动类赋值给mainApplicationClass

下面开始看run()方法

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		FailureAnalyzers analyzers = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			analyzers = new FailureAnalyzers(context);
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			listeners.finished(context, null);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			return context;
		}
		catch (Throwable ex) {
			handleRunFailure(context, listeners, analyzers, ex);
			throw new IllegalStateException(ex);
		}
	}

SpringApplicationRunListeners listeners = getRunListeners(args);

  •          获取所有的META-INF/spring.factories中的SpringApplicationRunListener并实例化返回。
    

listeners.starting()。

通知所有的监听者springboot开始启动

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		listeners.environmentPrepared(environment);
		if (isWebEnvironment(environment) && !this.webEnvironment) {
			environment = convertToStandardEnvironment(environment);
		}
		return environment;
	}

1.根据initialize()上面获取的是否web环境来创建web环境或者非web环境对象。
2.配置web初始化属性initParams等,设置spring启动profiles。
3.listeners.environmentPrepared(environment)通知监听者们springboot环境准备好了。

Banner printedBanner = printBanner(environment);

private Banner printBanner(ConfigurableEnvironment environment) {
		if (this.bannerMode == Banner.Mode.OFF) {
			return null;
		}
		ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
				: new DefaultResourceLoader(getClassLoader());
		SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
				resourceLoader, this.banner);
		if (this.bannerMode == Mode.LOG) {
			return bannerPrinter.print(environment, this.mainApplicationClass, logger);
		}
		return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
	}

打印springboot banner,也就是我们每次启动的时候打印的Springboot图像的地方。这里我们可以通过bannerMod来控制是否打印banner。

createApplicationContext()

根据是否web环境创建applicationContext对象。

prepareContext(context, environment, listeners, applicationArguments,printedBanner)

1.applyInitializers() 调用上面获取的initializers集合中的所有对象的initialize(C applicationContext)方法。
2.listeners.contextPrepared(context) 通知监听者们springboot的ApplicationContext准备好了,并把ApplicationContext作为参数传入回调
3.listeners.contextLoaded(context); 通知监听者们springboot的ApplicationContext加载好了。

refreshContext(context);

((AbstractApplicationContext) applicationContext).refresh(),调用spring ioc核心方法refresh(),可以看到springboot实现ioc的实现还是用的Spring IOC。

afterRefresh(context, applicationArguments);

protected void afterRefresh(ConfigurableApplicationContext context,
			ApplicationArguments args) {
		callRunners(context, args);
	}
	private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<Object>();		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<Object>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

callRunners(ApplicationContext context, ApplicationArguments args)。
获取容器中所有的ApplicationRunner和CommandLineRunner对象,并调用他们的run方法。

listeners.finished(context, null);

通知监听者们springboot启动完成。

SpringApplication.run()总结

1.我们可以在自己的项目里面添加META-INF/spring.factories文件,并指定上面的springboot启动中要从这个文件中读取的那些类型,自定义我们自己的启动加载类。
可以参考spring-boot-autoconfigure-1.5.3.RELEASE.jar中的该文件配置:

 *      # Initializers
 *          org.springframework.context.ApplicationContextInitializer=\
 *           org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
 *           org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
 *
 *      # Application Listeners
 *          org.springframework.context.ApplicationListener=\
 *          org.springframework.boot.autoconfigure.BackgroundPreinitializer
 *
 *      # Auto Configuration Import Listeners
 *          org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
 *          org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
 *
 *      # Auto Configuration Import Filters
 *          org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
 *          org.springframework.boot.autoconfigure.condition.OnClassCondition
 *
 *      # Auto Configure
 *          org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
 *          org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
 *          org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
  1. springboot除了有spring ioc中的ApplicationListener,还有自己的SpringApplicationRunListener和ApplicationRunner和CommandLineRunner.
    SpringApplicationRunListener在springboot整个启动过程中都有监听, ApplicationRunner和CommandLineRunner在springboot启动完成后才会回调。我们可以根据自己的需要选择合适的监听器

习惯大于配置的原理。

看完上面的run方法。好像没有找到我们平时整合mybatis等等的时候省去的繁琐的配置的原因。别急,Springboot的入口类除了上面的SpringApplication.run()以外还有一个核心注解@SpringBootApplication。
让我们点进去看看这个注解到底干了什么。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

可以看到@SpringBootApplication注解厉害还有很多其他的注解。其中@ComponentScan设置spring扫描的包路径,默认扫描该注解所在类的路径及子路经,所以我们配置了@SpringBootApplication之后一般就不用在配置@ComponentScan了。

另一个也是最核心的注解就是@EnableAutoConfiguration。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

可以看到他导入了EnableAutoConfigurationImportSelector。

public class EnableAutoConfigurationImportSelector
		extends AutoConfigurationImportSelector {
	@Override
	protected boolean isEnabled(AnnotationMetadata metadata) {
		if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
			return getEnvironment().getProperty(
					EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
					true);
		}
		return true;
	}
}

这里好像没有做什么事情,别急,让我们在点进他的父类AutoConfigurationImportSelector看一下

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		try {
			AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
					.loadMetadata(this.beanClassLoader);
			AnnotationAttributes attributes = getAttributes(annotationMetadata);
			List<String> configurations = getCandidateConfigurations(annotationMetadata,
					attributes);
			configurations = removeDuplicates(configurations);
			configurations = sort(configurations, autoConfigurationMetadata);
			Set<String> exclusions = getExclusions(annotationMetadata, attributes);
			checkExcludedClasses(configurations, exclusions);
			configurations.removeAll(exclusions);
			configurations = filter(configurations, autoConfigurationMetadata);
			fireAutoConfigurationImportEvents(configurations, exclusions);
			return configurations.toArray(new String[configurations.size()]);
		}
		catch (IOException ex) {
			throw new IllegalStateException(ex);
		}
	}

可以看到原来@EnableAutoConfiguration的主要实现就是在AutoConfigurationImportSelector里面。

上面的方法主要功能是去读取自己工程和依赖工程项目下的META-INF/spring.factories中的org.springframework.boot.autoconfigure.EnableAutoConfiguration指向的类并实例化。这样那些我们引入的相关springboot依赖就可以自动注入他们的bean,省去了我们自己手动注入的麻烦,从而实现springboot习惯大于配置的特点

比如:
springboot整合mybatis需要引入

 *  <groupId>org.mybatis.spring.boot</groupId>
 *  <artifactId>mybatis-spring-boot-starter</artifactId>

mybatis-spring-boot-starter包含的mybatis-boot-autoconfigure.jar的META-INF/spring.factories里面就添加了

 *  org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
 *  org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
  • 这样在@EnableAutoConfiguration注解读取所有jar包的META-INF/spring.factories的EnableAutoConfiguration时就会注入上面的org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
  • 当然,springboot还有一点智能的地方就是就算引入了上面的jar包也不会一定注入该bean。
    还是用上面的MybatisAutoConfiguration举例:
    该类上面有如下注解:
    @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
    @ConditionalOnBean(DataSource.class)
    @EnableConfigurationProperties(MybatisProperties.class)
    @AutoConfigureAfter(DataSourceAutoConfiguration.class)

也就是当我们引入了springboot-mybatis整合包后,还必须符合上面这些condition条件才会自动注入mybatis相关bean

总结

除了上面SpringApplication.run()方法里面的我们可以定义很多合适的listener监听者监听Springboot启动的不同事件以及定义自己的METF-INF/spring.factories里面的各种类以外。我们还可以在METF-INF/spring.factories里面定义我们自己的 org.springframework.boot.autoconfigure.EnableAutoConfiguration,这样我们就可以让springboot启动的时候自动注入我们想要注入的bean了。
这种方式还是有作用的,如果我们是分模块开发,可能有些通用的类我们不想重复在每个工程里使用。我们一般就会把他放到公共模块里面,但是这样Springboot启动的时候可能就不会扫描注入他,因为他不再我们当前项目目录下。这时候我们就可以在这个通用模块里面添加METF-INF/spring.factories文件,并配置org.springframework.boot.autoconfigure.EnableAutoConfiguration=我们想要注入的bean类。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值