【Spring高级】Spring Boot启动过程-SpringApplication new 分析

时间:2024-04-13 08:20:25

源码分析

下面来分析new SpringApplication()的代码,其源码如下:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   this.bootstrapRegistryInitializers = new ArrayList<>(
         getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}

下面是主要步骤:

  1. this.resourceLoader = resourceLoader:resourceLoader是资源加载器,用于加载资源,如配置文件、类文件等。这里是null,ResourceLoader 的自动配置主要依赖于 Spring Boot 的自动配置机制。
  2. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)):源类this.primarySources通常是包含 Spring Boot 应用的 @SpringBootApplication 注解的类或者是定义了一些 @Configuration 的类。这些类用于定义 Spring 应用的上下文配置。primarySources 指定的类会被 Spring Boot 扫描,以查找这些BeanDefinition,也可以说是BeanDefinition源。
  3. this.webApplicationType = WebApplicationType.deduceFromClasspath()WebApplicationTypededuceFromClasspath静态方法,根据类路径推断应用的Web类型(如是否为Servlet应用、Reactive Web应用或None)。
  4. this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class)getSpringFactoriesInstances方法,从Spring的META-INF/spring.factories文件中加载所有BootstrapRegistryInitializer的实现类。BootstrapRegistryInitializer是一个用于初始化BootstrapRegistry的回调接口,在使用BootstrapRegistry之前调用它。BootstrapRegistry可以用来注册一些对象,这些对象可以在从Spring Boot启动到Spring容器初始化完成的过程中使用。简单来说,在没有Spring容器之前,可以利用BootstrapRegistry来共享一些对象。
  5. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)):从spring.factories文件中加载所有ApplicationContextInitializer的实现类,并将它们设置为ApplicationContext的初始化器。可以在创建ApplicationContextrefresh之间对ApplicationContext做一些扩展。
  6. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)):从spring.factories文件中加载所有ApplicationListener的实现类,并将它们设置为当前对象的事件监听器。
  7. this.mainApplicationClass = deduceMainApplicationClass():调用deduceMainApplicationClass方法推断主应用类,主应用类通常是包含main方法的类,用于启动Spring Boot应用。

注意,创建时,并没有去创建Spring容器,只有run时才会。

步骤演示

primarySources和Sources

这两个主要是定义了BeanDefinition源。primarySources是一开始new时,一般是启动类,主要的BeanDefinition源,也可以在Sources中自己添加。

首先看如下示例:

package com.cys.spring.chapter15;

import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class TestNewSpringApplication {

    public static void main(String[] args) {
        // 手动new一个SpringApplication,第一个参数就是初始的BeanDefinition源
        SpringApplication spring = new SpringApplication(TestNewSpringApplication.class);

        
        // 调用run方法,返回一个容器
        ConfigurableApplicationContext context = spring.run(args);

        // 打印容器里的BeanDefinition
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
        }
        context.close();

    }

    static class Bean1 {

    }

    static class Bean2 {

    }

    static class Bean3 {

    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }

运行报错如下:

Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.

这是因为程序根据pom文件的依赖,推断他是一个基于Servlet实现的web服务器,因此需要使用ServletWebServerApplicationContext,从而需要ServletWebServerFactory。

我们给手动加上:

package com.cys.spring.chapter15;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TestNewSpringApplication {

    public static void main(String[] args) {
        // 手动new一个SpringApplication,第一个参数就是初始的BeanDefinition源
        SpringApplication spring = new SpringApplication(TestNewSpringApplication.class);


        // 调用run方法,返回一个容器
        ConfigurableApplicationContext context = spring.run(args);

        // 打印容器里的BeanDefinition
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
        }
        context.close();

    }

    static class Bean1 {

    }

    static class Bean2 {

    }

    static class Bean3 {

    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }
}

再次运行结果如下:

name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor 来源:null
name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor 来源:null
name: org.springframework.context.annotation.internalCommonAnnotationProcessor 来源:null
name: org.springframework.context.event.internalEventListenerProcessor 来源:null
name: org.springframework.context.event.internalEventListenerFactory 来源:null
name: testNewSpringApplication 来源:null
name: org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory 来源:null
name: bean2 来源:com.cys.spring.chapter15.TestNewSpringApplication
name: servletWebServerFactory 来源:com.cys.spring.chapter15.TestNewSpringApplication

上面结果可以看到,一些内置的无法查看到来源,像bean2和servletWebServerFactory都是我们自己定义的,可以分别看到他们的来源。

也可以自己添加source,如下:

package com.cys.spring.chapter15;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;


@Configuration
public class TestNewSpringApplication {

