1. spring boot的启动流程分析

环境:

  • spring boot 2.0.8.release
  • win10
  • idea 2018.03.05
    本文查看相关的文章与源代码总结的。

1. spring boot 启动流程

查看 SpringBoot启动流程解析 这篇文章,那里的启动结构图还是不错的。
启动流程主要分为三部分,

  • 第一部分, 进行SpringApplication的初始化模块,配置一些环境变量、资源、构造器、监听器、主方法的类;
  • 第二部分,实现应用的启动,包括启动流程的监听模块、加载配置环境模块、核心的创建上下文环境模块;
  • 第三部分,自动化配置模块,该模块作为spring boot的自动配置核心。

查看一下的图,这个图比较精彩。

1

2

3

4

如果不够全面,就查看[SpringBoot启动结构图] (https://www.processon.com/view/link/59812124e4b0de2518b32b6e)

2. 启动

每个SpringBoot程序都有一个主入口,也就是main方法,main里面调用SpringApplication.run()启动整个spring-boot程序,该方法所在类需要使用@SpringBootApplication注解,以及@ImportResource注解(if need),@SpringBootApplication包括三个注解,功能如下:

  • @EnableAutoConfiguration:SpringBoot根据应用所声明的依赖来对Spring框架进行自动配置

  • @SpringBootConfiguration(内部为@Configuration):被标注的类等于在spring的XML配置文件中(applicationContext.xml),装配所有bean事务,提供了一个spring的上下文环境

  • @ComponentScan:组件扫描,可自动发现和装配Bean,默认扫描SpringApplication的run方法里的Demo.class所在的包路径下文件,所以最好将该启动类放到根包路径下

1

3. 初始化模块

SpringBoot启动类进入run()方法,首先会创建SpringApplication实例,然后在构造函数内,初始化initialize.

public static ConfigurableApplicationContext run(Class primarySource,
            String... args) {
        return run(new Class[] { primarySource }, args);
    }
------------------------------------------
public static ConfigurableApplicationContext run(Class[] primarySources,
            String[] args) {
        return new SpringApplication(primarySources).run(args);
    }
------------------------------------------
public SpringApplication(Class... primarySources) {
        this(null, primarySources);
    }
-------------------------------------------

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
                // 配置类加载器
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
                // 配置sources
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
                // 判断应用的类型
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
                // 创建初始化构造器
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));、
                // 配置监听器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
                // 配置应用主类
        this.mainApplicationClass = deduceMainApplicationClass();
    }

其中WebApplicationType.deduceFromClasspath();主要配置是否为Web环境, REACTIVE环境,None(单jar)。

static WebApplicationType deduceFromClasspath() {
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : SERVLET_INDICATOR_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

注意: classUtils.isPresent(className, null)判断所提供的类名的类是否存在,且可以被加载
java.lang.Class.forName()的作用是什么?
返回与给定字符串名的类或接口的Class对象,使用给定的类加载器。

private static final String WEBFLUX_INDICATOR_CLASS = "org."
            + "springframework.web.reactive.DispatcherHandler";
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
            + "web.servlet.DispatcherServlet";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };

4. 应用的启动

应用的启动,会启动监听器、配置环境模块、配置Banner、配置应用上下文模块

public ConfigurableApplicationContext run(String... args) {
                // 开启计时器开始计时
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection exceptionReporters = new ArrayList<>();
                // headless模式配置
        configureHeadlessProperty();
                // 配置监听器
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
                        // 配置环境变量
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);
                        // 配置Banner
            Banner printedBanner = printBanner(environment);
                        // 创建应用上下文
            context = createApplicationContext();
                        // 获取springboot 异常类
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
                        // 更新应用上下文,prepareContext方法将listeners、environment、applicationArguments、banner等重要组件与上下文对象关联
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
                        //  (初始化方法如下)将是实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载,bean的实例化等核心工作。
            refreshContext(context);
// Springboot做了一些基本的收尾工作,返回了应用环境上下文
            afterRefresh(context, applicationArguments);
// 停止计时
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
//  这里会检测是否Runner,在项目启动后运行的,一般是CommandRunner、ApplicationRunner
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

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

该方法中实现了如下几个关键步骤:

    1. 创建了应用的监听器SpringApplicationRunListeners并开始监听
    1. 加载SpringBoot配置环境(ConfigurableEnvironment),如果是通过web容器发布,会加载StandardEnvironment,其最终也是继承了ConfigurableEnvironment,类图如下
image.png

可以看出,*Environment最终都实现了PropertyResolver接口,我们平时通过environment对象获取配置文件中指定Key对应的value方法时,就是调用了propertyResolver接口的getProperty方法

    1. 配置环境(Environment)加入到监听器对象中(SpringApplicationRunListeners)
    1. 创建run方法的返回对象:ConfigurableApplicationContext(应用配置上下文),我们可以看一下创建方法:
protected ConfigurableApplicationContext createApplicationContext() {
        Class contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext, "
                                + "please specify an ApplicationContextClass",
                        ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

方法会先获取显式设置的应用上下文(applicationContextClass),如果不存在,再加载默认的环境配置(通过是否是web environment判断),默认选择AnnotationConfigApplicationContext注解上下文(通过扫描所有注解类来加载bean),最后通过BeanUtils实例化上下文对象,并返回,ConfigurableApplicationContext类图如下:


image.png

主要看其继承的两个方向:

LifeCycle:生命周期类,定义了start启动、stop结束、isRunning是否运行中等生命周期空值方法

ApplicationContext:应用上下文类,其主要继承了beanFactory(bean的工厂类)

  • 5.回到run方法内,prepareContext方法将listeners、environment、applicationArguments、banner等重要组件与上下文对象关联

  • 6.接下来的refreshContext(context)方法(初始化方法如下)将是实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载,bean的实例化等核心工作。


private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
              // 注册关闭的钩子函数,用于收尾工作
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
    }
-----------------------
protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext) applicationContext).refresh();
    }
-----------------------
public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
// 刷新,检测环境变量
            this.prepareRefresh();
// 获取ConfigurableListableBeanFactory工厂
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
// 
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                } 
                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            } 
        }
    }
  • refresh方法

配置结束后,Springboot做了一些基本的收尾工作,返回了应用环境上下文。
回顾整体流程,Springboot的启动,主要创建了配置环境(environment)、事件监听(listeners)、应用上下文(applicationContext),并基于以上条件,在容器中开始实例化我们需要的Bean,至此,通过SpringBoot启动的程序已经构造完成,接下来我们来探讨自动化配置是如何实现。

引用:

  1. https://www.cnblogs.com/trgl/p/7353782.html
  2. https://blog.csdn.net/doegoo/article/details/52471310

PS: 若你觉得可以、还行、过得去、甚至不太差的话,可以“关注”一下,就此谢过!

你可能感兴趣的:(1. spring boot的启动流程分析)