笔记参考B站黑马程序员视频:spring原理
启动springboot由run方法开启,比如:
SpringApplication.run(Demo.class,args);
在源码中

关键就在下面了

return new SpringApplication(primarySources).run(args)重要逻辑就在这个构造方法和run方法中了
1.构造方法
这个构造方法的源码如下:

上面主要就是完成5个步骤:
- 获取 Bean Definition 源
- 推断应用类型
- ApplicationContext 初始化器
- 监听器与事件
- 主类推断
1.获取Bean Definition源
Bean Definition 是描述应用程序中所有 Bean 的配置元数据。应用程序会获取所有配置的 Bean 定义信息 容器在run方法里创建和初始化,我们通过调用run方法查看是否获取了Bean Definition context.getBeanFactory().getBeanDefinition(name).getResourceDescription()可以获取来源信息。
package com.dreams.demo39;
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 Demo {
public static void main(String[] args) throws Exception {
SpringApplication spring = new SpringApplication(Demo.class);
System.out.println("1. 演示获取 Bean Definition 源");
ConfigurableApplicationContext context = spring.run(args);
// 创建 ApplicationContext
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();
}
}直接运行会报错
Caused by: org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.

大致是pom.xml导入了spring-boot-starter-web,所以他推断容器的默认实现是ServletWebServerApplicationContext,所以我们只要注入该Bean即可。
@Bean
public TomcatServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}来源为null就是内置的了

还可以多加源。
spring.setSources(Collections.singleton("classpath:b01.xml"));
2.推断应用类型
Spring 框架能够根据配置和类路径推断出应用程序的类型,比如是 Web 应用程序还是普通的应用程序。 对应源码在WebApplicationType的deduceFromClasspath()方法里

源码如下:

通过检查是否存在 WEBFLUX_INDICATOR_CLASS来判断是否为 Reactive 应用程序。
如果存在 WEBFLUX_INDICATOR_CLASS,并且不存在 WEBMVC_INDICATOR_CLASS
以及不存在 JERSEY_INDICATOR_CLASS
则返回 WebApplicationType.REACTIVE,这是spring应用的实现。 如果不是 Reactive 应用,则通过遍历 SERVLET_INDICATOR_CLASSES 数组中的类名进行检查。 对于 SERVLET_INDICATOR_CLASSES 中的每个类名,如果不存在于类路径中,则直接返回 WebApplicationType.NONE,表示当前应用程序不是基于 Servlet 的应用。 如果以上条件都不符合,则默认返回 WebApplicationType.SERVLET,表示当前应用程序是基于 Servlet 的。 方法是私有的,所以我们可以根据反射调用。
public static void main(String[] args) throws Exception {
SpringApplication spring = new SpringApplication(Demo.class);
System.out.println("1. 演示获取 Bean Definition 源");
System.out.println("2. 演示推断应用类型");
Method deduceFromClasspath = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");
deduceFromClasspath.setAccessible(true);
System.out.println("\t应用类型为:"+deduceFromClasspath.invoke(null));
}
3.ApplicationContext 初始化器
在run方法中,会创建 ApplicationContext,调用初始化器对 ApplicationContext 做扩展 ,最后执行ApplicationContext.refresh方法来初始化。 而初始化器就在构造器创建。 这里是读取配置文件中的初始化器。

我们直接创建一个,而不用配置文件中的初始化器。
public static void main(String[] args) throws Exception {
SpringApplication spring = new SpringApplication(Demo.class);
System.out.println("1. 演示获取 Bean Definition 源");
System.out.println("2. 演示推断应用类型");
System.out.println("3. 演示 ApplicationContext 初始化器");
spring.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
if (applicationContext instanceof GenericApplicationContext) {
GenericApplicationContext gac = (GenericApplicationContext) applicationContext;
gac.registerBean("bean3", Bean3.class);
}
}
});
}Lombda简化
spring.addInitializers(applicationContext -> {
if (applicationContext instanceof GenericApplicationContext) {
GenericApplicationContext gac = (GenericApplicationContext) applicationContext;
gac.registerBean("bean3", Bean3.class);
}
});这里我们将”bean3″ 放到这个初始化器里,这时再调用run方法获取Bean可以看到,bean3在调用初始化器对 ApplicationContext 做扩展时加入了
public static void main(String[] args) throws Exception {
SpringApplication spring = new SpringApplication(Demo.class);
System.out.println("1. 演示获取 Bean Definition 源");
System.out.println("2. 演示推断应用类型");
System.out.println("3. 演示 ApplicationContext 初始化器");
spring.addInitializers(applicationContext -> {
if (applicationContext instanceof GenericApplicationContext) {
GenericApplicationContext gac = (GenericApplicationContext)applicationContext;
gac.registerBean("bean3", Bean3.class);
}
});
ConfigurableApplicationContext context = spring.run(args);
// 创建 ApplicationContext
for (String name : context.getBeanDefinitionNames()) {
System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
}
context.close();
}
4.监听器与事件
监听器(Listeners)和事件(Events)是一种重要的机制,用于实现应用程序内部组件之间的解耦和通信。 同样这里也是配置文件中的监听器与事件,用于在应用程序上下文初始化过程中使用。

