SpringBoot程序启动原理及⾃动化配置的原理
主要内容:SpringBott程序启动原理、⾃动化配置(@EnableAutoConfiguration)的原理
启动:
进⾏SpringApplication的初始化模块,配置⼀些基本的环境变量、资源、构造器、;
实现了应⽤具体的启动⽅案,包括启动流程的监听模块、加载配置环境模块、及核⼼的创建上下⽂环境模块;
进⾏⾃动化配置模块,该模块作为springboot⾃动配置核⼼。
⾃动配置:SpringFactoriesLoader——会从classpath中搜寻所有的META-INF/spring.factories配置⽂件,并将其中
org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为⼀个并加载到IoC容器。
Spring Starter依赖(如Spring Consul)与原⽣依赖(如Consul)相⽐的优点就在于前者只要引⼊依赖就可以开箱即⽤,⽽不需要进⾏很多配置,这就是依赖于Spring的⾃动配置。
很多Spring依赖与我们的项⽬并没有公共包前缀(如Spring Consul),⽽我们在ComponentScan指定basePath时也没有将Spring Consul的包路径包含进来,那为何只要引⼊依赖就能⽤?原因就在于SpringFactoriesLoader会⾃动加载类路径上各依赖中的META-INF/spring.factories⽂件⾥指定的依赖⽂件并实例化,从⽽达到⾃动配置。这也是为什么很多Spring依赖引⼊后即使没有在main所在类上加@Enable*注解该依赖也⽣效的原因
(@Enable*的作⽤本质上是引⼊配置⽂件、⽣成⽰例并注册到容器,从⽽使配置⽂件⽣效)。
===================
转⾃:,以下为正⽂。
我们开发任何⼀个Spring Boot项⽬,都会⽤到如下的启动类
1 @SpringBootApplication
2 public class Application {
3    public static void main(String[] args) {
4        SpringApplication.run(Application.class, args);
5    }
6 }
从上⾯代码可以看出,Annotation定义(@SpringBootApplication)和类定义(SpringApplication.run)最为耀眼,所以要揭开SpringBoot的神秘⾯纱,我们要从这两位开始就可以了。
⼀、SpringBootApplication背后的秘密
@SpringBootApplication注解是Spring Boot的核⼼注解,它其实是⼀个组合注解:
1 @Target(ElementType.TYPE)
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 @Inherited
5 @SpringBootConfiguration
6 @EnableAutoConfiguration
7 @ComponentScan(excludeFilters = {
8        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
9        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
10 public @interface SpringBootApplication {
11 ...
12 }
虽然定义使⽤了多个Annotation进⾏了原信息标注,但实际上重要的只有三个Annotation:
@Configuration(@SpringBootConfiguration点开查看发现⾥⾯还是应⽤了@Configuration)
@EnableAutoConfiguration
@ComponentScan
即 @SpringBootApplication = (默认属性)@Configuration + @EnableAutoConfiguration + @ComponentScan。
所以,如果我们使⽤如下的SpringBoot启动类,整个SpringBoot应⽤依然可以与之前的启动类功能对等:
1 @Configuration
2 @EnableAutoConfiguration
3 @ComponentScan
4 public class Application {
5    public static void main(String[] args) {
6        SpringApplication.run(Application.class, args);
7    }
8 }
每次写这3个⽐较累,所以写⼀个@SpringBootApplication⽅便点。接下来分别介绍这3个Annotation。
1、@Configuration
这⾥的@Configuration对我们来说不陌⽣,它就是JavaConfig形式的Spring Ioc容器的配置类使⽤的那个@Configuration,SpringBoot社区推荐使⽤基于JavaConfig的配置形式,所以,这⾥的启动类标注了@Configuration之后,本⾝其实也是⼀个IoC容器的配置类。
举⼏个简单例⼦回顾下,XML跟config配置⽅式的区别:
(1)表达形式层⾯
基于XML配置的⽅式是这样:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="/schema/beans"
3        xmlns:xsi="/2001/XMLSchema-instance"
4        xsi:schemaLocation="/schema/beans /schema/beans/spring-beans-3.0.xsd"
5        default-lazy-init="true">
6    <!--bean定义-->
7 </beans>
⽽基于JavaConfig的配置⽅式是这样:
1 @Configuration
2 public class MockConfiguration{
3    //bean定义
4 }
任何⼀个标注了@Configuration的Java类定义都是⼀个JavaConfig配置类。
(2)注册bean定义层⾯
基于XML的配置形式是这样:
1 <bean id="mockService" class="..MockServiceImpl">
2    ...
3 </bean>
⽽基于JavaConfig的配置形式是这样的:
1 @Configuration
2 public class MockConfiguration{
3    @Bean
4    public MockService mockService(){
5        return new MockServiceImpl();
6    }
7 }
任何⼀个标注了@Bean的⽅法,其返回值将作为⼀个bean定义注册到Spring的IoC容器,⽅法名将默认成该bean定义的id。
(3)表达依赖注⼊关系层⾯
为了表达bean与bean之间的依赖关系,在XML形式中⼀般是这样:
1 <bean id="mockService" class="..MockServiceImpl">
2    <propery name ="dependencyService" ref="dependencyService" />
3 </bean>
4
5 <bean id="dependencyService" class="DependencyServiceImpl"></bean>
⽽基于JavaConfig的配置形式是这样的:
1 @Configuration
2 public class MockConfiguration{
3    @Bean
4    public MockService mockService(){
5        return new MockServiceImpl(dependencyService());
6    }
7
8    @Bean
9    public DependencyService dependencyService(){
10        return new DependencyServiceImpl();
springboot框架的作用11    }
12 }
如果⼀个bean的定义依赖其他bean,则直接调⽤对应的JavaConfig类中依赖bean的创建⽅法就可以了。
@Configuration:提到@Configuration就要提到他的搭档@Bean。使⽤这两个注解就可以创建⼀个简单的spring配置类,可以⽤来替代相应的xml配置⽂件。
1 <beans>
2    <bean id = "car" class="st.Car">
3        <property name="wheel" ref = "wheel"></property>
4    </bean>
5    <bean id = "wheel" class="st.Wheel"></bean>
6 </beans>
相当于:
1 @Configuration
2 public class Conf {
3    @Bean
4    public Car car() {
5        Car car = new Car();
6        car.setWheel(wheel());
7        return car;
8    }
9
10    @Bean
11    public Wheel wheel() {
12        return new Wheel();
13    }
14 }
@Configuration的注解类标识这个类可以使⽤Spring IoC容器作为bean定义的来源。
@Bean注解告诉Spring,⼀个带有@Bean的注解⽅法将返回⼀个对象,该对象应该被注册为在Spring应⽤程序上下⽂中的bean。
2、@ComponentScan
@ComponentScan这个注解在Spring中很重要,它对应XML配置中的元素,@ComponentScan的功能其实就是⾃动扫描并加载符合条件的组件(⽐如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。
我们可以通过basePackages等属性来细粒度的定制@ComponentScan⾃动扫描的范围,如果不指定,则默认Spring框架实现会从声明
@ComponentScan所在类的package进⾏扫描。
注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。
3、@EnableAutoConfiguration
个⼈感觉@EnableAutoConfiguration这个Annotation最为重要,所以放在最后来解读,⼤家是否还记得Spring框架提供的各种名字为@Enable开头的Annotation定义?⽐如@EnableScheduling、@EnableCaching、@EnableMBeanExport等,@EnableAutoConfiguration的理念和做事⽅式其实⼀脉相承,简单概括⼀下就是,借助@Import的⽀持,收集和注册特定场景相关的bean定义。
@EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到IoC容器。
@EnableMBeanExport是通过@Import将JMX相关的bean定义加载到IoC容器。
⽽@EnableAutoConfiguration也是借助@Import的帮助,将所有符合⾃动配置条件的bean定义加载到IoC容器,仅此⽽已!
@EnableAutoConfiguration会根据类路径中的jar依赖为项⽬进⾏⾃动配置,如:添加了spring-boot-starter-web依赖,会⾃动添加Tomcat和Spring MVC 的依赖,Spring Boot会对Tomcat和Spring MVC进⾏⾃动配置。
@EnableAutoConfiguration作为⼀个复合Annotation,其⾃⾝定义关键信息如下:
1 @SuppressWarnings("deprecation")
2 @Target(ElementType.TYPE)
3 @Retention(RetentionPolicy.RUNTIME)
4 @Documented
5 @Inherited
6 @AutoConfigurationPackage
7 @Import(EnableAutoConfigurationImportSelector.class)
8 public @interface EnableAutoConfiguration {
9    ...
10 }
其中,最关键的要属@Import(EnableAutoConfigurationImportSelector.class),借助
EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应⽤将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使⽤的IoC容器。就像⼀只“⼋⽖鱼”⼀样,借助于Spring框架原有的⼀个⼯具类:SpringFactoriesLoader的⽀
持,@EnableAutoConfiguration可以智能的⾃动配置功效才得以⼤功告成!
⾃动配置幕后英雄:SpringFactoriesLoader详解
SpringFactoriesLoader属于Spring框架私有的⼀种扩展⽅案,其主要功能就是从指定的配置⽂件MET
A-INF/spring.factories加载配置。
1 public abstract class SpringFactoriesLoader {
2    //...
3    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
4        ...
5    }
6
7
8    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
9        ....
10    }
11 }
配合@EnableAutoConfiguration使⽤的话,它更多是提供⼀种配置查的功能⽀持,即根据@EnableAutoConfiguration的完整类名
org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查的Key,获取对应的⼀组@Configuration类。
上图就是从SpringBoot的autoconfigure依赖包中的META-INF/spring.factories配置⽂件中摘录的⼀段内容,可以很好地说明问题。
所以,@EnableAutoConfiguration⾃动配置的魔法骑⼠就变成了:从classpath中搜寻所有的META-INF/spring.factories配置⽂件,并将其中
org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为⼀个并加载到IoC容器。
⼆、深⼊探索SpringApplication执⾏流程
SpringApplication的run⽅法的实现是我们本次旅程的主要线路,该⽅法的主要流程⼤体可以归纳如下:
1)如果我们使⽤的是SpringApplication的静态run⽅法,那么,这个⽅法⾥⾯⾸先要创建⼀个SpringApplication对象实例,然后调⽤这个创建好的SpringApplication的实例⽅法。在SpringApplication实例初始化的时候,它会提前做⼏件事情:
根据classpath⾥⾯是否存在某个特征类(org.t.ConfigurableWebApplicationContext)来决定是否应该创建⼀个为Web 应⽤使⽤的ApplicationContext类型。
使⽤SpringFactoriesLoader在应⽤的classpath中查并加载所有可⽤的ApplicationContextInitializer。
使⽤SpringFactoriesLoader在应⽤的classpath中查并加载所有可⽤的ApplicationListener。
推断并设置main⽅法的定义类。
2) SpringApplication实例初始化完成并且完成设置后,就开始执⾏run⽅法的逻辑了,⽅法执⾏伊始,⾸先遍历执⾏所有通过SpringFactoriesLoader可以查到并加载的SpringApplicationRunListener。调⽤它们的started()⽅法,告诉这些SpringApplicationRunListener,“嘿,SpringBoot应⽤要开始执⾏咯!”。
3)创建并配置当前Spring Boot应⽤将要使⽤的Environment(包括配置要使⽤的PropertySource以及Profile)。
4)遍历调⽤所有SpringApplicationRunListener的environmentPrepared()的⽅法,告诉他们:“当前SpringBoot应⽤使⽤的Environment准备好了咯!”。5)如果SpringApplication的showBanner属性被设置为true,则打印banner。
6)根据⽤户是否明确设置了applicationContextClass类型以及初始化阶段的推断结果,决定该为当前SpringBoot应⽤创建什么类型的ApplicationContext 并创建完成,然后根据条件决定是否添加ShutdownHook,决定是否使⽤⾃定义的BeanNameGenerator,决定是否使⽤⾃定义的ResourceLoader,当然,最重要的,将之前准备好的Environment设置给创建好的ApplicationContext使⽤。
7) ApplicationContext创建好之后,SpringApplication会再次借助Spring-FactoriesLoader,查并加载classpath中所有可⽤的ApplicationContext-Initializer,然后遍历调⽤这些ApplicationContextInitializer的initialize(applicationContext)⽅法来对已经创建好的ApplicationContext进⾏进⼀步的处理。
8)遍历调⽤所有SpringApplicationRunListener的contextPrepared()⽅法。
9)最核⼼的⼀步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。10)遍历调⽤所有SpringApplicationRunListener的contextLoaded()⽅法。
11)调⽤ApplicationContext的refresh()⽅法,完成IoC容器可⽤的最后⼀道⼯序。
12)查当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执⾏它们。
13)正常情况下,遍历执⾏SpringApplicationRunListener的finished()⽅法、(如果整个过程出现异常,则依然调⽤所有SpringApplicationRunListener的finished()⽅法,只不过这种情况下会将异常信息⼀并传⼊处理)
去除事件通知点后,整个流程如下:
本⽂以调试⼀个实际的SpringBoot启动程序为例,参考流程中主要类类图,来分析其启动逻辑和⾃动化配置原理。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。