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

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


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 的bean
        spring.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 {
    }
    @Bean
    public 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.xsd
            http://www.springframework.org/schema/util
            http://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;
                }
            }
          // servlet
            return 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>() {
            @Override
            public 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;
    }
相关文章
|
15天前
|
小程序 JavaScript Java
高校宿舍信息|基于Spring Boot的高校宿舍信息管理系统的设计与实现(源码+数据库+文档)
高校宿舍信息|基于Spring Boot的高校宿舍信息管理系统的设计与实现(源码+数据库+文档)
22 0
|
22天前
|
监控 Java 应用服务中间件
Spring Boot 源码面试知识点
【5月更文挑战第12天】Spring Boot 是一个强大且广泛使用的框架,旨在简化 Spring 应用程序的开发过程。深入了解 Spring Boot 的源码,有助于开发者更好地使用和定制这个框架。以下是一些关键的知识点:
37 6
|
22天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
44 3
|
2天前
|
Java Spring
【JavaEE进阶】 Spring AOP源码简单剖析
【JavaEE进阶】 Spring AOP源码简单剖析
|
15天前
|
小程序 JavaScript Java
小程序商城|基于Spring Boot的智能小程序商城的设计与实现(源码+数据库+文档)
小程序商城|基于Spring Boot的智能小程序商城的设计与实现(源码+数据库+文档)
23 0
小程序商城|基于Spring Boot的智能小程序商城的设计与实现(源码+数据库+文档)
|
15天前
|
安全 JavaScript Java
在线问卷调查|基于Spring Boot的在线问卷调查系统的设计与实现(源码+数据库+文档)
在线问卷调查|基于Spring Boot的在线问卷调查系统的设计与实现(源码+数据库+文档)
27 0
|
22天前
|
存储 前端开发 Java
Spring Boot自动装配的源码学习
【4月更文挑战第8天】Spring Boot自动装配是其核心机制之一,其设计目标是在应用程序启动时,自动配置所需的各种组件,使得应用程序的开发和部署变得更加简单和高效。下面是关于Spring Boot自动装配的源码学习知识点及实战。
18 1
|
22天前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
72 0
|
22天前
|
缓存 安全 Java
Spring Boot 面试题及答案整理,最新面试题
Spring Boot 面试题及答案整理,最新面试题
157 0
|
22天前
|
存储 JSON Java
SpringBoot集成AOP实现每个接口请求参数和返回参数并记录每个接口请求时间
SpringBoot集成AOP实现每个接口请求参数和返回参数并记录每个接口请求时间
58 2