(Transfer) Spring Boot dry goods series: (3) Analysis of startup principle

Transfer: http://tengj.top/2017/03/09/springboot3/

foreword

In the previous chapters, we have seen the automatic configuration that SpringBoot does for us, which is really convenient and fast, but for novices, if they don't understand the internal startup principle of SpringBoot, they will inevitably suffer in the future. So this time, the blogger will work with you to unveil the mystery of SpringBoot step by step, so that it is not mysterious.

text

When we develop any Spring Boot project, we will use the following startup classes

1
2
3
4
5
6
@SpringBootApplication
public class Application {public static void main(String[] args) { SpringApplication.run(Application.class, args); }}




 

As can be seen from the above code, the Annotation definition (@SpringBootApplication) and the class definition (SpringApplication.run) are the most dazzling, so to uncover the mystery of SpringBoot, we have to start with these two.

The secret behind SpringBootApplication

1
2
3
4
5
6
7
8
9
10
11
12
@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) })public @interface SpringBootApplication {...}





Although the definition uses multiple Annotations to label the original information, there are actually only three Annotations that are important:

  • @Configuration (@SpringBootConfiguration click to view and find that @Configuration is still applied)
  • @EnableAutoConfiguration
  • @ComponentScan

Therefore, if we use the following SpringBoot startup class, the entire SpringBoot application can still be functionally equivalent to the previous startup class:

1
2
3
4
5
6
7
8
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {public static void main(String[] args) { SpringApplication.run(Application.class, args); }}




 

It is tiring to write these three each time, so it is convenient to write a @SpringBootApplication. Next, these three Annotations are introduced separately.

@Configuration

The @Configuration here is not unfamiliar to us. It is the @Configuration used by the configuration class of the Spring Ioc container in the form of JavaConfig. The SpringBoot community recommends using the configuration form based on JavaConfig. Therefore, after the startup class here is marked with @Configuration, It is actually a configuration class of an IoC container.
To give a few simple examples to review, the difference between XML and config configuration methods:


  • The XML-based configuration at the expression level looks like this:
    1
    2
    3
    4
    5
    6
    7
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"default-lazy-init="true"><!--bean定义--></beans>





The configuration based on JavaConfig is like this:

1
2
3
4
@Configuration
public class MockConfiguration{//bean定义}


 

Any Java class definition annotated with @Configuration is a JavaConfig configuration class.


  • The XML-based configuration at the registration bean definition level looks like this:
    1
    2
    3
    <bean id="mockService" class="..MockServiceImpl">
    ...
    </bean>

The configuration based on JavaConfig is like this:

1
2
3
4
5
6
7
@Configuration
public class MockConfiguration{@Beanpublic MockService mockService(){return new MockServiceImpl(); }}





 

For any method marked with @Bean, its return value will be registered in Spring's IoC container as a bean definition, and the method name will default to the id of the bean definition.

  • Expressing the dependency injection relationship level
    In order to express the dependency between beans and beans, it is generally like this in XML form:
1
2
3
4
5
<bean id="mockService" class="..MockServiceImpl">
<propery name ="dependencyService" ref="dependencyService" />
</bean>

<bean id="dependencyService" class="DependencyServiceImpl"></bean>

而基于JavaConfig的配置形式是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class MockConfiguration{
@Bean
public MockService mockService(){
return new MockServiceImpl(dependencyService());
}

@Bean
public DependencyService dependencyService(){
return new DependencyServiceImpl();
}
}

 

如果一个bean的定义依赖其他bean,则直接调用对应的JavaConfig类中依赖bean的创建方法就可以了。

@ComponentScan

@ComponentScan这个注解在Spring中很重要,它对应XML配置中的元素,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。

我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。

注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。

@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作为一个复合Annotation,其自身定义关键信息如下:

1
2
3
4
5
6
7
8
9
10
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}

 

其中,最关键的要属@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。就像一只“八爪鱼”一样

借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自动配置功效才得以大功告成!

自动配置幕后英雄:SpringFactoriesLoader详解
SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。

1
2
3
4
5
6
7
8
9
10
11
public abstract class SpringFactoriesLoader {
//...
public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
...
}


public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
....
}
}

 

配合@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.springframework.web.context.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的核心组件完成了基本的解析,综合来看,大部分都是Spring框架背后的一些概念和实践方式,SpringBoot只是在这些概念和实践上对特定的场景事先进行了固化和升华,而也恰恰是这些固化让我们开发基于Sping框架的应用更加方便高效。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325222866&siteId=291194637