zoukankan      html  css  js  c++  java
  • Sprigboot启动原理解析

    核心注解@SpringBootApplication

    每个SpringBoot应用,都有一个入口类,标注@SpringBootApplication注解。

    @SpringBootApplication
    public class DemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    }
    

    点开@SpringBootApplication的源码,可以看到这个注解其实包含了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解。

    下面对这三个注解简单解释解释。

    • @SpringBootConfiguration

      对于这个注解不做解释,将这个注解点进去,发现还有@Configuration注解,对于@Configuration注解,用过Spring或SpringBoot的基本上都不陌生,标注了@Configuration的类相当于Spring中的配置XML,不过SpringBoot社区推荐使用JavaConfig,所以@Configuration就构建出了一个基础JavaConfig的Ioc容器。

    • @EnableAutoConfiguration

      Spring中有很多Enable*的注解,表示开启某项东西,如@EnableSchuduling。所以看这个注解的名字就知道是开启自动配置。这是一个复合注解,其中最主要的还是@Import,借助于EnableAutoConfigurationImportSelector,将所有符合自动配置条件的Bean加载到Ioc容器里。

      SpringBoot加载自动配置的方式有两种(目前我知道的):

      • 在classpath下新建META-INF/spring.factories文件,将标注了@Configuration的类的全路径配置到此文件中,如:

        org.springframework.boot.autoconfigure.EnableAutoConfiguration=
         com.quartz.config.QuartzBeanConfiguration,
         com.quartz.config.QuartzAutoConfiguration
        

        在启动时,通过SpringFactoriesLoader工具类,将所有META-INF目录下的spring.factories文件中的配置类加载到Ioc容器里。

      • 使用@Import,将配置类加载到Ioc容器里。

        @Target(ElementType.TYPE)
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @Import({QuartzAutoConfiguration.class})
        public @interface EnableQuartz {
        }
        

        使用@Import导入的类必须满足以下任意一个要求:

        1. 导入的类使用@Configuration进行标注
        2. 导入的类中至少有一个使用@Bean标准的方法
        3. 导入的类实现了ImportSelector接口
        4. 导入的类实现了ImportBeanDefinitionRegistrar接口
    • @ComponentScan

      看到这个注解,可以回想一下以前使用SpringMVC时,xml配置文件里的一个标签

      <context:component-scan base-package="" />
      

      不过这个注解一般不需要手动指定扫描的包路径,它默认会从标注了@ComponentScan的类所在包往下查找,将标注了如@Component,@Service等Bean加载到Ioc容器里。

    自动配置核心类SpringFactoriesLoader

    上面在说@EnableAutoConfiguration的时候有说META-INF下的spring.factories文件,那么这个文件是怎么被spring加载到的呢,其实就是SpringFactoriesLoader类。

    SpringFactoriesLoader是一个供Spring内部使用的通用工厂装载器,SpringFactoriesLoader里有两个方法,

    // 加载工厂类并实例化
    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {}
    // 加载工厂类的类名
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {}
    

    在这个SpringBoot应用启动过程中,SpringFactoriesLoader做了以下几件事:

    1. 加载所有META-INF/spring.factories中的Initializer
    2. 加载所有META-INF/spring.factories中的Listener
    3. 加载EnvironmentPostProcessor(允许在Spring应用构建之前定制环境配置)
    4. 接下来加载Properties和YAML的PropertySourceLoader(针对SpringBoot的两种配置文件的加载器)
    5. 各种异常情况的FailureAnalyzer(异常解释器)
    6. 加载SpringBoot内部实现的各种AutoConfiguration
    7. 模板引擎TemplateAvailabilityProvider(如Freemarker、Thymeleaf、Jsp、Velocity等)

    总得来说,SpringFactoriesLoader和@EnableAutoConfiguration配合起来,整体功能就是查找spring.factories文件,加载自动配置类。

    整体启动流程

    在我们执行入口类的main方法之后,运行SpringApplication.run,后面new了一个SpringApplication对象,然后执行它的run方法。

    public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
        return new SpringApplication(sources).run(args);
    }
    

    初始化SpringApplication类

    创建一个SpringApplication对象时,会调用它自己的initialize方法

    private void initialize(Object[] sources) {
        if (sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
        }
        // 根据标志类javax.servlet.Servlet,org.springframework.web.context.ConfigurableWebApplicationContext是否存在,判断是否是web环境
        this.webEnvironment = deduceWebEnvironment();
        // 通过SpringFactoriesLoader,获取到所有META-INF/spring.factories中的ApplicationContextInitializer,并实例化
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        // 通过SpringFactoriesLoader,获取到所有META-INF/spring.factories中的ApplicationListener,并实例化
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 获取执行当前main方法的类,也就是启动类
        this.mainApplicationClass = deduceMainApplicationClass();
    }
    

    注 : 各方法内部执行逻辑就不做说明了,比较简单,需要的读者可自行点进源码查看

    1. 根据classpath里是否存在某个特征类(javax.servlet.Servlet,org.springframework.web.context.ConfigurableWebApplicationContext)来判断是否需要创建一个为Web应用使用的ApplicationContext。

    2. 使用SpringFactoriesLoader在应用的classpath下的所有META-INF/spring.factories中查找并加载所有可用的ApplicationContextInitializer。

    3. 使用SpringFactoriesLoader在应用的classpath下的所有META-INF/spring.factories中查找并加载所有可用的ApplicationListener。

    4. 设置main方法的定义类

    执行核心run方法

    初始化initialize方法执行完之后,会调用run方法,开始启动SpringBoot。

    public ConfigurableApplicationContext run(String... args) {
        // 启动任务执行的时间监听器
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        // 设置系统java.awt.headless属性,确定是否开启headless模式(默认开启headless模式)
        configureHeadlessProperty();
        // 通过SpringFactoriesLoader,获取到所有META-INF/spring.factories下的SpringApplicationRunListeners并实例化
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 开始广播启动
        listeners.started();
        try {
            // 创建SpringBoot默认启动参数对象
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 根据启动参数创建并配置Environment(所有有效的配置,如Profile),并遍历所有的listeners,广播启动环境已准备
            ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
            // 打印启动图案
            Banner printedBanner = printBanner(environment);
            // 根据标志类(上面有提到过),创建对应类型的ApplicationContext
            context = createApplicationContext();
            // 创建异常解析器(当启动失败时,由此解析器处理失败结果)
            analyzers = new FailureAnalyzers(context);
            // 准备Spring上下文环境
            // 在这个方法中,主要完成了以下几件事:
            //  1、设置SpringBoot的环境配置(Environment)
            //  2、注册Spring Bean名称的序列化器BeanNameGenerator,并设置资源加载器ResourceLoader
            //  3、加载ApplicationContextInitializer初始化器,并进行初始化
            //  4、统一将上面的Environment、BeanNameGenerator、ResourceLoader使用默认的Bean注册器进行注册
            prepareContext(context, environment, listeners, applicationArguments,printedBanner);
            // 注册一个关闭Spring容器的钩子
            refreshContext(context);
            // 获取当前所有ApplicationRunner和CommandLineRunner接口的实现类,执行其run方法
            // ApplicationRunner和CommandLineRunner功能基本一样,在Spring容器启动完成时执行,唯一不同的是ApplicationRunner的run方法入参是ApplicationArguments,而CommandLineRunner是String数组
            afterRefresh(context, applicationArguments);
            // 通知所有listener,Spring容器启动完成
            listeners.finished(context, null);
            // 停止时间监听器
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        } catch (Throwable ex) {
            // 启动有异常时,调用异常解析器解析异常信息,根据异常级别,判断是否退出Spring容器
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
    }
    
    1. 首先遍历执行所有通过SpringFactoriesLoader,在当前classpath下的META-INF/spring.factories中查找所有可用的SpringApplicationRunListeners并实例化。调用它们的started()方法,通知这些监听器SpringBoot应用启动。

    2. 创建并配置当前SpringBoot应用将要使用的Environment,包括当前有效的PropertySource以及Profile。

    3. 遍历调用所有的SpringApplicationRunListeners的environmentPrepared()的方法,通知这些监听器SpringBoot应用的Environment已经完成初始化。

    4. 打印SpringBoot应用的banner,SpringApplication的showBanner属性为true时,如果classpath下存在banner.txt文件,则打印其内容,否则打印默认banner。

    5. 根据启动时设置的applicationContextClass和在initialize方法设置的webEnvironment,创建对应的applicationContext。

    6. 创建异常解析器,用在启动中发生异常的时候进行异常处理(包括记录日志、释放资源等)。

    7. 设置SpringBoot的Environment,注册Spring Bean名称的序列化器BeanNameGenerator,并设置资源加载器ResourceLoader,通过SpringFactoriesLoader加载ApplicationContextInitializer初始化器,调用initialize方法,对创建的ApplicationContext进一步初始化。

    8. 调用所有的SpringApplicationRunListeners的contextPrepared方法,通知这些Listener当前ApplicationContext已经创建完毕。

    9. 最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。

    10. 调用所有的SpringApplicationRunListener的contextLoaded方法,加载准备完毕的ApplicationContext。

    11. 调用refreshContext,注册一个关闭Spring容器的钩子ShutdownHook,当程序在停止的时候释放资源(包括:销毁Bean,关闭SpringBean的创建工厂等)

      注: 钩子可以在以下几种场景中被调用:

      1)程序正常退出

      2)使用System.exit()

      3)终端使用Ctrl+C触发的中断

      4)系统关闭

      5)使用Kill pid命令杀死进程

    12. 获取当前所有ApplicationRunner和CommandLineRunner接口的实现类,执行其run方法

    13. 遍历所有的SpringApplicationRunListener的finished()方法,完成SpringBoot的启动。




    参考链接:https://www.jianshu.com/p/482c2c1ce00e   
  • 相关阅读:
    DGA域名可以是色情网站域名
    使用cloudflare加速你的网站隐藏你的网站IP
    167. Two Sum II
    leetcode 563. Binary Tree Tilt
    python 多线程
    leetcode 404. Sum of Left Leaves
    leetcode 100. Same Tree
    leetcode 383. Ransom Note
    leetcode 122. Best Time to Buy and Sell Stock II
    天津Uber优步司机奖励政策(12月28日到12月29日)
  • 原文地址:https://www.cnblogs.com/superming/p/13369897.html
Copyright © 2011-2022 走看看