• SpringBoot执行原理



    一、执行原理:

    每个Spring Boot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法, 在该方法中通过执行SpringApplication.run()即可启动整个Spring Boot程序。

    Q:

    那么SpringApplication.run()方法到底是如何做到启动Spring Boot项目的呢?

    @SpringBootApplication  //能够扫描Spring组件并自动配置SpringBoot
    public class Springboot01DemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(Springboot01DemoApplication.class, args);
        }
    }
    

    上述是一个SpringBoot的启动类,进入SpringApplication.run()方法

    image-20201215233706067

    如图所示,进入了run方法后,紧接着,调用了重载方法,重载方法做了两件事:

    1. 实例化SpringApplication对象
    2. 调用run方法

    1. 实例化SpringApplication对象

    public SpringApplication(Class<?>... primarySources) {
    	this(null, primarySources);
    }
    
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    	//......设置了一些参数....这里省略,下面是重点
        //......设置了一些参数....这里省略,下面是重点
        //......设置了一些参数....这里省略,下面是重点
    
    	//项目启动类 SpringbootDemoApplication.class设置为属性存储起来
    	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    
    	//设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
    	this.webApplicationType = WebApplicationType.deduceFromClasspath();
    
    	// 设置初始化器(Initializer),最后会调用这些初始化器
    	//所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作
    	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    
    	// 设置监听器(Listener)
    	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    
    	// 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
    	this.mainApplicationClass = deduceMainApplicationClass();
    }
    

    SpringApplication的构造方法中,首先设置了一些参数,然后做了5件事

    1.1 项目启动类 SpringbootDemoApplication.class设置为属性存储起来

    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    

    给这个成员变量赋值,把传入的primarySources进行转换,然后赋值,这个primarySources就是我们Springboot启动类的Main方法中传入的:
    image-20201215234948784

    1.2 设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用

    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    

    判断当前的web应用类型是servlet应用还是reactive应用,那么如何判断的? 进入.deduceFromClasspath()方法:
    image-20201215235441994

    1. 首先判断类路径下Reactive相关的class是否存在,如果存在就说明当前应用是内嵌的 Reactive Web 应用。例如说,Spring Webflux 。
    2. 判断类路径下是否存在Servlet类型的类。如果不存在,则返回NONE,表示当前应用是非内嵌的 Web 应用
    3. 否则,表示当前应用是内嵌的 Servlet Web 应用。例如说,Spring MVC 。

    1.3 设置初始化器(Initializer),最后会调用这些初始化器

    所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作.

    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    

    这里传入了一个ApplicationContextInitializer.class

    进入getSpringFactoriesInstances()方法(下图如果看不清请右键另存为):
    image-20201216001358494

    这段代码主要做了如下几件事:

    1. SpringFactoriesLoader.loadFactoryNames(type, classLoader)
      这里的type就是刚才传入的,ApplicationContextInitializer.class

    2. loadFactoryNames 调用了 loadSpringFactories方法

    3. loadSpringFactories方法做了如下的事:

      Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
      LinkedMultiValueMap result = new LinkedMultiValueMap();
      

      判断classLoader是否为空,如果不为空加载META-INF下的spring.factories,如上图所示,根据传入的参数值(ApplicationContextInitializer.class)的类型,在spring.factories中进行查找,根据当前传入的类型找到两个类,这两个类就是初始化器:

      org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,
      org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
      

    得到这两个类后,把它们存入set去重,然后进行实例化,然后排序,最终返回,到此初始化器已经设置完成了。然后存入List<ApplicationContextInitializer<?>> initializers,等待之后使用

    image-20201216002412069

    1.4 设置监听器(Listener)

    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    

    和1.3同理,也是通过调用getSpringFactoriesInstances,只不过传递的参数发生了改变。变成了ApplicationListener.class ,所以它就是在spring.factories中根据ApplicationListener.class找,然后实例化,然后返回存入Listeners中。

    1.5 初始化 mainApplicationClass 属性

    用于推断并设置项目main()方法启动的主程序启动类

    this.mainApplicationClass = deduceMainApplicationClass();
    
    	private Class<?> deduceMainApplicationClass() {
    		try {
    		    // 获得当前 StackTraceElement 数组
    			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
    			// 判断哪个执行了 main 方法
    			for (StackTraceElement stackTraceElement : stackTrace) {
    				if ("main".equals(stackTraceElement.getMethodName())) {
    					return Class.forName(stackTraceElement.getClassName());
    				}
    			}
    		} catch (ClassNotFoundException ex) {
    			// Swallow and continue
    		}
    		return null;
    	}
    

    判断哪个类执行了main方法,然后返回。

    1.6 总结

    实例化SpringApplication对象做了哪些事?

    1. 项目启动类 SpringbootDemoApplication.class设置为属性存储起来
    2. 设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
    3. 设置初始化器(Initializer),最后会调用这些初始化器
    4. 设置监听器(Listener)
    5. 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类

    2. 调用run方法

    回忆一下,在SpringBoot启动类的Main方法中,执行了SpringApplication.run(Main方法所在的当前类.class, args);,这个方法主要做了两件事:

    • 实例化SpringApplication对象 (已上述)
    • 调用run方法

    进入run方法:
    image-20201216005004395

    run方法大体上做了9件比较重要的事。

    2.1 获取并启动监听器

    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    //args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    

    image-20201216005219434

    它其实还是通过getSpringFactoriesInstances()这个方法来获取,这个方法已经很熟悉了, 1.3,1.4都使用到了,不再赘述。

    那么本步骤就是通过getSpringFactoriesInstances()拿到了一个SpringApplicationRunListeners类型的监听器,然后调用.starting()启动。

    2.2 项目运行环境Environment的预配置

    创建并配置当前SpringBoot应用将要使用的Environment,并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法

    ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    
    configureIgnoreBeanInfo(environment);
    // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
    Banner printedBanner = printBanner(environment);
    

    进入prepareEnvironment()方法:
    image-20201216010027603

    1. 查询environment,有就返回,没有的话创建后返回。
    2. 配置环境
      1. PropertySources:加载执行的配置文件
      2. Profiles:多环境配置,针对不同的环境,加载不同的配置
    3. listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)。
    4. 将创建的环境绑定到SpringApplication对象上
    5. 是否是web环境,如果不是就转换为标准环境
    6. 配置PropertySources对它自己的递归依赖
    7. 返回

    此时已经拿到了ConfigurableEnvironment 环境对象,然后执行configureIgnoreBeanInfo(environment),使其生效。

    2.3 创建Spring容器

    context = createApplicationContext();
    // 获得异常报告器 SpringBootExceptionReporter 数组
    //这一步的逻辑和实例化初始化器和监听器的一样,
    // 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
    exceptionReporters = getSpringFactoriesInstances(
          SpringBootExceptionReporter.class,
          new Class[] { ConfigurableApplicationContext.class }, context);
    

    image-20201216011031124

    根据 webApplicationType 类型,获得 ApplicationContext 类型,这里创建容器的类型 还是根据webApplicationType进行判断的,该类型为SERVLET类型,所以会通过反射装载对应的字节码,也就是AnnotationConfigServletWebServerApplicationContext

    然后通过getSpringFactoriesInstances()获得异常报告器。

    2.4 Spring容器前置处理

    这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。

    prepareContext(context, environment, listeners, applicationArguments,
          printedBanner);
    

    image-20201216011633102

    这块会对整个上下文进行一个预处理,比如触发监听器的响应事件、加载资源、设置上下文环境等等。

    2.5 刷新容器

    refreshContext(context);
    

    image-20201216011805415

    • IOC容器初始化
    • 向JVM运行时注册一个关机钩子(函数),在JVM关机时关闭这个上下文,除非它当时已经关闭。(如果jvm变关闭了,当前上下文对象也可以被关闭了)

    //TODO refresh方法在springioc章节中会有详细说明(挖个坑- - )。

    2.6 Spring容器后置处理

    afterRefresh(context, applicationArguments);
    

    image-20201216012326686

    扩展接口,设计模式中的模板方法,默认为空实现。
    如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。

    2.7 发出结束执行的事件通知

    listeners.started(context);
    

    image-20201216012516833

    2.8 执行Runners运行器

    callRunners(context, applicationArguments);
    

    image-20201216012757179

    用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序。

    Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。

    Spring Boot提供了ApplicationRunnerCommandLineRunner两种服务接口

    2.9 发布应用上下文就绪事件

    listeners.running(context);
    

    表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext.

    这样整个Spring Boot项目就正式启动完成了。

    2.10 返回容器

    return context;
    

    完成~

    总结:

    1. 获取并启动监听器
    2. 项目运行环境Environment的预配置
    3. 创建Spring容器
    4. Spring容器前置处理
    5. 刷新容器
    6. Spring容器后置处理
    7. 发出结束执行的事件通知
    8. 执行Runners运行器
    9. 发布应用上下文就绪事件
    10. 返回容器
  • 相关阅读:
    Jeninks远程部署war包
    DOCKER中centos7的中文支持
    正则四
    正则三
    正则二
    正则一
    SHELL小练习
    SHELL用法九(awk练习)
    SHELL用法八(Grep语句)
    SHELL用法七(Sed语句)
  • 原文地址:https://www.cnblogs.com/isdxh/p/14164265.html
Copyright © 2020-2023  润新知