zoukankan      html  css  js  c++  java
  • 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,用于和官方提供的区分开)

  • 相关阅读:
    测序分析软件-phred的安装
    测序分析软件-trimmomatic的记录
    linux-ubuntu下fastQC的安装及使用
    linux 下统计文本行数
    linux .gz文件 解压缩命令
    启用谷歌浏览器Adobe Flash Player
    「三代组装」使用Pilon对基因组进行polish
    用BUSCO来评估基因组完整性
    linux tar.gz 文件解压缩
    查看jobs详情
  • 原文地址:https://www.cnblogs.com/jackion5/p/14736862.html
Copyright © 2011-2022 走看看