技术文章spring boot

SpringBoot启动原理解析

2021-02-06  本文已影响0人  singleZhang2010

概述

前面已经介绍过Spring的IOC和AOP原理,那我们再来看我们平时很熟悉的SpringBoot的启动原理是怎么样的。

SpringBoot启动流程

spring boot 启动流程

图片来自: https://www.processon.com/view/link/59812124e4b0de2518b32b6e

创建一个springboot 项目

这里就拿之前写过的springboot demo项目来为例,看一下其启动类DemoApplication

@SpringBootApplication
public class DemoApplication {

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

简单到一眼就可以让你发现它的关键信息:@SpringBootApplication、SpringApplication.run(DemoApplication.class, args) 在这两个地方。

SpringApplication.run(DemoApplication.class, args)

我们可以跟踪这个run方法进去看看里边的玄机,最终我们会看到一个核心的run方法:

    public ConfigurableApplicationContext run(String... args) {

        //应用启动计时器器StopWatch 对象
        StopWatch stopWatch = new StopWatch();

        //计时开始(后面有个对应的计时结束)
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

            //配置环境信息
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);

            // banner配置和打印
            Banner printedBanner = this.printBanner(environment);
            
            //注释1. 创建ApplicationContext上下文容器
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

            //注释2. 容器初始化(很重要的ApplicationContext#refresh就在这里!!!)
            this.refreshContext(context);

            this.afterRefresh(context, applicationArguments);

            //应用启动计时结束, 到这应用启动就结束了
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

注释1:创建ApplicationContext上下文容器
注释2:容器初始化(很重要的ApplicationContext#refresh就在这里!!!)
对于容器的初始化,我们在Spring IOC系列文章中已经讲过了,对于beanFactory的创建,bean定义的注册,beanFactoryPostProcessor的调用,bean的创建和初始化,beanPostProcessor的调用等等,可以回顾一下《Spring源码阅读----Spring IoC之BeanFactory、ApplicationContext》,后面可以参考。
先到这里,我们先来解析注解,后面再回过来展开这里的细节。

@SpringBootApplication注解

@SpringBootApplication注解

可以看到它是一个复合注解,前面四个就不解释了,另外包含了@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <!--bean 定义 -->
    <bean id="testAaService" class="com.zhlab.ssm.demo.web.service.TestAaService">
        <property name="testBbService" ref="testBbService"></property>
    </bean>
    <bean id="testBbService" class="com.zhlab.ssm.demo.web.service.TestBbService">
        <property name="testAaService" ref="testAaService"></property>
    </bean>
</beans>

那我们在springboot里就可以写一个:

@Configuration
public class XxConfig {

    //bean 定义
    @Bean
    public TestBbService testBbService(){
        TestBbService bb =  new TestBbService();
        bb.setTestAaService(testAaService());
        return bb;
    }
    
    @Bean
    public TestAaService testAaService(){
        return new TestAaService();
    }
}

这两种效果是一样的前者为xml配置、后者为JavaConfig的配置形式,TestAaService、TestBbService 两个类在介绍Spring 循环依赖一文中有。

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
        }
    }

它实现了ImportBeanDefinitionRegistrar接口,并重写了registerBeanDefinitions方法,其调用AutoConfigurationPackages.register,将其同级包名作为bean定义注册到容器中,这样可以给@ConponentScan作为默认的basePackages来扫描。

其被调用过程如下:

SpringApplication.run()
    => refreshContext
        // 这里web应用的时候创建的是AnnotationConfigServletWebServerApplicationContext
        => AnnotationConfigServletWebServerApplicationContext.refresh()
            //这个比较熟悉吧?调用各个注册了的BeanFactoryPostProcessors
            =>AbstractApplicationContext.invokeBeanFactoryPostProcessors()
              => PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors()
                //注释1.在这里调用到了ConfigurationClassPostProcessor这个后置处理器
                =>ConfigurationClassPostProcessor.processConfigBeanDefinitions()
                  =>ConfigurationClassBeanDefinitionReader.loadBeanDefinitions()
                    //遍历执行实现了ImportBeanDefinitionRegistrar接口的实例的registerBeanDefinitions方法
                    =>loadBeanDefinitionsFromRegistrars()
                      //注释2.调用到Registrar类的registerBeanDefinitions
                      =>AutoConfigurationPackages$Registrar.registerBeanDefinitions()
                        =>AutoConfigurationPackages.register()

注释1. 这里用到了ConfigurationClassPostProcessor后置处理器,这个类比较强大,用于启动过程中对配置类的发现和处理,注册bean定义。
注释2.调用到Registrar类的registerBeanDefinitions方法,其调用了AutoConfigurationPackages.register方法
register方法源码如下:

    //定义Bean的名称
    private static final String BEAN = AutoConfigurationPackages.class.getName();

    public static void register(BeanDefinitionRegistry registry, String... packageNames) {

        //该bean是否已经注册
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);

            //将要注册包名称添加进去
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        } else {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(2);

            //注册bean
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }

    }

