SpringBoot启动流程源码解析

前言

SpringBoot本质上没有技术革新,而是在Spring框架的基础之上简化了系统的配置,核心功能是自动装配和starter组件

一、SpringBoot核心功能

1、独立运行程序(SpringBoot程序可以不依赖其他容器独立运行)

2、内嵌Servlet容器(内嵌Servlet容器,不依赖其他Web容器)

3、提供starter组件简化maven配置(只需要依赖一个starter组件就可以自动依赖相关jar包,简化maven配置)

4、自动装配配置(为大多数应用场景提供了默认配置,大幅度减少了应用配置)

二、SpringBoot常用注解

注解 修饰 用途 案例
@ComponentScan 自动扫描,修饰某个类则Spring会自动扫描当前类所在包路径下所以被@Component修饰的类,也可以通过basePackages自定义扫描包的根目录 @ComponentScan(basePackages="com.test.lucky")
@Component 声明当前类表示当前类为一个组件,Spring容器会很自动扫描被@Component修饰的类加载bean到Spring容器中 @Component(value="beanName")
@Controller 本质是一个Component,声明当前类表示当前类是一个控制器类,分发处理器会扫描被@Controller注解修饰的类的方法 @Controller(value="testController")
@RequestMapping 方法 声明方法表示请求URL映射的方法 @RequestMapping(value="/test")
@ResponseBody 方法/类 将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据 @ResponseBody
@RestController 等同于@Controller + @ResponseBody组合,表示当前类的所有方法都自动被@ResponseBody注解修饰 @RestController(value="testController")
@Service 本质是一个Component, 声明当前类是Service层的bean @Service(value="testService")
@Repository 本质是一个Component, 声明当前类是Dao层的bean @Repository(value="testDao")
@Configuration 本质是一个Component, 表面当前类是一个配置类,会生成一个代理对象,表示当前类是@Bean定义的源类 @Configuration
@Bean 方法 相当于<bean>标签,被@Bean修饰的方法返回对象会被加载到Spring容器中 @Bean(name="beanName")
@Import 用于导入第三方包中的bean,导入的bean自动加载到Spring容器中 @Import(value=Test.class)
@Value 方法/属性 将常量、配置中的值、其他bean注入到变量中 @Value("${properteis.test}")

三、SpringBoot启动流程解析

SpringBoot的功能是基于Spring框架,所以启动时的核心步骤自然少不了Spring容器的初始化以及Spring容器的启动过程,另外SpringBoot还提供了自动装配配置功能。

1、SpringBootApplication.run()方法

SpringBoot程序启动类案例如下:

1 @SpringBootApplication
2 public class BootStrap {
3 
4     public static void main(String[] args){
5         ApplicationContext applicationContext = SpringApplication.run(BootStrap.class);
6     }
7 }

SpringBoot程序启动入口,代码比较简洁,除了被@SpringBootApplication注解修饰之外,main丰富直接调用SpringBootApplication类的静态方法run方法即可,最终会是调用了SpringBootApplication的实例方法run方法,核心逻辑如下:

 1 public ConfigurableApplicationContext run(String... args) {
 2         ConfigurableApplicationContext context = null;
 3         /** 1.通过反射创建ApplicationContext对象 */
 4         context = createApplicationContext();
 5         /** 2.准备ApplicationContext上下文环境 */
 6         prepareContext(context, environment, listeners, applicationArguments, printedBanner);
 7         /** 3.刷新ApplicationContext */
 8         refreshContext(context);
 9         /** 4.刷新ApplicationContext后置处理*/
10         afterRefresh(context, applicationArguments);
11         return context;
12     }

可以看出SpringBoot程序启动的核心逻辑实际就两步,1、创建IOC容器ApplicationContext实例;2、启动IOC容器

而SpringBoot的其他核心功能很显然就是通过@SpringBootApplication注解来实现,这个注解才是SpringBoot的核心实现

2、@SpringBootApplication注解

@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) })
public @interface SpringBootApplication

从定义可以看出@SpringBootApplication是一个组合注解,内部包含了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三个注解

2.1、@SpringBootConfiguration

@SpringBootConfiguration注解定义如下:

