美文网首页程序员我爱编程Java 杂谈
SpringBoot原理浅析2-SpringApplicatio

SpringBoot原理浅析2-SpringApplicatio

作者: Coding小聪 | 来源:发表于2018-06-10 10:08 被阅读128次

    前言

    在上一节中我们看到SpringBoot方便快捷的POM依赖,这一节我们主要来看下SpringApplication.run的执行流程。

    public static void main(String[] args) throws Exception {
        SpringApplication.run(StartSpringBootMain.class, args);
    }
    

    虽然只有一行代码,不过这一行代码却做了大量的工作。这行代码可以分成两个部分:1、SpringApplication对象的创建;2、Spring容器的创建(即run方法的执行)

    说明:本节源码的分析基于spring-boot-1.5.4.RELEASE

    SpringApplication对象的创建

    SpringApplication.run()的调用执行过程中会创建出SpringApplication对象,然后委托给该对象的run方法。

    public SpringApplication(Object... sources) {
        /*
         *sources="cn.zgc.springboot.basic.StartSpringBootMain",
         *同时可以发现SpringApplication.run方法可以接收多个启动配置类。
         */
        initialize(sources);
    }
    
    private void initialize(Object[] sources) {
        if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
        }
        // 判断当前应用是否为web应用
        this.webEnvironment = deduceWebEnvironment();
        // 加载所有可用的ApplicationContextInitializer
        setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
        // 加载所有可用的ApplicationListener
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 判断main方法的启动类
        this.mainApplicationClass = deduceMainApplicationClass();
    }
    

    SpringApplication类在实例化的过程中做了以下事情:
    1、判断当前应用类型是web还是标准的Standalone,这是因为它们对应的ApplicaitonContext类不同。通过deduceWebEnvironment方法完成,在该方法中会检测classpath中是否同时存在类"javax.servlet.Servlet"和 "org.springframework.web.context.ConfigurableWebApplicationContext"
    2、加载Classpath中所有的ApplicationContextInitializer类;
    3、加载classpath中所有的ApplicationListener类;
    4、推断并设置main方法的定义类。

    deduceWebEnvironment()的源码如下

    private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
     "org.springframework.web.context.ConfigurableWebApplicationContext" };
    
    private boolean deduceWebEnvironment() {
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return false;
            }
        }
        return true;
    }
    

    ApplicationContextInitializer的作用

    ApplicationContextInitializer接口是在spring容器刷新之前执行的一个回调函数,在Spring容器创建之前会先调用ApplicationContextInitializer类中的initialize方法

    默认加载的ApplicationContextInitializer
    那默认加载的这些ApplicationContextInitializer类名是从哪里获取的呢?通过SpringFactoriesLoader.loadFactoryNames(type, classLoader)读取spring-boot-1.5.4.RELEASE.jar/META-INF/spring.factories和spring-boot-autoconfigure.1.5.4.RELEASE.jar/META-INF/spring.factories文件中的org.springframework.context.ApplicationContextInitializer定义的值。

    ApplicationListener的作用

    ApplicationListener用来在Spring容器初始化完成之后,进行一些常用的操作,例如初始化缓存、特定任务的注册等。在SpringApplication类的初始化的过程中,会默认加载10个ApplicationListener类。这些类和ApplicationContextInitializer一样定义在spring.factories文件中。

    创建容器

    实例化完SpringApplication之后,接着会调用run方法。run方法执行完之后,Spring容器也创建好了,先来看看run的源码。

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
        // 通过SpringFactoriesLoader查找并加载所有的SpringApplicationRunListener
        // SpringApplicationRunListener 可以监听springboot应用启动过程中的一些生命周期事件,并做一些处理
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 调用SpringFactoriesLoader的starting方法,广播SpringBoot要开始执行了。
        listeners.starting();
        try {
    
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            //创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),
            //并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            // 打印Banner,Banner图案支持自定义
            Banner printedBanner = printBanner(environment);
            //根据webEnvironment的值来决定创建何种类型的ApplicationContext对象
            //如果是web环境,则创建org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext
            //否则创建org.springframework.context.annotation.AnnotationConfigApplicationContext
            context = createApplicationContext();
            // 异常分析器
            analyzers = new FailureAnalyzers(context);
            //为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,
            //并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,
            //之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成,
            //这里就包括通过@EnableAutoConfiguration导入的各种自动配置类。
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            //初始化所有通过@EnableAutoConfiguration导入的各种自动配置类,调用ApplicationContext的refresh()方法
            refreshContext(context);
            //遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
            //该过程可以理解为是SpringBoot完成ApplicationContext初始化前的最后一步工作,
            //我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。
            afterRefresh(context, applicationArguments);
            // 调用所有的SpringApplicationRunListener的finished()方法,广播SpringBoot已经完成了ApplicationContext初始化的全部过程。
            listeners.finished(context, null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        }catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
    }
    

    SpringApplication.run方法的执行就是创建Spring容器的过程。run方法执行过程中将SpringBoot的生命周期和监听器类SpringApplicationRunListener类绑定在一起,运行过程中通过SpringApplicationRunListener广播对象的事件。

    public interface SpringApplicationRunListener {
    
         //刚执行run方法时
        void started();
         //环境建立好时候
        void environmentPrepared(ConfigurableEnvironment environment);
         //上下文建立好的时候
        void contextPrepared(ConfigurableApplicationContext context);
        //上下文载入配置时候
        void contextLoaded(ConfigurableApplicationContext context);
        //上下文刷新完成后,run方法执行完之前
        void finished(ConfigurableApplicationContext context, Throwable exception);
    
    }
    

    SpringApplicationRunListener在SpringBoot中只有一个实现类org.springframework.boot.context.event.EventPublishingRunListener。它用于在SpringBoot启动的时发布不同的事件类型(ApplicationEvent),如果有哪些ApplicationListener对这些事件敢兴趣,则可以接收该事件并进行处理。

    自动配置类的加载注册

    在上一节中,我们看到SpringBoot会为我们加载很多自动配置类。而自动配置类是在SpringApplication.run的执行过程中加载并注册到Spring容器中的,更具体点是在SpringApplication.refreshContext()方法中执行的。具体方法调用栈如下:

    SpringApplication的refreshContext执行过程
    可以看到refreshContext的执行过程中会涉及到Bean后置处理器ConfigurationClassPostProcessor(后置处理器是Spring中很重要的一种机制,后面单独写篇文章介绍一下)。而ConfigurationClassPostProcessor又会去调用ConfigurationClassParser对自动配置类进行解析。ConfigurationClassParser是个局部变量,它定义在ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法内。
    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    
        // Parse each @Configuration class
        ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, this.problemReporter, this.environment,
                this.resourceLoader, this.componentScanBeanNameGenerator, registry);
        
        ...
    }
    

    由注释可以知道ConfigurationClassParser类的作用是解析带有@Configuration注解的类。

    扩展阅读

    1. ApplicationContextInitializer接口

    2. Spring 工具类 ConfigurationClassParser 是如何工作的 ?

    3.【深入SpringBoot 第三章】SpringApplicationRunListener及其周期

    相关文章

      网友评论

        本文标题:SpringBoot原理浅析2-SpringApplicatio

        本文链接:https://www.haomeiwen.com/subject/nauusftx.html