这里的BeanDefinitionRegistry 对象registry 其实是DefaultListableBeanFactory实例对象,对于DefaultListableBeanFactory类应该比较熟悉了吧。
所以,SpringBoot是通过这种方式,自动地扫描并注册了启动类同级以及子包下的组件bean定义,一般会把启动类放在根目录下。

  1. @Import({AutoConfigurationImportSelector.class})
    接下来看这个注解,导入了AutoConfigurationImportSelector
    查看AutoConfigurationImportSelector类的类图,如下:
AutoConfigurationImportSelector

其实现了ImportSelector接口,重新了selectImports方法,其源码如下:

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            // 获取自动配置的信息
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

其被调用过程如下:

SpringApplication.run()
    => refreshContext
        => AnnotationConfigServletWebServerApplicationContext.refresh()
            =>AbstractApplicationContext.invokeBeanFactoryPostProcessors()
              => PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors()
                //注释1. ConfigurationClassPostProcessor后置处理器被调用了
                =>ConfigurationClassPostProcessor.processConfigBeanDefinitions()
                  //解析启动类
                  =>ConfigurationClassParser.parse()
                    =>parse()
                      //遍历查找configClass,并处理配置类
                      =>processConfigurationClass()
                        //真正做事的,处理configClass
                        =>doProcessConfigurationClass()
                          //处理这些improt的类,这里有个getImports()是获取import标签里的值
                          =>processImports()
                            //处理实现了DeferredImportSelector接口的类
                            //AutoConfigurationImportSelector实现了这接口,添加到deferredImportSelectors列表中
                            =>ConfigurationClassParser.DeferredImportSelectorHandler.handle()
                    //注释2.遍历deferredImportSelectors列表中的ImportSelector类,处理并注册搜索到的bean
                    //AutoConfigurationImportSelector类就是在这里被调用到并执行selectImports方法
                    =>ConfigurationClassParser.DeferredImportSelectorHandler.process()

注释1. ConfigurationClassPostProcessor后置处理器被调用了,ConfigurationClassPostProcessor比较重要,本文关注的是SpringBoot启动过程,这个类后面增加一篇详细解析一下
注释2. 我们需要关注的是AutoConfigurationImportSelector类被调用,这里就是它被调用并加载各类配置类的地方,它的selectImports方法被执行了。

知道了被调用的过程,我们继续跟踪getAutoConfigurationEntry方法,查看源码:

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
          
            //注释1.自动配置所需的配置信息在这里获取
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);

            //最后会包装成其内部类AutoConfigurationEntry对象返回
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

注释1.通过getCandidateConfigurations获取自动配置的各种配置类

继续跟踪getCandidateConfigurations方法:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

        //注释. 这里加载自动配置相关的类
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

注释. getSpringFactoriesLoaderFactoryClass获取的是EnableAutoConfiguration类,所以给SpringFactoriesLoader.loadFactoryNames方法传入的参数为EnableAutoConfiguration类,
SpringFactoriesLoader.loadFactoryNames,这里跟进去可以发现它是加载一个文件"META-INF/spring.factories",这个文件在哪呢,里边都有些什么内容?如下图所示:

spring.factories spring.factories

可以发现这里有很多自动配置的类,可以自行点开其中一些类,可以发现它们都是带@Configuration注解的配置类,根据传入的类名org.springframework.boot.autoconfigure.EnableAutoConfiguration,会根据这个匹配这个key以下的配置类列表,返回这个配置类列表。

加载完这些,最终会回到ConfigurationClassPostProcessor类中,被loadBeanDefinitions方法执行,注册成bean定义。
最后容器完成这些bean的实例化初始化,容器刷新完成,并启动。

小结

观察整个启动过程,最重要的就是检索加载各个组件bean到Spring容器中的过程,
@Component(@Configuration、@Controller、@Service等也都是特殊的@Component),

@SpringBootApplication由三个组成:@SpringBootConfiguration、@EnableAutoConfiguration
、@ComponentScan

核心的分析完毕,我们在关注一下Tomcat。

Tomcat

我们在使用SpringBoot的时候,可以在其里边直接启动内嵌的Tomcat,这是在什么地方实现的?

在之前分析refresh的时候,其中有个onRefresh方法,它是个模板方法,交给子类自己实现的。
AnnotationConfigServletWebServerApplicationContext 继承自ServletWebServerApplicationContext,在ServletWebServerApplicationContext有个onRefresh方法的实现

    protected void onRefresh() {
        super.onRefresh();

        try {

            //这里创建web服务
            this.createWebServer();

        } catch (Throwable var2) {
            throw new ApplicationContextException("Unable to start web server", var2);
        }
    }

    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = this.getServletContext();
        if (webServer == null && servletContext == null) {

            //这里获取ServletWebServerFactory ,这里会获取TomcatServletWebServerFactory
            //实现这个接口的还有UndertowServletWebServerFactory、JettyServletWebServerFactory
            ServletWebServerFactory factory = this.getWebServerFactory();

            //注释1.从TomcatServletWebServerFactory获取WebServer
            this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
        } else if (servletContext != null) {
            try {
                this.getSelfInitializer().onStartup(servletContext);
            } catch (ServletException var4) {
                throw new ApplicationContextException("Cannot initialize servlet context", var4);
            }
        }

        this.initPropertySources();
    }