1 @Target(ElementType.TYPE)
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 @Configuration
5 public @interface SpringBootConfiguration

可以看出@SpringBootConfiguration本质上就是一个@Configuration注解,标注当前类是一个配置类

2.2、@ComponentScan

@ComponentScan注解定义如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan 

@ComponentScan注解表示自动扫描,会扫描当前类包路径下所有被@Component注解修饰的bean

2.3、@EnableAutoConfiguration

@EnableAutoConfiguration注解定义如下:

1 @Target(ElementType.TYPE)
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 @Inherited
5 @AutoConfigurationPackage
6 @Import(AutoConfigurationImportSelector.class)
7 public @interface EnableAutoConfiguration

可以看出@EnableAutoConfiguration注解也是一个组合注解,包含了@AutoConfigurationPackage和@Import注解,再看@AutoConfigurationPackage注解的定义:

1 @Target(ElementType.TYPE)
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 @Inherited
5 @Import(AutoConfigurationPackages.Registrar.class)
6 public @interface AutoConfigurationPackage

该注解内部包含了一个@Import注解,所以可以得出结论@EnableAutoConfiguration实际上就是由两个@Import注解组成,分别导入了AutoConfiguartionImportSelector和AutoConfigurationPackages.Registrar类的实例。

所以总结可以得出这两个类是实现SpringBoot自动装载配置的核心实现。

关于@Import注解的详细用法这里不多介绍,先简单介绍下,@Import注解有三种用法,分别如下:

1.显示引入需要加载的bean,如:@Import(value=TestService.class)

2.引入实现ImportSelector接口的类,如自定义MyImportSelector类实现ImportSelector并重写selectImports方法,返回需要导入的bean的数组,案例如下:

1 public class MyImportSelector implements ImportSelector {
2     @Override
3     public String[] selectImports(AnnotationMetadata importingClassMetadata) {
4         return new String[]{UserService.class.getName(), GoodsService.class.getName()};
5     }
6 }

3.引入实现ImportBeanDefinitionRegistrar接口的类,如自定义MyImportBeanDefinitionRegistrar并重写registBeanDefinitions方法,手动注册需要加载的bean,案例如下:

1 public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
2 
3     @Override
4     public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
5                                          BeanNameGenerator importBeanNameGenerator) {
6         /** 手动注册需要加载的bean*/
7         registerBeanDefinitions(importingClassMetadata, registry);
8     }
9 }

回过头再看@EnableAutoConfiguration注解,分别导入了ImportSelector接口的实现类AutoConfigurationImportSelector和ImportBeanDefinitionRegistrar的实现类AutoConfigurationPackages.Registrar,接下来依次分析。

2.4、AutoConfigurationImportSelector

AutoConfigurationImportSelector实现了ImportSelector接口,所以会加载selectImports方法返回的String数组中的beanName,所以重点是selectImports方法,源码如下:

 1 public String[] selectImports(AnnotationMetadata annotationMetadata) {
 2         if (!isEnabled(annotationMetadata)) {
 3             return NO_IMPORTS;
 4         }
 5         /** 加载元数据*/
 6         AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
 7                 .loadMetadata(this.beanClassLoader);
 8         AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
 9                 annotationMetadata);
10         return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
11     }

核心步骤就两步,第一步是执行loadMetadata方法,源码如下:

 1 protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
 2 
 3     static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
 4         /** 加载指定路径下的元数据*/
 5         return loadMetadata(classLoader, PATH);
 6     }
 7 
 8     static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
 9         try {
10             Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
11                     : ClassLoader.getSystemResources(path);
12             Properties properties = new Properties();
13             /** 遍历Spring-autoconfigure-metadata.properties配置文件,读取数据封装为AutoConfigurationMetadata对象*/
14             while (urls.hasMoreElements()) {
15                 properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
16             }
17             return loadMetadata(properties);
18         }
19         catch (IOException ex) {
20             throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
21         }
22     }

逻辑是从加载路径下找到spring-autoconfigure-metadata.properties配置文件,然后遍历读取配置文件内容加入到Properties对象中,并封装成AutoConfigurationMetadata对象