在run方法中产生事件,就会回调onApplicationEvent方法,这里我们添加一个将所有监听打印出来。
public static void main(String[] args) throws Exception {
SpringApplication spring = new SpringApplication(Demo.class);
System.out.println("1. 演示获取 Bean Definition 源");
System.out.println("2. 演示推断应用类型");
System.out.println("3. 演示 ApplicationContext 初始化器");
System.out.println("4. 演示监听器与事件");
spring.addListeners(new ApplicationListener<ApplicationEvent>() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("\t事件为:" + event.getClass());
}
});
ConfigurableApplicationContext context = spring.run(args);
// 创建 ApplicationContext
for (String name : context.getBeanDefinitionNames()) {
System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
}
context.close();
}

5.主类推断
就是查看main方法在哪个类里。

方法源码:

我们反射调用一下:
public static void main(String[] args) throws Exception {
SpringApplication spring = new SpringApplication(Demo.class);
System.out.println("1. 演示获取 Bean Definition 源");
System.out.println("2. 演示推断应用类型");
System.out.println("3. 演示 ApplicationContext 初始化器");
System.out.println("4. 演示监听器与事件");
System.out.println("5. 演示主类推断");
Method deduceMainApplicationClass = SpringApplication.class.getDeclaredMethod("deduceMainApplicationClass");
deduceMainApplicationClass.setAccessible(true);
System.out.println("\t主类是:"+deduceMainApplicationClass.invoke(spring));
}可以看到成功获取了。

2.run流程
步骤一:得到 SpringApplicationRunListeners,虽然叫Listeners,但是实际是事件发布器。
SpringApplicationRunListener是一个接口

下面这是事件发布器的组合器SpringApplicationRunListeners

SpringApplicationRunListener它对应的实现EventPublishingRunListener

这里通过配置文件来配置他的实现类

配置文件在这个包下

读取spring.factories配置,可以使用spring提供的SpringFactoriesLoader.loadFactoryNames方法
package com.dreams.demo39;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.core.io.support.SpringFactoriesLoader;
import java.util.List;
public class RunDemo {
public static void main(String[] args) throws Exception{
// 添加 app 监听器
SpringApplication app = new SpringApplication();
app.addListeners(e -> System.out.println(e.getClass()));
// 获取事件发送器实现类名
// 读取配置文件
List<String> names = SpringFactoriesLoader.loadFactoryNames(SpringApplicationRunListener.class, RunDemo.class.getClassLoader());
for (String name : names) {
System.out.println(name);
}
}
}可以看到类名被成功获取了

run 方法内获取事件发布器 (得到 SpringApplicationRunListeners) 的过程, 对应步骤中
- 获取事件发布器
- 发布 application starting 事件
- 发布 application environment 已准备事件
- 发布 application context 已初始化事件
- 发布 application prepared 事件
- 发布 application started 事件
- 发布 application ready 事件
- 这其中有异常,发布 application failed 事件
package com.dreams.demo39;
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.util.List;
public class RunDemo {
public static void main(String[] args) throws Exception{
// 添加 app 监听器
SpringApplication app = new SpringApplication();
app.addListeners(e -> System.out.println("事件:" + e.getClass()));
// 获取事件发送器实现类名
List<String> names = SpringFactoriesLoader.loadFactoryNames(SpringApplicationRunListener.class, RunDemo.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();
// 事件1:spring boot 开始启动事件
publisher.starting(bootstrapContext);
// 事件2:环境信息准备完毕事件
publisher.environmentPrepared(bootstrapContext, new StandardEnvironment());
//事件3: 在 spring 容器创建,并调用初始化器之后,发送此事件
GenericApplicationContext context = new GenericApplicationContext();
publisher.contextPrepared(context);
//事件4: 所有 bean definition 加载完毕后调用此事件
publisher.contextLoaded(context);
context.refresh();
// 事件5:spring 容器初始化完成(refresh 方法调用完毕)
publisher.started(context);
// 事件6:spring boot 启动完毕
publisher.running(context);
// 事件7:spring boot 启动过程中,出现错误,发送该事件,
publisher.failed(context, new Exception("出错了"));
}
}
}输出的蓝色的就是对应事件,其他不是run方法加入的事件

