目录
在我们 启动Spring Boot应用程序时,一般是下面这段代码:
SpringApplication.run(TestNewSpringApplication.class, args)
看下run方法,他是一个静态方法:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
再进去看下里面的run方法,还是一个静态方法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
这个方法主要有两个步骤:
- 创建SpringApplication对象
- 调用SpringApplication对象的run方法
下面来分别分析下这两步。
SpringApplication new 分析
源码分析
下面来分析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();
}
下面是主要步骤:
this.resourceLoader = resourceLoader
:resourceLoader是资源加载器,用于加载资源,如配置文件、类文件等。这里是null,ResourceLoader
的自动配置主要依赖于 Spring Boot 的自动配置机制。this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources))
:源类this.primarySources
通常是包含 Spring Boot 应用的@SpringBootApplication
注解的类或者是定义了一些@Configuration
的类。这些类用于定义 Spring 应用的上下文配置。primarySources
指定的类会被 Spring Boot 扫描,以查找这些BeanDefinition
,也可以说是BeanDefinition
源。this.webApplicationType = WebApplicationType.deduceFromClasspath()
:WebApplicationType
的deduceFromClasspath
静态方法,根据类路径推断应用的Web类型(如是否为Servlet应用、Reactive Web应用或None)。this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class)
:getSpringFactoriesInstances
方法,从Spring的META-INF/spring.factories
文件中加载所有BootstrapRegistryInitializer
的实现类。BootstrapRegistryInitializer
是一个用于初始化BootstrapRegistry的
回调接口,在使用BootstrapRegistry之前调用它。BootstrapRegistry可以用来注册一些对象,这些对象可以在从Spring Boot启动到Spring容器初始化完成的过程中使用。简单来说,在没有Spring容器之前,可以利用BootstrapRegistry来共享一些对象。setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class))
:从spring.factories
文件中加载所有ApplicationContextInitializer
的实现类,并将它们设置为ApplicationContext
的初始化器。可以在创建ApplicationContext
和refresh
之间对ApplicationContext
做一些扩展。setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class))
:从spring.factories
文件中加载所有ApplicationListener
的实现类,并将它们设置为当前对象的事件监听器。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;
}
主要有以下三步:
- 如果包含了REACTIVE相关的包,不包含servlet相关的包,就判断为REACTIVE类型
- 如果还不包含
javax.servlet.Servlet
和org.springframework.web.context.ConfigurableWebApplicationContext
,则判断为NONE - 否则,判断为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))
SpringApplication run 分析
主要步骤
下面先看下主要步骤概览:
-
得到 SpringApplicationRunListeners,名字取得不好,实际是事件发布器
- 发布 application starting 事件1️⃣
-
封装启动 args
-
准备 Environment 添加命令行参数(*)
-
ConfigurationPropertySources 处理(*)
- 发布 application environment 已准备事件2️⃣
-
通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理(*)
- application.properties,由 StandardConfigDataLocationResolver 解析
- spring.application.json
-
绑定 spring.main 到 SpringApplication 对象(*)
-
打印 banner(*)
-
创建容器
-
准备容器
- 发布 application context 已初始化事件3️⃣
-
加载 bean 定义
- 发布 application prepared 事件4️⃣
-
refresh 容器
- 发布 application started 事件5️⃣
-
执行 runner
-
发布 application ready 事件6️⃣
-
这其中有异常,发布 application failed 事件7️⃣
-
带 * 的有独立的示例
下面逐一演示下步骤
步骤演示
事件发布
对应第1步
演示时,先获取到时间发布器,在Springboot中,自动配置过程中,在自动配置文件org/springframework/boot/spring-boot/2.6.13/spring-boot-2.6.13.jar!/META-INF/spring.factories
中会定义实现类有哪些。
示例:
package com.cys.spring.chapter15;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.support.SpringFactoriesLoader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
public class TestSpringApplicationRun {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
SpringApplication app = new SpringApplication();
/**
* 1. 演示事件发布
*/
// 添加时间监听器
app.addListeners(e -> System.out.println(e.getClass()));
// 获取事件发送器实现类名,实现类名在配置文件org/springframework/boot/spring-boot/2.6.13/spring-boot-2.6.13.jar!/META-INF/spring.factories中
// 配置如下:org.springframework.boot.SpringApplicationRunListener=org.springframework.boot.context.event.EventPublishingRunListener
// 下面可以通过SpringFactoriesLoader.loadFactoryNames获取实现类的类名有哪些,这里就一个
List<String> names = SpringFactoriesLoader.loadFactoryNames(SpringApplicationRunListener.class, TestSpringApplicationRun.class.getClassLoader());
for (String name : names) {
System.out.println(name);
// 获取类型
Class<?> clazz = Class.forName(name);
// 获取构造方法
Constructor<?> constructor = clazz.getConstructor(SpringApplication.class, String[].class);
// 创建发布器
SpringApplicationRunListener publisher = (SpringApplicationRunListener) constructor.newInstance(app, args);
// 开始发布各种事件
// 有些事件发布需要一个容器
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
publisher.starting(bootstrapContext); // spring boot 开始启动
publisher.environmentPrepared(bootstrapContext, new StandardEnvironment()); // 环境信息准备完毕
GenericApplicationContext context = new GenericApplicationContext();
publisher.contextPrepared(context); // 在 spring 容器创建,并调用初始化器之后,发送此事件
publisher.contextLoaded(context); // 所有 bean definition 加载完毕
context.refresh();
publisher.started(context); // spring 容器初始化完成(refresh 方法调用完毕)
publisher.running(context); // spring boot 启动完毕
publisher.failed(context, new Exception("出错了")); // spring boot 启动出错
}
}
}
容器相关
对应8,9,10,11步
这里容器相关的步骤主要是:创建容器、容器增强、加载 BeanDefinition、refresh 容器。
代码如下:
package com.cys.spring.chapter15;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.boot.*;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.*;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ClassPathResource;
import java.util.Arrays;
public class TestSpringApplicationRun2 {
public static void main(String[] args) {
SpringApplication app = new SpringApplication();
// 添加初始化器,模拟初始化增强,会在地8步后,容器创建好菜可以被调用
app.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("执行初始化器增强...");
}
});
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器");
GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器");
// 这里就模拟给容器做些增强
for (ApplicationContextInitializer initializer : app.getInitializers()) {
initializer.initialize(context);
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义");
// 先获取容器的BeanFactory
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
// 1. 创建根据注解获取BeanDefinition的读取器
AnnotatedBeanDefinitionReader reader1 = new AnnotatedBeanDefinitionReader(beanFactory);
// 解析Config中的注解,获取BeanDefinition,并在到容器中
reader1.register(Config.class);
// 2. 创建根据xml获取BeanDefinition的读取器
XmlBeanDefinitionReader reader2 = new XmlBeanDefinitionReader(beanFactory);
reader2.loadBeanDefinitions(new ClassPathResource("b03.xml"));
// 23. 扫描获取BeanDefinition的读取器
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
scanner.scan("com.cys.spring.chapter15.beans");
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 11. refresh 容器");
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println("name:" + name + " 来源:" + beanFactory.getBeanDefinition(name).getResourceDescription());
}
}
/**
* 辅助函数:根据类型参数获取不同的容器类型
*
* @param type
* @return
*/
private static GenericApplicationContext createApplicationContext(WebApplicationType type) {
GenericApplicationContext context = null;
switch (type) {
case SERVLET : context = new AnnotationConfigServletWebServerApplicationContext();
break;
case REACTIVE : context = new AnnotationConfigReactiveWebServerApplicationContext();
break;
case NONE : context = new AnnotationConfigApplicationContext();
break;
}
return context;
}
static class Bean4 {
}
static class Bean5 {
}
static class Bean6 {
}
@Configuration
static class Config {
@Bean
public Bean5 bean5() {
return new Bean5();
}
@Bean
public ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
}
运行结果,打印如下:
>>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器
>>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器
执行初始化器增强...
>>>>>>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义
>>>>>>>>>>>>>>>>>>>>>>>> 11. refresh 容器
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:testSpringApplicationRun2.Config 来源:null
name:bean4 来源:class path resource [b03.xml]
name:bean7 来源:file [.../classes/com/cys/spring/chapter15/beans/Bean7.class]
name:org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory 来源:null
name:bean5 来源:com.cys.spring.chapter15.TestSpringApplicationRun2$Config
name:servletWebServerFactory 来源:com.cys.spring.chapter15.TestSpringApplicationRun2$Config
执行 runner
对应地2,12步
在Spring Boot中,CommandLineRunner
和ApplicationRunner
是两个常用的接口,它们允许你在Spring Boot应用启动后立即执行一些自定义的代码。这些接口特别有用,当你需要在应用启动之后立即进行一些初始化工作,比如数据加载、缓存预热、检查配置等。
下面来看下如何主动调用,示例:
package com.cys.spring.chapter15;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.boot.*;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.*;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ClassPathResource;
import java.util.Arrays;
public class TestSpringApplicationRun2 {
public static void main(String[] args) throws Exception {
SpringApplication app = new SpringApplication();
// 添加初始化器,模拟初始化增强,会在地8步后,容器创建好菜可以被调用
app.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("执行初始化器增强...");
}
});
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args");
// 执行runner使用
DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器");
GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器");
// 这里就模拟给容器做些增强
for (ApplicationContextInitializer initializer : app.getInitializers()) {
initializer.initialize(context);
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义");
// 先获取容器的BeanFactory
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
// 1. 创建根据注解获取BeanDefinition的读取器
AnnotatedBeanDefinitionReader reader1 = new AnnotatedBeanDefinitionReader(beanFactory);
// 解析Config中的注解,获取BeanDefinition,并在到容器中
reader1.register(Config.class);
// 2. 创建根据xml获取BeanDefinition的读取器
XmlBeanDefinitionReader reader2 = new XmlBeanDefinitionReader(beanFactory);
reader2.loadBeanDefinitions(new ClassPathResource("b03.xml"));
// 23. 扫描获取BeanDefinition的读取器
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
scanner.scan("com.cys.spring.chapter15.beans");
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 11. refresh 容器");
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println("name:" + name + " 来源:" + beanFactory.getBeanDefinition(name).getResourceDescription());
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 12. 执行 runner");
for (CommandLineRunner runner : context.getBeansOfType(CommandLineRunner.class).values()) {
runner.run(args);
}
for (ApplicationRunner runner : context.getBeansOfType(ApplicationRunner.class).values()) {
runner.run(arguments);
}
}
/**
* 辅助函数:根据类型参数获取不同的容器类型
*
* @param type
* @return
*/
private static GenericApplicationContext createApplicationContext(WebApplicationType type) {
GenericApplicationContext context = null;
switch (type) {
case SERVLET : context = new AnnotationConfigServletWebServerApplicationContext();
break;
case REACTIVE : context = new AnnotationConfigReactiveWebServerApplicationContext();
break;
case NONE : context = new AnnotationConfigApplicationContext();
break;
}
return context;
}
static class Bean4 {
}
static class Bean5 {
}
static class Bean6 {
}
@Configuration
static class Config {
@Bean
public Bean5 bean5() {
return new Bean5();
}
@Bean
public ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean
public CommandLineRunner commandLineRunner() {
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
System.out.println("commandLineRunner()..." + Arrays.toString(args));
}
};
}
@Bean
public ApplicationRunner applicationRunner() {
return new ApplicationRunner() {
@Override
public void run(ApplicationArguments args) throws Exception {
// 可以拿到Application里的各种参数
System.out.println("applicationRunner()..." + Arrays.toString(args.getSourceArgs()));
System.out.println(args.getOptionNames());
System.out.println(args.getOptionValues("server.port"));
System.out.println(args.getNonOptionArgs());
}
};
}
}
}
运行打印结果如下:
>>>>>>>>>>>>>>>>>>>>>>>> 12. 执行 runner
commandLineRunner()...[]
applicationRunner()...[]
[]
null
[]
准备Environment
对应第3步
这里以Springboot中的ApplicationEnvironment
为例。
ApplicationEnvironment
是一个用于访问应用环境信息的接口。它通常用于读取应用的环境属性,例如配置文件中的属性、命令行参数、系统属性等。ApplicationEnvironment
继承自Spring框架的Environment
接口,并添加了Spring Boot特有的功能。
其类图如下:
ApplicationEnvironment
的主要用途包括:
- 读取属性:通过
getProperty
方法读取属性值。这些属性可能来自application.properties
或application.yml
配置文件、命令行参数、环境变量或系统属性等。 - 属性源管理:
ApplicationEnvironment
管理着多个属性源(Property Sources),每个属性源都包含一组键值对。Spring Boot按照特定的顺序(例如,命令行参数优先级最高,然后是配置文件等)来解析属性值。 - 自定义属性源:你可以向
ApplicationEnvironment
添加自定义的属性源,以便在应用启动时加载特定的配置。 - 激活的配置文件:Spring Boot支持配置文件的激活,例如
application-dev.properties
用于开发环境,application-prod.properties
用于生产环境。ApplicationEnvironment
提供了方法来获取当前激活的配置文件。 - 环境检查:通过
Environment
接口的方法,你可以检查应用是否处于特定环境(如测试环境或生产环境),这对于条件性地配置应用行为非常有用。
在Spring Boot应用中,你通常不需要直接实例化ApplicationEnvironment
,因为Spring Boot会自动配置并注入它。你可以在你的组件中通过依赖注入来获取并使用它。
由于这个类在org.springframework.boot中,并不对外开放,需要在内部测试:
package org.springframework.boot;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.ResourcePropertySource;
import java.io.IOException;
public class Step3 {
public static void main(String[] args) throws IOException {
ApplicationEnvironment env = new ApplicationEnvironment(); // 系统环境变量, properties, yaml
// 加一个配置源到最后一位
env.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource("step3.properties")));
// 加油一个配置源到第一位
env.getPropertySources().addFirst(new SimpleCommandLinePropertySource(args));
for (PropertySource<?> ps : env.getPropertySources()) {
System.out.println(ps);
}
System.out.println(env.getProperty("JAVA_HOME"));
System.out.println(env.getProperty("server.port"));
}
}
EnvironmentPostProcessor后处理器
对应第5步。
EnvironmentPostProcessor
主要功能是在Spring Boot环境加载后,动态加载其他配置文件中的一些信息到环境变量里面。通过这个接口,我们可以添加或修改环境配置,实现多个微服务共同配置的修改与维护。
当项目需要在不同环境(如开发、测试、生产等)中运行时,每套环境可能有专属的配置文件。EnvironmentPostProcessor允许我们在项目启动时读取环境变量或启动命令参数,从而获取特定环境下的配置信息(如服务地址、命名空间名称、分组名称等)。然后,根据这些配置参数或环境变量,我们可以读取不同的配置文件,实现不同环境使用不同的配置文件,而无需修改代码或本地配置文件。
使用EnvironmentPostProcessor时,我们需要实现该接口,并覆盖其postProcessEnvironment方法。在这个方法中,我们可以加载对应的配置文件到环境变量中。同时,还需要在spring.factories文件中进行相应配置,以便Spring Boot在启动时能够识别并加载我们的EnvironmentPostProcessor实现类。
在Springboot中通过ApplicationListener
监听器EnvironmentPostProcessorApplicationListener
,来监听并执行下面那些后处理器,如下图:
示例:
public class Step5 {
public static void main(String[] args) {
SpringApplication app = new SpringApplication();
app.addListeners(new EnvironmentPostProcessorApplicationListener());
/*List<String> names = SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, Step5.class.getClassLoader());
for (String name : names) {
System.out.println(name);
}*/
EventPublishingRunListener publisher = new EventPublishingRunListener(app, args);
ApplicationEnvironment env = new ApplicationEnvironment();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 增强前");
for (PropertySource<?> ps : env.getPropertySources()) {
System.out.println(ps);
}
publisher.environmentPrepared(new DefaultBootstrapContext(), env);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 增强后");
for (PropertySource<?> ps : env.getPropertySources()) {
System.out.println(ps);
}
}
}
环境参数与对象绑定
底层通过Binder
来绑定:
示例:
package org.springframework.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.ResourcePropertySource;
import java.io.IOException;
public class Step6 {
// 绑定 spring.main 前缀的 key value 至 SpringApplication, 请通过 debug 查看
public static void main(String[] args) throws IOException {
SpringApplication application = new SpringApplication();
ApplicationEnvironment env = new ApplicationEnvironment();
// 把step4.properties配置源加到环境
env.getPropertySources().addLast(new ResourcePropertySource("step4", new ClassPathResource("step4.properties")));
// 把step6.properties配置源加到环境
env.getPropertySources().addLast(new ResourcePropertySource("step6", new ClassPathResource("step6.properties")));
// User user = Binder.get(env).bind("user", User.class).get();
// System.out.println(user);
// User user = new User();
// Binder.get(env).bind("user", Bindable.ofInstance(user));
// System.out.println(user);
System.out.println(application);
Binder.get(env).bind("spring.main", Bindable.ofInstance(application));
System.out.println(application);
}
static class User {
private String firstName;
private String middleName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public String toString() {
return "User{" +
"firstName='" + firstName + '\'' +
", middleName='" + middleName + '\'' +
", lastName='" + lastName + '\'' +
'}';
}
}
}