[Spring源码] 浅析 SpringApplication`的构造方法

2023-12-13 10:20

本文主要是介绍[Spring源码] 浅析 SpringApplication`的构造方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • `SpringApplication`的构造方法
      • 获取 Bean Definition 源
      • 推断应用类型
      • 添加 ApplicationContext 初始化器
      • 添加事件监听器
      • 主类推断

SpringApplication的构造方法

Springboot的主启动类为:

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

其中 SpringApplication#run() 方法 调用的是如下静态方法:

	/*** Static helper that can be used to run a {@link SpringApplication} from the* specified source using default settings.* @param primarySource the primary source to load* @param args the application arguments (usually passed from a Java main method)* @return the running {@link ApplicationContext}*/public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class<?>[] { primarySource }, args);}/*** Static helper that can be used to run a {@link SpringApplication} from the* specified sources using default settings and user supplied arguments.* @param primarySources the primary sources to load* @param args the application arguments (usually passed from a Java main method)* @return the running {@link ApplicationContext}*/public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return new SpringApplication(primarySources).run(args);}

最终使用 new 关键字构造了 SpringApplication 对象,然后调用了非静态 run() 方法。

/*** Run the Spring application, creating and refreshing a new* {@link ApplicationContext}.* @param args the application arguments (usually passed from a Java main method)* @return a running {@link ApplicationContext}*/public ConfigurableApplicationContext run(String... args) {Startup startup = Startup.create();if (this.registerShutdownHook) {SpringApplication.shutdownHook.enableShutdownHookAddition();}DefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting(bootstrapContext, this.mainApplicationClass);try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);Banner printedBanner = printBanner(environment);context = createApplicationContext();context.setApplicationStartup(this.applicationStartup);prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);startup.started();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);}listeners.started(context, startup.timeTakenToStarted());callRunners(context, applicationArguments);}catch (Throwable ex) {...}try {if (context.isRunning()) {listeners.ready(context, startup.ready());}}catch (Throwable ex) {...}return context;}

构造 SpringApplication 对象时做了如下几件事:

  1. 获取 Bean Definition 源
  2. 推断应用类型
  3. 添加 ApplicationContext 初始化器
  4. 添加事件监听器
  5. 主类推断

获取 Bean Definition 源

package com.example.boot;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;import java.util.Arrays;
import java.util.Collections;@SpringBootApplication
public class BootApplication {public static void main(String[] args) {SpringApplication spring = new SpringApplication(BootApplication.class);// 设置 xml 的beanspring.setSources(Collections.singleton("classpath:bean.xml"));// 创建并初始化 Spring 容器ConfigurableApplicationContext context = spring.run(args);Arrays.stream(context.getBeanDefinitionNames()).forEach(i -> {System.out.println("name: " + i +" 来源: " + context.getBeanFactory().getBeanDefinition(i).getResourceDescription());});context.close();}static class Bean1 {}static class Bean2 {}@Beanpublic Bean2 bean2() {return new Bean2();}}
<?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:p="http://www.springframework.org/schema/p"xmlns:util="http://www.springframework.org/schema/util"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-4.0.xsd"
><bean id="bean1" class="com.example.boot.BootApplication.Bean1" />
</beans>

输出

...
name: bootApplication 来源: null
name: bean1 来源: class path resource [bean.xml]
name: bean2 来源: com.example.boot.BootApplication...

其中来源为 null的是 Spring内置的。

推断应用类型

应用推断主要在 SpringbootApplication的构造方法中,this.webApplicationType = WebApplicationType.deduceFromClasspath();

    static WebApplicationType deduceFromClasspath() {if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {return REACTIVE;} else {String[] var0 = SERVLET_INDICATOR_CLASSES;int var1 = var0.length;for(int var2 = 0; var2 < var1; ++var2) {String className = var0[var2];if (!ClassUtils.isPresent(className, (ClassLoader)null)) {// 非 web return NONE;}}// servletreturn SERVLET;}}

当然,我们可以直接使用反射调用这个静态方法,判断当前应用环境

        Method deduceFromClasspath = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");deduceFromClasspath.setAccessible(true);System.out.println("\t应用类型为: " + deduceFromClasspath.invoke(null));

输出

应用类型为: SERVLET

添加 ApplicationContext 初始化器

调用 SpringApplication 对象的 run() 方法时会创建 ApplicationContext,最后调用 ApplicationContext 的 refresh() 方法完成初始化。

在创建与初始化完成之间的一些拓展功能就由 ApplicationContext 初始化器完成。

SpringApplication 的构造方法中,添加的初始化器信息从配置文件中读取:

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {...this.webApplicationType = WebApplicationType.deduceFromClasspath(); 初始化器this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = this.deduceMainApplicationClass();}

当然可以调用 SpringApplication 对象的 addInitializers() 方法添加自定义初始化器:

注意添加初始化器需要在调用 run方法之前,因为 run 方法会 refresh

        // 初始化器// 这里用到了函数方法,可以简化代码spring.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println(">>>>>>>>>>");if( applicationContext instanceof GenericApplicationContext genericApplicationContext) {System.out.println(">>>>> 注册 bean3");genericApplicationContext.registerBean("bean3", Bean3.class);}}});// 创建并初始化 Spring 容器ConfigurableApplicationContext context = spring.run(args);

输出

name: bean3 来源: null
name: bean1 来源: class path resource [bean.xml]
name: bean2 来源: com.example.boot.BootApplication
name: beanNameViewResolver 来源: class path resource [org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]
name: beanNameHandlerMapping 来源: class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]应用类型为: SERVLET

添加事件监听器

与添加 ApplicationContext 初始化器一样,在 SpringApplication 的构造方法中,添加的事件监听器信息从配置文件中读取:

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {...this.webApplicationType = WebApplicationType.deduceFromClasspath(); 初始化器this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));// 事件监听器this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = this.deduceMainApplicationClass();}

可以调用 SpringApplication 对象的 addListeners() 方法添加自定义事件监听器:

        spring.addListeners(event -> System.out.println("\t事件为: " + event));// 创建并初始化 Spring 容器ConfigurableApplicationContext context = spring.run(args);

输出

2023-12-12 23:08:04.067  INFO 82643 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''事件为: org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent[source=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@54db056b]事件为: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@389c4eb1, started on Tue Dec 12 23:08:03 CST 2023]
2023-12-12 23:08:04.073  INFO 82643 --- [           main] com.example.boot.BootApplication         : Started BootApplication in 0.695 seconds (JVM running for 0.9)事件为: org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@5119fb47]事件为: org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@389c4eb1, started on Tue Dec 12 23:08:03 CST 2023]事件为: org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@5119fb47]事件为: org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@389c4eb1, started on Tue Dec 12 23:08:03 CST 2023]事件为: org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@389c4eb1, started on Tue Dec 12 23:08:03 CST 2023]事件为: org.springframework.context.event.ContextClosedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@389c4eb1, started on Tue Dec 12 23:08:03 CST 2023]