    public static void main(String[] args) {
        // 手动new一个SpringApplication,第一个参数就是初始的BeanDefinition源
        SpringApplication spring = new SpringApplication(TestNewSpringApplication.class);
        // 手动添加一个BeanDefinition源
        spring.setSources(new HashSet<String>() {{add("classpath:b01.xml");}});

        // 调用run方法,返回一个容器
        ConfigurableApplicationContext context = spring.run(args);

        // 打印容器里的BeanDefinition
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
        }
        context.close();

    }

    static class Bean1 {

    }

    static class Bean2 {

    }

    static class Bean3 {

    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }
}

运行结果如下:

在这里插入图片描述

应用类型webApplicationType

先直接看源码

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

	private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";

	private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";

	private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";


static WebApplicationType deduceFromClasspath() {
   // ClassUtils.isPresent用来判断类路径下是否含有某个类
   // 1. 如果包含WEBFLUX_INDICATOR_CLASS,并且不包含WEBMVC_INDICATOR_CLASS和JERSEY_INDICATOR_CLASS,则判断为REACTIVE类型
   if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
      return WebApplicationType.REACTIVE;
   }
  
   // 2. 如果SERVLET_INDICATOR_CLASSES里的都不含有,则判断为NONE
   for (String className : SERVLET_INDICATOR_CLASSES) {
      if (!ClassUtils.isPresent(className, null)) {
         return WebApplicationType.NONE;
      }
   }
   
   // 3.最后判断为SERVLET类型
   return WebApplicationType.SERVLET;
}

主要有以下三步:

  1. 如果包含了REACTIVE相关的包,不包含servlet相关的包,就判断为REACTIVE类型
  2. 如果还不包含javax.servlet.Servletorg.springframework.web.context.ConfigurableWebApplicationContext,则判断为NONE
  3. 否则,判断为SERVLET类型

代码演示:

package com.cys.spring.chapter15;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;


@Configuration
public class TestNewSpringApplication {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        System.out.println("1. 演示获取 Bean Definition 源");
        // 手动new一个SpringApplication,第一个参数就是初始的BeanDefinition源
        SpringApplication spring = new SpringApplication(TestNewSpringApplication.class);
        // 手动添加一个BeanDefinition源
        spring.setSources(new HashSet<String>() {{add("classpath:b01.xml");}});
        System.out.println("2. 演示推断应用类型");
        Method deduceFromClasspath = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");
        deduceFromClasspath.setAccessible(true);
        System.out.println("\t应用类型为:"+deduceFromClasspath.invoke(null));

        // 调用run方法,返回一个容器
        ConfigurableApplicationContext context = spring.run(args);

        // 打印容器里的BeanDefinition
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
        }
        context.close();

    }

    static class Bean1 {

    }

    static class Bean2 {

    }

    static class Bean3 {

    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }
}

打印如下:

1. 演示获取 Bean Definition 源
2. 演示推断应用类型
	应用类型为:SERVLET
setInitializers设置容器初始化器

此步骤用来设置ApplicationContext的初始化器。ApplicationContext的初始化器主要用来在创建ApplicationContext后,refresh之前对ApplicationContext做一些扩展。

下面手动添加一个看下:

package com.cys.spring.chapter15;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;


@Configuration
public class TestNewSpringApplication {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        System.out.println("1. 演示获取 Bean Definition 源");
        // 手动new一个SpringApplication,第一个参数就是初始的BeanDefinition源
        SpringApplication spring = new SpringApplication(TestNewSpringApplication.class);
        // 手动添加一个BeanDefinition源
        spring.setSources(new HashSet<String>() {{add("classpath:b01.xml");}});
        System.out.println("2. 演示推断应用类型");
        Method deduceFromClasspath = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");
        deduceFromClasspath.setAccessible(true);
        System.out.println("\t应用类型为:"+deduceFromClasspath.invoke(null));
        System.out.println("3. 演示 ApplicationContext 初始化器");
        spring.addInitializers(applicationContext -> {
            if (applicationContext instanceof GenericApplicationContext) {
                GenericApplicationContext gac = (GenericApplicationContext) applicationContext;
                // 手动注册一个Bean3
                gac.registerBean("bean3", Bean3.class);
            }
        });

        // 调用run方法,返回一个容器
        ConfigurableApplicationContext context = spring.run(args);

        // 打印容器里的BeanDefinition
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
        }
        context.close();

    }

    static class Bean1 {

    }

    static class Bean2 {

    }

    static class Bean3 {

    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }
}

运行:

在这里插入图片描述

发现Bean3被注册进容器。

setListeners设置监听器

ApplicationListener,即事件监听器。用于监听容器中发布的事件。它是事件驱动模型开发的一部分,允许开发者在特定事件发生时执行相应的操作。