步骤二:封装启动 args
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args");
DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);这里是为步骤十二准备的。
步骤三:准备 Environment 添加命令行参数
环境信息,就是配置信息的抽象
配置信息可能来源系统环境变量,propertie,yaml,环境信息就是把他们组合到一起,将来方便获取。
StandardEnvironment是spring的实现。springboot对其扩展了一下,默认实现是ApplicationEnvironment。

作用就是根据键找到值
package org.springframework.boot;
import org.springframework.core.env.PropertySource;
import java.io.IOException;
public class Step3 {
public static void main(String[] args) throws IOException {
// 系统环境变量, properties, yaml
ApplicationEnvironment env = new ApplicationEnvironment();
for (PropertySource<?> ps : env.getPropertySources()) {
System.out.println(ps);
}
}
}默认来源只有系统属性和系统环境。

按优先级查找,系统属性找到了就不会往下找。
public static void main(String[] args) throws IOException {
// 系统环境变量, properties, yaml
ApplicationEnvironment env = new ApplicationEnvironment();
for (PropertySource<?> ps : env.getPropertySources()) {
System.out.println(ps);
}
System.out.println(env.getProperty("JAVA_HOME"));
}默认输出java home目录

加入VM参数:-DJAVA_HOME=deams

然后可以添加其他来源
public static void main(String[] args) throws IOException {
ApplicationEnvironment env = new ApplicationEnvironment();
// 添加配置文件来源 addLast优先级最后
env.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource("application.properties")));
// 添加命令行来源 addFirst优先级最前
env.getPropertySources().addFirst(new SimpleCommandLinePropertySource(args));
for (PropertySource<?> ps : env.getPropertySources()) {
System.out.println(ps);
}
System.out.println(env.getProperty("server.port"));
}
不过注意配置文件application.properties来源不是在个步骤加的,只加了命令行来源
步骤四:ConfigurationPropertySources 处理
package org.springframework.boot;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.ResourcePropertySource;
import java.io.IOException;
public class Step4 {
public static void main(String[] args) throws IOException, NoSuchFieldException {
ApplicationEnvironment env = new ApplicationEnvironment();
env.getPropertySources().addLast(
new ResourcePropertySource("step4", new ClassPathResource("step4.properties"))
);
System.out.println(env.getProperty("user.first-name"));
System.out.println(env.getProperty("user.middle-name"));
System.out.println(env.getProperty("user.last-name"));
}
}可以看到默认_写成-是获取不到的

但是调用ConfigurationPropertySources 处理env
package org.springframework.boot;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.ResourcePropertySource;
import java.io.IOException;
public class Step4 {
public static void main(String[] args) throws IOException, NoSuchFieldException {
ApplicationEnvironment env = new ApplicationEnvironment();
env.getPropertySources().addLast(
new ResourcePropertySource("step4", new ClassPathResource("step4.properties"))
);
ConfigurationPropertySources.attach(env);
for (PropertySource<?> ps : env.getPropertySources()) {
System.out.println(ps);
}
System.out.println(env.getProperty("user.first-name"));
System.out.println(env.getProperty("user.middle-name"));
System.out.println(env.getProperty("user.last-name"));
}
}会添加一个ConfigurationPropertySourcesPropertySource {name=’configurationProperties’}来源,并且优先级是最高的,这样就能把命名不统一的统一。

步骤五:通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理
注意:这里SpringApplicationRunListeners会发布 application environment 已准备事件
application.properties,由 StandardConfigDataLocationResolver 解析
第五步就是对 ApplicationEnvironment的进一步处理,配置文件application.properties来源就是在这个步骤加的
对应的接口是EnvironmentPostProcessor

比如它的实现ConfigDataEnvironmentPostProcessor,就是去读取配置文件application.properties

package org.springframework.boot;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor;
import org.springframework.boot.logging.DeferredLog;
import org.springframework.boot.logging.DeferredLogs;
import org.springframework.core.env.PropertySource;
public class Step5 {
public static void main(String[] args) {
SpringApplication app = new SpringApplication();
ApplicationEnvironment env = new ApplicationEnvironment();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 增强前");
for (PropertySource<?> ps : env.getPropertySources()) {
System.out.println(ps);
}
ConfigDataEnvironmentPostProcessor postProcessor1 = new ConfigDataEnvironmentPostProcessor(new DeferredLogs(), new DefaultBootstrapContext());
postProcessor1.postProcessEnvironment(env, app);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 增强后");
for (PropertySource<?> ps : env.getPropertySources()) {
System.out.println(ps);
}
}
}可以看到加入了配置文件来源