主类推断

依然是在 SpringApplication的构造方法中,有

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {...this.webApplicationType = WebApplicationType.deduceFromClasspath(); 初始化器this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));// 事件监听器this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));// 主类推断this.mainApplicationClass = this.deduceMainApplicationClass();}
    private Class<?> deduceMainApplicationClass() {try {StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();StackTraceElement[] var2 = stackTrace;int var3 = stackTrace.length;for(int var4 = 0; var4 < var3; ++var4) {StackTraceElement stackTraceElement = var2[var4];if ("main".equals(stackTraceElement.getMethodName())) {return Class.forName(stackTraceElement.getClassName());}}} catch (ClassNotFoundException var6) {}return null;}

这篇关于[Spring源码] 浅析 SpringApplication`的构造方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/488098

相关文章

Spring boot整合dubbo+zookeeper的详细过程

《Springboot整合dubbo+zookeeper的详细过程》本文讲解SpringBoot整合Dubbo与Zookeeper实现API、Provider、Consumer模式,包含依赖配置、... 目录Spring boot整合dubbo+zookeeper1.创建父工程2.父工程引入依赖3.创建ap

SpringBoot结合Docker进行容器化处理指南

《SpringBoot结合Docker进行容器化处理指南》在当今快速发展的软件工程领域,SpringBoot和Docker已经成为现代Java开发者的必备工具,本文将深入讲解如何将一个SpringBo... 目录前言一、为什么选择 Spring Bootjavascript + docker1. 快速部署与

Spring Boot spring-boot-maven-plugin 参数配置详解(最新推荐)

《SpringBootspring-boot-maven-plugin参数配置详解(最新推荐)》文章介绍了SpringBootMaven插件的5个核心目标(repackage、run、start... 目录一 spring-boot-maven-plugin 插件的5个Goals二 应用场景1 重新打包应用

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Java中读取YAML文件配置信息常见问题及解决方法

《Java中读取YAML文件配置信息常见问题及解决方法》:本文主要介绍Java中读取YAML文件配置信息常见问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录1 使用Spring Boot的@ConfigurationProperties2. 使用@Valu

创建Java keystore文件的完整指南及详细步骤

《创建Javakeystore文件的完整指南及详细步骤》本文详解Java中keystore的创建与配置,涵盖私钥管理、自签名与CA证书生成、SSL/TLS应用,强调安全存储及验证机制,确保通信加密和... 目录1. 秘密键(私钥)的理解与管理私钥的定义与重要性私钥的管理策略私钥的生成与存储2. 证书的创建与

浅析Spring如何控制Bean的加载顺序

《浅析Spring如何控制Bean的加载顺序》在大多数情况下,我们不需要手动控制Bean的加载顺序,因为Spring的IoC容器足够智能,但在某些特殊场景下,这种隐式的依赖关系可能不存在,下面我们就来... 目录核心原则:依赖驱动加载手动控制 Bean 加载顺序的方法方法 1:使用@DependsOn(最直

SpringBoot中如何使用Assert进行断言校验

《SpringBoot中如何使用Assert进行断言校验》Java提供了内置的assert机制,而Spring框架也提供了更强大的Assert工具类来帮助开发者进行参数校验和状态检查,下... 目录前言一、Java 原生assert简介1.1 使用方式1.2 示例代码1.3 优缺点分析二、Spring Fr

java使用protobuf-maven-plugin的插件编译proto文件详解

《java使用protobuf-maven-plugin的插件编译proto文件详解》:本文主要介绍java使用protobuf-maven-plugin的插件编译proto文件,具有很好的参考价... 目录protobuf文件作为数据传输和存储的协议主要介绍在Java使用maven编译proto文件的插件