[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 的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();
    }


}


<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;
    }

你可能感兴趣的:(Java探索者之路,spring,java,后端)