spring-autoconfigure-metadata.properties配置文件位于spring-boot-autoconfigure包中的META-INF目录下,暂时可以不关心作用是什么,目前可以得出的结论是会先把这个配置中的内容全部加载出来。

再看第二步getAutoConfigurationEntry方法的实现,源码如下:

 1  protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
 2                                                                AnnotationMetadata annotationMetadata) {
 3         if (!isEnabled(annotationMetadata)) {
 4             return EMPTY_ENTRY;
 5         }
 6         /** 封装加载的配置元数据为AnnotationAttributes对象*/
 7         AnnotationAttributes attributes = getAttributes(annotationMetadata);
 8         /** 获取所有候选的配置信息集合 (IOC容器需要加载的bean)
 9          *  实际是加载META-INF/spring.factories配置文件中的bean
10          * */
11         List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
12         /** 去除重复的*/
13         configurations = removeDuplicates(configurations);
14         /** 去除排除的*/
15         Set<String> exclusions = getExclusions(annotationMetadata, attributes);
16         checkExcludedClasses(configurations, exclusions);
17         configurations.removeAll(exclusions);
18         /** 去除过滤的*/
19         configurations = filter(configurations, autoConfigurationMetadata);
20         fireAutoConfigurationImportEvents(configurations, exclusions);
21         /** 封装成AutoConfigurationEntry对象*/
22         return new AutoConfigurationEntry(configurations, exclusions);
23     }

目的是加载META-INF/spring.factories配置文件,得到所有需要加载的类,然后通过去除、排除的方式筛选需要加载的类

而spring.factories中有一个key为EnableAutoConfiguration,value是一个集合,包含了几乎常用的所有各种中间件的自动配置类,这些类都在spring-boot-autoconfigure包中,包括常用的有:

MongoDataAutoConfiguration、RedisAutoConfiguration、DataSourceAutoConfiguration、RabbitAutoConfiguration、ElasticsearchAutoConfiguration等等,每个类都封装了各自的自动配置类

tips:那么问题来了,SpringBoot默认提供了各种各有的配置类,如果我们的程序中并没有用到相关的组件,会不会讲这些无用的配置类也加载到容器中呢?

答案很显然是否定的,spring-autoconfigure-metadata.properties中定义了各种组件的ConditionalOnClass的key,值为对应的Class全路径,比如:

org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.ConditionalOnClass=org.springframework.data.redis.core.RedisOperations

org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.ConditionalOnClass=com.rabbitmq.client.Channel,org.springframework.amqp.rabbit.core.RabbitTemplate

org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.core.JdbcTemplate

ConditionalOnClass代表如果要加载对应的属性,必要要加载对应的类,比如程序中用到了RabbitMQ,那么肯定依赖了RabbitMQ对应的jar包,所以肯定会加载RabbitTemplate类,那么此时才会加载RabbitAutoConfiguration类,同理适用RedisAutoConfiguration类的前提是需要先加载RedisOperations类,只有先加载了对应的类才会加载对应的AutoConfiiguration类

总结

1、SpringBoot启动时会通过@EnableAutoConfiguration注解导入AutoConfigurationImportSelector对象,会从扫描包路径下META-INF/spring.factories中的配置,该配置文件中包含了各种组件的自动配置类对应的AutoConfiguration类,然后通过加载各种组件的AutoConfiguration类从而可以加载对应的自动配置类,并且通过ConditionalOnClass来过滤需要的自动配置类,去除掉程序中没有使用的自动配置类

2、@SpringBootApplication是个组合注解,分别是@SpringBootConfiguration、@ComponentScan和@EnableAutoConfiguration三个组件,其中@SpringBootConfiguration和@ComponentScan两个组件保证可以扫描用户自定义的bean,@EnableAutoConfiguration用于扫描加载默认的第三方的bean

3、SpringBoot程序启动的流程实际就是IOC容器创建和启动的过程,然后通过扫描用户自定义的bean和第三方的bean完成bean的加载。

四、SpringBoot内置Tomcat源码解析

SpringBoot既然内置了web容器,那么就不会只内置Tomcat一个容器,还有Jetty、Undertow,所以从设计者的角度就会在这三个具体容器之上进行抽象,定义了一个WebServer接口,WebServer接口定义如下:

public interface WebServer {