注释1.从TomcatServletWebServerFactory获取WebServer
来TomcatServletWebServerFactory类中看一下getWebServer方法源码:

    public WebServer getWebServer(ServletContextInitializer... initializers) {

        //创建一个Tomcat对象,然后下面开始设置配置信息(以后可以阅读一下Tomcat的源码)
        Tomcat tomcat = new Tomcat();
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());

        //构建一个Connector对象,传入协议值:org.apache.coyote.http11.Http11NioProtocol
        //如果要修改其默认值,需要借助BeanFactoryPostProcessor接口的postProcessBeanFactory
        //这样可以通过获取TomcatServletWebServerFactory对象来修改其additionalTomcatConnectors值
        Connector connector = new Connector(this.protocol);

        tomcat.getService().addConnector(connector);
        this.customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        this.configureEngine(tomcat.getEngine());
        Iterator var5 = this.additionalTomcatConnectors.iterator();

        while(var5.hasNext()) {
            Connector additionalConnector = (Connector)var5.next();
            tomcat.getService().addConnector(additionalConnector);
        }

        this.prepareContext(tomcat.getHost(), initializers);

        //注释1. 获取TomcatWebServer实例
        return this.getTomcatWebServer(tomcat);
    }

注释1. 获取TomcatWebServer实例,传入参数为tomcat

继续跟踪getTomcatWebServer方法:

    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        //创建TomcatWebServer对象
        return new TomcatWebServer(tomcat, this.getPort() >= 0);
    }

继续到TomcatWebServer类中看看其TomcatWebServer构造方法:

    public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
        //设置各类属性值
        this.monitor = new Object();
        this.serviceConnectors = new HashMap();
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;

        //开始初始化
        this.initialize();
    }

    private void initialize() throws WebServerException {
        logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
        synchronized(this.monitor) {
            try {

                //InstanceId加到EngineName中
                this.addInstanceIdToEngineName();
                Context context = this.findContext();
                context.addLifecycleListener((event) -> {
                    if (context.equals(event.getSource()) && "start".equals(event.getType())) {
                        this.removeServiceConnectors();
                    }

                });

                //注释1. tomcat服务启动监听,对服务生命周期的监听,对应的还有个服务结束的监听器
                this.tomcat.start();
                this.rethrowDeferredStartupExceptions();

                try {
                    ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
                } catch (NamingException var5) {

                }

                //Tomcat线程都是守护线程。我们创建一个阻塞非守护线程来避免立即关闭
                this.startDaemonAwaitThread();
            } catch (Exception var6) {
                this.stopSilently();
                throw new WebServerException("Unable to start embedded Tomcat", var6);
            }

        }
    }

注释1.在这里开始对tomcat服务启动的监听,对应的还有个对服务结束的监听。tomcat的server属性因为其类型Server 继承了Lifecycle接口,会对服务的生命周期进行监听,start() 、stop()来自Lifecycle接口。

那在哪里要启动TomcatWebServer服务了呢?在refresh流程中,容器创建的最后一个步骤就是finishRefresh(不考虑resetCommonCaches方法哈),我们来看ServletWebServerApplicationContext的finishRefresh方法:

    protected void finishRefresh() {
        //执行父类AbstractApplicationContext的finishRefresh方法
        super.finishRefresh();
        
        //注释1. 启动了WebServer 
        WebServer webServer = this.startWebServer();
        if (webServer != null) {
            this.publishEvent(new ServletWebServerInitializedEvent(webServer, this));
        }

    }


    private WebServer startWebServer() {
        WebServer webServer = this.webServer;
        if (webServer != null) {
            //注释2. 这里就启动了web服务,调用TomcatWebServer的start方法
            webServer.start();
        }

        return webServer;
    }

注释1. 启动了WebServer
注释2. 这里就启动了web服务,调用TomcatWebServer的start方法

所以,到这里内嵌的tomcat服务就这样在容器初始化的最后阶段启动了。有start当然对应也会有stop停止的方法,具体的Tomcat相关的源码就不在这里展开了,以后阅读Tomcat的源码时再展开做笔记^^。

对于事件驱动的部分,可以参考之前Spring Ioc部分介绍的Spring 事件监听机制。

总结

SpringBoot启动原理的解析就讲到这里,最重要的还是其自动配置的部分,然后也了解了内嵌的Tomcat服务是如何被启动的。

上一篇下一篇

猜你喜欢

热点阅读