还有一个实现是RandomValuePropertySourceEnvironmentPostProcessor,会返回一个随机值
package org.springframework.boot;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor;
import org.springframework.boot.logging.DeferredLog;
import org.springframework.boot.logging.DeferredLogs;
import org.springframework.core.env.PropertySource;
public class Step5 {
public static void main(String[] args) {
SpringApplication app = new SpringApplication();
ApplicationEnvironment env = new ApplicationEnvironment();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 增强前");
for (PropertySource<?> ps : env.getPropertySources()) {
System.out.println(ps);
}
ConfigDataEnvironmentPostProcessor postProcessor1 = new ConfigDataEnvironmentPostProcessor(new DeferredLogs(), new DefaultBootstrapContext());
postProcessor1.postProcessEnvironment(env, app);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 增强后");
for (PropertySource<?> ps : env.getPropertySources()) {
System.out.println(ps);
}
RandomValuePropertySourceEnvironmentPostProcessor postProcessor2 = new RandomValuePropertySourceEnvironmentPostProcessor(new DeferredLog());
postProcessor2.postProcessEnvironment(env, app);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 增强后");
for (PropertySource<?> ps : env.getPropertySources()) {
System.out.println(ps);
}
System.out.println(env.getProperty("server.port"));
System.out.println(env.getProperty("random.int"));
System.out.println(env.getProperty("random.int"));
System.out.println(env.getProperty("random.int"));
System.out.println(env.getProperty("random.uuid"));
System.out.println(env.getProperty("random.uuid"));
}
}
不过它的默认实现应该从配置文件中读取

调用SpringFactoriesLoader.loadFactoryNames方法读取
package org.springframework.boot;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.EnvironmentPostProcessorApplicationListener;
import org.springframework.core.io.support.SpringFactoriesLoader;
import java.util.List;
public class Step5 {
public static void main(String[] args) {
SpringApplication app = new SpringApplication();
List<String> names = SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, Step5.class.getClassLoader());
for (String name : names) {
System.out.println(name);
}
}
}如下

然后就是由监听器来调用他们的方法
监听器的实现还是在配置文件里读取,EnvironmentPostProcessorApplicationListener就是去读取环境的处理器,然后去调用他们。

这里还会通过事件发布器来调用
package org.springframework.boot;
import org.springframework.boot.context.event.EventPublishingRunListener;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.EnvironmentPostProcessorApplicationListener;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.SpringFactoriesLoader;
import java.util.List;
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);
}
}
}
步骤六:绑定 spring.main开的属性 到 SpringApplication 对象
也就是将env的属性绑定到已有的java对象。
也就是@ConfigurationProperties的底层
就是将配置文件中的配置属性与SpringApplication中的对象绑定
比如配置文件step6.properties
spring.main.banner-mode=off spring.main.lazy-initialization=true
SpringApplication中的对应属性


package org.springframework.boot;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
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();
env.getPropertySources().addLast(new ResourcePropertySource("step6", new ClassPathResource("step6.properties")));
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 + '\'' +
'}';
}
}
}通过debug看看


成功改变了。


