自动配置
使用 Spring Boot 开发较之以前的基于 xml 配置式的开发,要简捷方便快速的多。而这完全得益于 Spring Boot 的自动配置。下面就通过源码阅读方式来分析自动配置的运行原理。
打开启动类的@SpringBootApplication 注解源码。
1 | java复制代码@SpringBootApplication |
查看@SpringBootApplication
注解的源码,我们发现@SpringBootApplication 注解其实就是一个组合注解,如下:
1 | java复制代码@Target(ElementType.TYPE) |
元注解
@Target(ElementType.TYPE)
:表示当前注解仅能注解在类上。@Retention(RetentionPolicy.RUNTIME)
:表示当前注解的生存时间为源码、字节码,及 JVM 运行期间。@Documented
:表示当前注解的注释信息将显示在 javaAPI 文档中@Inherited
:表示当前注解所标注的类的子类将会继承该类上的注解
@SpringBootConfiguration
该注解与@Configuration
注解功能完全相同。即标注该类是Spring配置类
1 | java复制代码@Target(ElementType.TYPE) |
@ComponentScan
用于指定当前应用所要扫描的包。
@EnableAutoConfiguration
该注解用于完成自动配置,是 Spring Boot 的核心注解。后面要详细解析。这里主要是要对@EnableXxx 注解进行说明。@EnableXxx 注解一般用于开启某一项功能,是为了避免简化配置代码的引入。其是组合注解,一般情况下@EnableXxx 注解中都会组合一个@Import 注解,而该@Import 注解用于导入指定的类,而该被导入的类一般为配置类。其导入配置类的方式常见的有三种:
- A、直接引入配置类,@Import 中指定的类一般为 Configuration 结尾,且该类上会注解@Configuration,表示当前类为 JavaConfig 类。例如用于开启定时任务的@EnableScheduling 注解的@Import。
- B、 根据条件选择配置类,@Import 中指定的类一般以 ConfigurationSelector 结尾,且该类实现了 ImportSelector接口,表示当前类会根据条件选择不同的配置类导入。例如,用于开启缓存功能的@EnableCaching 的@Import。
- C、 动态注册 Bean,@Import 中指定的类一般以 Registrar 结尾,且该类实现了 ImportBeanDefinitionRegistrar接口,用于表示在代码运行时若使用了到该配置类,则系统会自动将其导入。例如,用于开启 AspectJ 自动代理功能的@EnableAspectJAutoProxy 注解的@Import。
查看@EnableAutoConfiguration
注解如下:
1 | java复制代码@Target(ElementType.TYPE) |
该注解用于完成自动配置,是 Spring Boot 的核心注解,是一个组合注解。所谓自动配置是指,将用户自定义的类及框架本身用到的类进行装配。其中最重要的注解有两个:
- @AutoConfigurationPackage:用于导入并装配用户自定义类,即自动扫描包中的类
- @Import:用于导入并装配框架本身的类
@Import
由于AutoConfigurationImportSelector
类间接实现了ImportSelector
,该接口的方法selectImports
能够将返回的类的全限定类名集合中的所有类都注册到Spring容器中。
1 | java复制代码public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, |
接着查看getAutoConfigurationEntry
方法,如下:
1 | java复制代码protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, |
接着getCandidateConfigurations
方法,如下:
1 | java复制代码protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { |
接着查看SpringFactoriesLoader
的loadFactoryNames
方法
1 | java复制代码public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { |
spring.factories
文件的内容如下:
1 | properties复制代码# Initializers |
综上AutoConfigurationImportSelector
类的作用就是将META-INF/spring.factories
中默认的全限定类名对应的类注册到Spring容器中。
@AutoConfigurationPackage
该注解的定义如下:
1 | java复制代码@Target(ElementType.TYPE) |
接着重点查看@Import(AutoConfigurationPackages.Registrar.class)
中的AutoConfigurationPackages.Registrar
类
1 | java复制代码static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { |
Registrar
实现了ImportBeanDefinitionRegistrar
接口,该接口的registerBeanDefinitions
方法能够往Spring的容器BeanDefinitionMap
中注册自定义的BeanDefinition
对象。又由于metadata
封装了我们的@SpringBootApplication
信息,调用getPackageName()
返回带有@SpringBootApplication
注解类的包名,所以@AutoConfigurationPackage
注解的作用就是扫描启动类(即带有@SpringBootApplication
注解的类)所在的包及其子包下的类,封装成BeanDefinition
对象,注册到Spring容器BeanDefinitionMap
中。
自定义Starter
自定义starter
1.首先创建工程zdy-spring-boot-starter
,引入依赖如下:
1 | xml复制代码<dependencies> |
2.编写javaBean,如下:
1 | java复制代码/** |
3.编写配置类MyAutoConfifiguration,如下:
1 | java复制代码@Configuration |
4.在resources
下创建/META-INF/spring.factories
1 | properties复制代码org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
使用starter
1.引入上面自定义starter工程的依赖,如下:
1 | xml复制代码<dependency> |
2.在application.properties
中配置属性值
1 | properties复制代码simplebean.id=1 |
3.编写测试方法
1 | java复制代码@RunWith(SpringRunner.class) //测试启动器,并加载spring boot测试注解 |
启动源码
大家都知道SpringBoot项目启动主要是依靠启动类中的run
方法,例如下面的Springboot01DemoApplication
类
1 | java复制代码@SpringBootApplication |
接着查看run
方法,如下:
1 | java复制代码/** |
从以上代码可知,run
方法主要分为两个步骤,一个是SpringApplication
实例化,另外一个是调用SpringApplication
的run
方法。
SpringApplication实例化
首先来查看上面代码中的new SpringApplication(primarySources)
,primarySources
参数就是run
方法传进来的项目启动类Springboot01DemoApplication
。
1 | java复制代码public SpringApplication(Class<?>... primarySources) { |
从上述源码可以看出,SpringApplication的初始化过程主要包括4部分,具体说明如下。
- (1)this.webApplicationType = WebApplicationType.deduceFromClasspath()用于判断当前webApplicationType应用的类型。deduceFromClasspath()方法用于查看Classpath类路径下是否存在某个特征类,从而判断当前webApplicationType类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
1 | java复制代码/** |
- (2)this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))用于SpringApplication应用的初始化器设置。在初始化器设置过程中,会使用Spring类加载器SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中获取所有可用的应用初始化器类ApplicationContextInitializer。
1 | java复制代码/** |
接着查看SpringFactoriesLoader.loadFactoryNames(type, classLoader)
,如下:
1 | java复制代码public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { |
从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中获取所有可用的应用初始化器类ApplicationContextInitializer,一共有两个,如下:
1 | 复制代码org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer, |
- (3)this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class))用于SpringApplication应用的监听器设置。监听器设置的过程与上一步初始化器设置的过程基本一样,也是使用SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中获取所有可用的监听器类ApplicationListener。
代码跟第二步的类似,从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中获取所有可用的监听器类ApplicationListener。,如下图所示
- (4)this.mainApplicationClass = this.deduceMainApplicationClass()用于推断并设置项目main()方法启动的主程序启动类
run方法
接着查看run
方法,如下:
1 | java复制代码public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { |
从上述源码可以看出,项目初始化启动过程大致包括以下部分:
- 第一步:获取并启动监听器
this.getRunListeners(args)和listeners.starting()方法主要用于获取SpringApplication实例初始化过程中初始化的SpringApplicationRunListener监听器并运行。
- 第二步:根据SpringApplicationRunListeners以及参数来准备环境
this.prepareEnvironment(listeners, applicationArguments)方法主要用于对项目运行环境进行预设置,同时通过this.configureIgnoreBeanInfo(environment)方法排除一些不需要的运行环境
- 第三步:创建Spring容器根据webApplicationType进行判断, 确定容器类型,如果该类型为SERVLET类型,会通过反射装载
对应的字节码,也就是AnnotationConfigServletWebServerApplicationContext,接着使用之前初始化设置的context(应用上下文环境)、environment(项目运行环境)、listeners(运行监听器)、applicationArguments(项目参数)和printedBanner(项目图标信息)进行应用上下文的组装配置,并刷新配置
- 第四步:Spring容器前置处理
这一步主要是在容器刷新之前的准备动作。设置容器环境,包括各种变量等等,其中包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础
- 第五步:刷新容器
开启刷新spring容器,通过refresh方法对整个IOC容器的初始化(包括bean资源的定位,解析,注册等等),同时向JVM运行时注册一个关机钩子,在JVM关机时会关闭这个上下文,除非当时它已经关闭
- 第六步:Spring容器后置处理
扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。
- 第七步:发出结束执行的事件
获取EventPublishingRunListener监听器,并执行其started方法,并且将创建的Spring容器传进去了,创建一个ApplicationStartedEvent事件,并执行ConfigurableApplicationContext 的publishEvent方法,也就是说这里是在Spring容器中发布事件,并不是在SpringApplication中发布事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的监听器发布启动事件。
- 第八步:执行Runners
用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序。其中,Spring Boot提供的执行器接口有ApplicationRunner 和CommandLineRunner两种,在使用时只需要自定义一个执行器类实现其中一个接口并重写对应的run()方法接口,然后Spring Boot项目启动后会立即执行这些特定程序
下面,通过一个Spring Boot执行流程图,让大家更清晰的知道Spring Boot的整体执行流程和主要启动阶段:
本文转载自: 掘金