    /** 启动web容器*/
    void start() throws WebServerException;

    /** 关闭web容器*/
    void stop() throws WebServerException;

    /** 获取web容器监听的端口 */
    int getPort();

}

而Tomcat、Jetty、Undertow等容器就会有各自的实现类实现了WebServer接口,分别是TomcatWebServer、JettyWebServer、UndertowWebServer,而SpringBoot采用了工厂模式来决定使用哪一个容器模式,所以定义了工厂类ServertWebServerFactory。在spring.factories配置文件中就定义了ServletWebServerFactoryAutoConfiguration,该类的定义如下:

 1 @Configuration(proxyBeanMethods = false)
 2 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
 3 @ConditionalOnClass(ServletRequest.class)
 4 @ConditionalOnWebApplication(type = Type.SERVLET)
 5 @EnableConfigurationProperties(ServerProperties.class)
 6 @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
 7         ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
 8         ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
 9         ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
10 public class ServletWebServerFactoryAutoConfiguration 

可以看出该类通过@Import注解分别导入了EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow的bean,以EmbeddedTomcat为例,定义如下:

 1 @Configuration(proxyBeanMethods = false)
 2     @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
 3     @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
 4     public static class EmbeddedTomcat {
 5 
 6         @Bean
 7         public TomcatServletWebServerFactory tomcatServletWebServerFactory(
 8                 ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
 9                 ObjectProvider<TomcatContextCustomizer> contextCustomizers,
10                 ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
11             TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
12             factory.getTomcatConnectorCustomizers()
13                     .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
14             factory.getTomcatContextCustomizers()
15                     .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
16             factory.getTomcatProtocolHandlerCustomizers()
17                     .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
18             return factory;
19         }
20 
21     }

该类内部通过@Bean注解定义了一个TomcatServletWebServerFactory对象,该对象就是TomcatWebServer的工厂类,TomcatServletWebServerFactory有一个getWebServer方法,定义如下:

 1 @Override
 2     public WebServer getWebServer(ServletContextInitializer... initializers) {
 3         if (this.disableMBeanRegistry) {
 4             Registry.disableRegistry();
 5         }
 6         Tomcat tomcat = new Tomcat();
 7         File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
 8         tomcat.setBaseDir(baseDir.getAbsolutePath());
 9         Connector connector = new Connector(this.protocol);
10         connector.setThrowOnFailure(true);
11         tomcat.getService().addConnector(connector);
12         customizeConnector(connector);
13         tomcat.setConnector(connector);
14         tomcat.getHost().setAutoDeploy(false);
15         configureEngine(tomcat.getEngine());
16         for (Connector additionalConnector : this.additionalTomcatConnectors) {
17             tomcat.getService().addConnector(additionalConnector);
18         }
19         prepareContext(tomcat.getHost(), initializers);
20         return getTomcatWebServer(tomcat);
21     }
1 protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
2         return new TomcatWebServer(tomcat, getPort() >= 0);
3     }

源码不复杂,创建了一个Tomcat对象,然后初始化参数,再传给TomcatWebServer的构造函数创建TomcatWebServer对象,TomcatWebServer构造函数中会调用Tomcat的start方法启动Tomcat服务器

tips:那么问题来了,getWebServer方法是何时调用的呢?

在Spring容器创建之后会执行刷新操作,ApplicationContext的子类ServletWebServerApplicationContext实现了ApplicationContext的onRefresh方法,源码如下:

1 protected void onRefresh() {
2         super.onRefresh();
3         try {
4             createWebServer();
5         }
6         catch (Throwable ex) {
7             throw new ApplicationContextException("Unable to start web server", ex);
8         }
9     }

首先是调用父类的onRefresh方法,然后执行createWebServer方法创建WebServer对象,逻辑就是从IOC容器中找到ServletWebServerFactory对象,执行getWebServer方法获取WebServer对象。

apache的Tomcat实现原理解析可参考:

五、如何实现自定义starter

1、新建项目spring-boot-mytest-starter(官方提供的starter命名为spring-boot-starter-xxx,自定义的通常命名为spring-boot-xxx-starter,用于和官方提供的区分开)

原文地址:https://www.cnblogs.com/jackion5/p/14736862.html