步骤七:打印 banner
这里就是打印springboot启动的banner了
package org.springframework.boot;
import org.springframework.core.io.DefaultResourceLoader;
public class Step7 {
public static void main(String[] args) {
ApplicationEnvironment env = new ApplicationEnvironment();
SpringApplicationBannerPrinter printer = new SpringApplicationBannerPrinter(
new DefaultResourceLoader(),
new SpringBootBanner()
);
// 版本号的获取
System.out.println(SpringBootVersion.getVersion());
printer.print(env, Step7.class, System.out);
}
}
还有一些其他的实现
文字 banner
env.getPropertySources().addLast(new MapPropertySource("custom", Collections.singletonMap("spring.banner.location", "banner1.txt")));图片 banner
env.getPropertySources().addLast(new MapPropertySource("custom", Collections.singletonMap("spring.banner.image.location","banner2.png")));
步骤八:创建容器
根据传入的类型创建对应容器。
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器");
GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);逻辑在createApplicationContext中
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;
}
步骤九:准备容器
SpringApplication app = new SpringApplication();
// 添加一个初始化器
app.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("执行初始化器增强...");
}
});
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args");
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);
}
步骤十:加载 bean 定义
读取Bean定义到Bean工厂 这里从配置里读取 AnnotatedBeanDefinitionReader reader1 = new AnnotatedBeanDefinitionReader(beanFactory); 使用注解的方式注册 bean。 XmlBeanDefinitionReader reader2 = new XmlBeanDefinitionReader(beanFactory); 使用 XML 文件注册 bean。 ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory); 扫描指定包路径下的 bean。 在构造函数中,已经加载了Bean的配置信息,而这一步就是从Bean的配置信息里加载 bean 定义。
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义");
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
AnnotatedBeanDefinitionReader reader1 = new AnnotatedBeanDefinitionReader(beanFactory);
XmlBeanDefinitionReader reader2 = new XmlBeanDefinitionReader(beanFactory);
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
reader1.register(Config.class);
reader2.loadBeanDefinitions(new ClassPathResource("b03.xml"));
scanner.scan("com.dreams.demo39.sub");
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 11. refresh 容器");
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println("name:" + name + " 来源:" + beanFactory.getBeanDefinition(name).getResourceDescription());
}
步骤十一:refresh 容器
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 11. refresh 容器");
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println("name:" + name + " 来源:" + beanFactory.getBeanDefinition(name).getResourceDescription());
}
步骤十二:执行 runner
注意:这里SpringApplicationRunListeners会发布 application running事件,如果这其中有异常,发布 application failed 事件
runner就是实现了特定接口的Bean,在该步骤被调用
要实现的接口是ApplicationRunner和CommandLineRunner,用于在 Spring Boot 应用程序启动后执行一些特定的操作。
CommandLineRunner的run方法的参数是一个字符串数组 args,可以用来接收命令行参数。

ApplicationRunnerrun 方法的参数是一个 ApplicationArguments 对象,提供了更丰富的方法来访问应用程序的参数,包括非命令行参数。

在配置类里注入这两个Bean
@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 {
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());
}
};
}
}getOptionNames() 方法返回一个包含所有命令行选项名称的集合。命令行选项通常以 – 或 — 开头,如 -Dkey=value 或 –server.port=8080
getOptionValues(“server.port”) 方法返回指定选项名称的值列表。
getNonOptionArgs() 方法返回一个包含所有非选项参数的列表。非选项参数是那些不带 – 或 — 前缀的命令行参数。
main方法逻辑就是拿到这两个Bean然后调用他们的run方法
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);
}执行加入参数:–server.port=8080 debug

3.Boot 启动过程
1.记录 BeanDefinition 源
2.推断应用类型
3.记录 ApplicationContext 初始化器
4.记录监听器
5.推断主启动类
(2)执行 run 方法
这里打开run源码
1.创建事件发布器SpringApplicationRunListeners,虽然叫Listeners,但是实际是事件发布器。

然后发布 application starting 事件

2.封装启动 args

3.准备 Environment 添加命令行参数
在prepareEnvironment方法

第三步在prepareEnvironment方法里面

4.ConfigurationPropertySources 处理

5.通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理
- application.properties,由 StandardConfigDataLocationResolver 解析
发布 application environment 已准备事件

6.绑定 spring.main 到 SpringApplication 对象

7.打印 banner
回到run方法

8.创建容器

9.准备容器
逻辑在prepareContext方法中

applyInitializers(context)就对应第九步

然后发布 application prepared 事件

10.加载 bean 定义

同时发布 application context 已初始化事件

11.refresh 容器
回到run方法

在里面会调用容器的refresh方法做处理各种Bean工厂处理器等等操作


回到run方法
同时发布 application started 事件

12.执行 runner
执行 runner方法和发布 application running 事件

这其中有异常,发布 application failed 事件

4.补充@Indexed的原理
spring扫描的效率不高,因为他要遍历所有的JAR包,类文件等,将注解解析等等操作
@Indexed 的原理就是在编译时就根据 @Indexed 生成 META-INF/spring.components 文件
扫描时:
- 如果发现 META-INF/spring.components 存在,以它为准加载 bean definition
- 否则,会遍历包下所有 class 资源 (包括 jar 内的)
用法就是加入依赖即可
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<optional>true</optional>
</dependency>然后Bean配置类加入@Indexed 依赖,不过@Component注解默认加入了。所以不用再加入了

测试一下:
package com.dreams.demo44;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
public class Demo {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 组件扫描的核心类
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
scanner.scan(Demo.class.getPackage().getName());
for (String name : beanFactory.getBeanDefinitionNames()) {
System.out.println(name);
}
}
}可以看到自动生成了 META-INF/spring.components 文件
scanner.scan方法默认就是先找这个文件

5.参考
B站黑马程序员视频:spring原理