ApplicationListener监听ApplicationEvent及其子事件。通过实现ApplicationListener接口,你可以创建自定义的监听器来监听并响应特定的应用事件。例如,当使用ConfigurableApplicationContext接口中的refresh()、start()、stop()或close()方法时,会发布相应的事件(如ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent和ContextClosedEvent),你可以创建监听器来监听这些事件,并在事件发生时执行必要的操作,如初始化资源、启动服务、清理资源或关闭应用等。

示例:

package com.cys.spring.chapter15;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;


@Configuration
public class TestNewSpringApplication {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        System.out.println("1. 演示获取 Bean Definition 源");
        // 手动new一个SpringApplication,第一个参数就是初始的BeanDefinition源
        SpringApplication spring = new SpringApplication(TestNewSpringApplication.class);
        // 手动添加一个BeanDefinition源
        spring.setSources(new HashSet<String>() {{add("classpath:b01.xml");}});
        System.out.println("2. 演示推断应用类型");
        Method deduceFromClasspath = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");
        deduceFromClasspath.setAccessible(true);
        System.out.println("\t应用类型为:"+deduceFromClasspath.invoke(null));
        System.out.println("3. 演示 ApplicationContext 初始化器");
        spring.addInitializers(applicationContext -> {
            if (applicationContext instanceof GenericApplicationContext) {
                GenericApplicationContext gac = (GenericApplicationContext) applicationContext;
                // 手动注册一个Bean3
                gac.registerBean("bean3", Bean3.class);
            }
        });
        System.out.println("4. 演示监听器与事件");
        // 添加一个监听器监听所有事件
        spring.addListeners(event -> System.out.println("\t事件为:" + event.getClass()));

        // 调用run方法,返回一个容器
        ConfigurableApplicationContext context = spring.run(args);

        // 打印容器里的BeanDefinition
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
        }
        context.close();

    }

    static class Bean1 {

    }

    static class Bean2 {

    }

    static class Bean3 {

    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }
}

结果打印出很多时间,如下:

1. 演示获取 Bean Definition 源
2. 演示推断应用类型
	应用类型为:SERVLET
3. 演示 ApplicationContext 初始化器
4. 演示监听器与事件
	事件为:class org.springframework.boot.context.event.ApplicationStartingEvent
	事件为:class org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::               (v2.6.13)

	事件为:class org.springframework.boot.context.event.ApplicationContextInitializedEvent
2024-03-10 12:25:54.085  INFO 50655 --- [           main] c.c.s.c.TestNewSpringApplication         : Starting TestNewSpringApplication using Java 11.0.2 on testdeMBP with PID 50655 (/Users/fltech/Desktop/cys/学习笔记/sping高级-heima/sping-advanced/target/classes started by test in /Users/fltech/Desktop/cys/学习笔记/sping高级-heima/sping-advanced)
2024-03-10 12:25:54.092  INFO 50655 --- [           main] c.c.s.c.TestNewSpringApplication         : No active profile set, falling back to 1 default profile: "default"
	事件为:class org.springframework.boot.context.event.ApplicationPreparedEvent
2024-03-10 12:25:54.960  INFO 50655 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2024-03-10 12:25:54.973  INFO 50655 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2024-03-10 12:25:54.973  INFO 50655 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.68]
2024-03-10 12:25:55.226  INFO 50655 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2024-03-10 12:25:55.227  INFO 50655 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 811 ms
2024-03-10 12:25:55.313  INFO 50655 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
	事件为:class org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent
	事件为:class org.springframework.context.event.ContextRefreshedEvent
2024-03-10 12:25:55.331  INFO 50655 --- [           main] c.c.s.c.TestNewSpringApplication         : Started TestNewSpringApplication in 2.021 seconds (JVM running for 2.673)
	事件为:class org.springframework.boot.context.event.ApplicationStartedEvent
	事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
	事件为:class org.springframework.boot.context.event.ApplicationReadyEvent
	事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor 来源:null
name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor 来源:null
name: org.springframework.context.annotation.internalCommonAnnotationProcessor 来源:null
name: org.springframework.context.event.internalEventListenerProcessor 来源:null
name: org.springframework.context.event.internalEventListenerFactory 来源:null
name: bean3 来源:null
name: testNewSpringApplication 来源:null
name: bean1 来源:class path resource [b01.xml]
name: org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory 来源:null
name: bean2 来源:com.cys.spring.chapter15.TestNewSpringApplication
name: servletWebServerFactory 来源:com.cys.spring.chapter15.TestNewSpringApplication
	事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
	事件为:class org.springframework.context.event.ContextClosedEvent
主类推断

主类推断即用来推断含有main方法的启动Spring Boot应用的主类。

可使用反射查看

Method deduceMainApplicationClass = SpringApplication.class.getDeclaredMethod("deduceMainApplicationClass");
deduceMainApplicationClass.setAccessible(true);
System.out.println("\t主类是:"+deduceMainApplicationClass.invoke(spring))