主要内容
1. 启动原理
2. 运行流程 ****
其中有几个重要的事件回调机制
我们在研究运行流程的时候遇见这几个接口,要核心的关注一下。
ApplicationContextInitializer.
在容器刷新之前调用该类的 initialize 方法。
并将 ConfigurableApplicationContext 类的实例传递给该方法
SpringApplicationRunListener
ApplicationRunner
CommandLineRunner
3. 自动配置原理
4. 自定义启动器
1. 启动原理分析:源码版本 1.5.10
注意:由于2.3.1版本在这一块的源码和视频中差别较大,我将<parent/>的版本换成了1.5.10
1. 创建一个简单的SpringBoot项目:只导入了web引用模块
2. 在主方法入口的run()方法处打断点 Debug启动
SpringApplication.run(Day0704Springboot09Application.class, args);
传入主配置类(标注了@SpringBootApplication的类)和命令行参数
3. 进入run()方法
其中这一段代码:return (new SpringApplication(sources)).run(args);
先new SpringApplication(sources)
再调用run()方法
4. 所以SpringBoot应用启动相当于分为两步:
注意
1. 先创建SpringApplication对象
2. 再运行run()方法
2 new SpringApplication(sources) :创建SpringApplication对象的过程
分析一下创建SpringApplication对象做了什么
1. 进入new SpringApplication(sources)
进到构造方法中,详细看看做了什么
给SpringApplication对象中的属性都赋上一些默认值
2. 方法的最后一步调用了initialize()方法。
点进去看一下:
this.initialize(sources);
3. 也就是initialize()方法创建了SpringApplication对象
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = this.deduceWebEnvironment();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
1. 首先判断sources是否为空:
sources是主配置类:com.athuigu.springboot.Day0704Springboot09Application
不为空就存到Object[] sources
this.sources.addAll(Arrays.asList(sources))
2. 判断当前应用是否是web应用:是(我导入了web模块)
this.webEnvironment = this.deduceWebEnvironment()
3.1 点进setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))
方法
setInitializers()方法是为initializers赋值
3.2 我们分析一下
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
getSpringFactoriesInstances(ApplicationContextInitializer.class)
其中的参数:getSpringFactoriesInstances(ApplicationContextInitializer.class)
意思是得到ApplicationContextInitializer,从哪找呢?
点进去看:
再点进去getSpringFactoriesInstances(type, new Class[0])
再点进去loadFactoryNames(type, classLoader)
3.3 setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))
从类路径下的找到META-INF/spring.factories里面配置的所有ApplicationContextInitializer
然后保存起来
3.4 在相应目录下找到了ApplicationContextInitializer
3.5 看一下Initializers现在保存的值
4. initialize(Object[] sources)方法的第四步
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
这就很明显了,也是从类路径下的找到META-INF/spring.factories 里面配置的所有
ApplicationListener。
4.1 这就是Listeners找到后保存起来的值
5. initialize(Object[] sources)方法的第五步
his.mainApplicationClass = this.deduceMainApplicationClass();
决定哪一个ApplicationClass是一个主程序
5.1 点进去
来判断我们传进来的配置类那个里面有main方法,那个就是主程序。
注意:SpringApplication.run(Day0704Springboot09Application.class, args);
run()方法的参数一,有另一种重载形式:
参数一:配置类(@SpringBootApplication)可以传多个。
所以在5.1 才会判断哪一个ApplicationClass个主程序
4. initialize(Object[] sources)
执行完了,就得到一个SpringApplication对象。
即Spring应用对象创建完了。
3 运行SpringBoot --- 分析 run(args)
点进run(args)方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
new FailureAnalyzers(context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
listeners.finished(context, (Throwable)null);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
throw new IllegalStateException(var9);
}
}
这里面就包含了SpringBoot项目:SpringApplcation对象运行的整个流程。
我们来看一下关键的步骤
1. 开始启动监听
StopWatch stopWatch = new StopWatch();
stopWatch.start();
2. 声明一个IOC容器
ConfigurableApplicationContext context = null;
3. FailureAnalyzers analyzers = null
声明一个FailureAnalyzers
4. this.configureHeadlessProperty();
点进来,是和做awt应用有关的
private void configureHeadlessProperty() {
System.setProperty("java.awt.headless", System.getProperty("java.awt.headless", Boolean.toString(this.headless)));
}
5. 关键步骤:SpringApplicationRunListeners listeners = this.getRunListeners(args);
获取SpringApplicationRunListeners
5.1 从哪获取呢,我们点进去看一下
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
return new SpringApplicationRunListeners(logger,
this.getSpringFactoriesInstances(SpringApplicationRunListener.class,
types, this, args));
}
发现了熟悉的getSpringFactoriesInstances:
也是类路径下的找到META-INF/spring.factories里面配置的所有SpringApplicationRunListener
6. listeners.starting():
回调所有的SpringApplicationRunListener的start方法
点进去发现它将每个SpringApplicationRunListener的start方法都调用了一遍
6.1 我们进去SpringApplicationRunListener类中看一下:
其中相当于有5个回调机制
我们现在先是来回调所有的starting()方法
7. 将args用ApplicationArguments封装一下
封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
8. ConfigurableEnvironment environment =
this.prepareEnvironment(listeners, applicationArguments);
准备环境
8.1 点进来看一下
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
//创建环境
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
//配置环境
this.configureEnvironment((ConfigurableEnvironment)environment,
applicationArguments.getSourceArgs());
//回调了SpringApplicationRunListeners的environmentPrepared()方法
listeners.environmentPrepared((ConfigurableEnvironment)environment);
//判断是如果是web环境还能转成web环境
if (!this.webEnvironment) {
environment = (new EnvironmentConverter(this.getClassLoader())).convertToStandardEnvironmentIfNecessary((ConfigurableEnvironment)environment);
}
return (ConfigurableEnvironment)environment;
}
8.2 准备好环境后,回调SpringApplicationRunListeners的environmentPrepared()方法
意思是:环境准备完成
8.3 判断是如果是web环境还能转成web环境,返回环境。
环境就创建好了。
9. Banner printedBanner = this.printBanner(environment);
打印Banner图标
10. 关键的一步:context = this.createApplicationContext();
创建IOC容器
10.1判断是创建一个webIOC容器还是普通的IOC容器
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
contextClass = Class.forName(this.webEnvironment ?
"org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext"
: "org.springframework.context.annotation.AnnotationConfigApplicationContext");
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
return (ConfigurableApplicationContext)BeanUtils.instantiate(contextClass);
}
10.2由于当前项目是web项目,得到一个webIOC容器
10.3return (ConfigurableApplicationContext)BeanUtils.instantiate(contextClass)
判断之后由反射得到一个IOC容器
11 new FailureAnalyzers(context);
做异常分析报告的
12. this.prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
准备上下文
传入IOC容器,准备好的环境,listener,封装的args参数
点进去prepareContext()方法
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//将environment保存到IOC中
context.setEnvironment(environment);
this.postProcessApplicationContext(context);
//关键点
this.applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
this.logStartupInfo(context.getParent() == null);
this.logStartupProfileInfo(context);
}
context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
Set<Object> sources = this.getSources();
Assert.notEmpty(sources, "Sources must not be empty");
this.load(context, sources.toArray(new Object[sources.size()]));
listeners.contextLoaded(context);
}
12.1关键点
this.applyInitializers(context);
12.1.1 点进去发现
获取了所有的getInitializers()
并调用了initialize()方法,initializer.initialize(context)
这些initializer来自哪里呢?
就是创建SpringApplication时,从类路径下的找到META-INF/spring.factories里面配置的所有
ApplicationContextInitializer然后保存起来。
setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))
所以在这一步回调之前保存的所有的ApplicationContextInitializer的initialize(context)
方法
也就是说在IOC容器将环境准备设置好以后,他就来调用这些初始化器ApplicationContextInitializer
的initialize(context)方法
12.2listeners.contextPrepared(context);
12.2.1 点进去发现
获取了所有的SpringApplicationRunListener(在第5步得到保存的)
并且回调了contextPrepared(context)方法
所有的监听器回调完。
12.3 来到日志相关
if (this.logStartupInfo) {
this.logStartupInfo(context.getParent() == null);
this.logStartupProfileInfo(context);
}
12.4 向IOC容器中注册命令行参数args和打印的banner printedBanner
context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
12.5 Set<Object> sources = this.getSources();
拿到我们的主类。
即@@SpringBootApplication标注的类
12.6 一些非关键步骤
Assert.notEmpty(sources, "Sources must not be empty");
this.load(context, sources.toArray(new Object[sources.size()]));
12.7 listeners.contextLoaded(context);
当prepareContext()方法走到最后一步时,所有的SpringApplicationRunListeners
回调contextLoaded(context)方法
12.8 prepareContext()执行完了控制台把相关的环境都准备好了
13. prepareContext()执行完了来到refreshContext(context)
刷新容器:IOC容器初始化,加载其中所有的组件。
如果是web程序会加载一个嵌入式的tomcat组件
14. this.afterRefresh(context, applicationArguments);
14.1点进afterRefresh()看看
从IOC容器中获取所有的ApplicationRunner和CommandLineRunner
再进行回调
先回调ApplicationRunner
再回调CommandLineRunner
14.2 afterRefresh()相当于回调这两个runner
15. listeners.finished(context, (Throwable)null);
所有的SpringApplicationRunListener回调finished()方法
到这一步整个SpringBoot应用就启动完了。
16. stopWatch.stop();
保存当前应用状态,应用已经启动完成了
17. 返回IOC容器
return context;
4. 小结
1. 这几个接口:事件监听机制
1. 要在META-INF/spring.factories文件中进行配置
ApplicationContextInitializer.
SpringApplicationRunListener
2. 只需要放在IOC容器中即可。
ApplicationRunner
CommandLineRunner
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
//获取SpringApplicationRunListeners;从类路径下META-INF/spring.factories
SpringApplicationRunListeners listeners = getRunListeners(args);
---------------------------------------------------------------------------------------
//回调所有的获取SpringApplicationRunListener.starting()方法
listeners.starting();
try {
//封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
---------------------------------------------------------------------------------------
//创建环境完成后回调SpringApplicationRunListener.environmentPrepared();表示环境准备完成
Banner printedBanner = printBanner(environment);
//创建ApplicationContext;决定创建web的ioc还是普通的ioc
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
//准备上下文环境;将environment保存到ioc中;而且applyInitializers();
//applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法
---------------------------------------------------------------------------------------
//回调所有的SpringApplicationRunListener的contextPrepared();
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
//s刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat);Spring注解版
//扫描,创建,加载所有组件的地方;(配置类,组件,自动配置)
refreshContext(context);
---------------------------------------------------------------------------------------
//从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调
//ApplicationRunner先回调,CommandLineRunner再回调
afterRefresh(context, applicationArguments);
---------------------------------------------------------------------------------------
//所有的SpringApplicationRunListener回调finished方法
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//整个SpringBoot应用启动完成以后返回启动的ioc容器;
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
2. 注意这些事件的回调
这些事件都是接口。
想要干预运行SpringBoot --- run(args)的哪个阶段,就用实现相应的监听回调器就行了。
3. 注意IOC容器的刷新refreshContext(context);
扫描,创建,加载所有组件的地方;
我们自己写的配置类,组件,自动配置就是在这里加载的。
5. 事件监听机制
1. 这几个接口:事件监听机制
1. 要在META-INF/spring.factories文件中进行配置
ApplicationContextInitializer.
SpringApplicationRunListener
2. 只需要放在IOC容器中即可。
ApplicationRunner
CommandLineRunner
2. 我的疑问:
当我自己写一个HelloApplicationContextInitializer实现了ApplicationContextInitializer
后。我只在里面写了一些及其简单的输出语句。
那么当回调相应的监听事件方法时,不就只会执行我得实现类中的方法了吗?
这点输出语句根本无法满足启动一个SpringBoot程序的!
3. 解惑:
因为ApplicationContextInitializer,SpringApplicationRunListener要在
META-INF/spring.factories文件中进行配置,这个文件在类路径下。
那么可以有这几种情况:
jar包中的
当前项目resource下的
而且每个spring.factories都可以配置多个相关的监听实现类
而这些配置在类路径下的监听器,都会被加载访问。
5.1 事件监听机制相关测试
1. ApplicationContextInitializer
在容器刷新之前调用该类的 initialize 方法。
并将 ConfigurableApplicationContext 类的实例传递给该方法
注意泛型:<ConfigurableApplicationContext>监听IOC容器
//要在META-INF/spring.factories文件中进行配置
public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("ApplicationContextInitializer...运行了...");
}
}
2. SpringApplicationRunListener
//要在META-INF/spring.factories文件中进行配置
public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {
//必须要写一个有参构造
public HelloSpringApplicationRunListener(SpringApplication application, String[] args) {
}
@Override
public void starting() {
System.out.println("IOC还没初始化,获取到了所有的SpringApplicationRunListener,...开始starting");
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
System.out.println("SpringApplicationRunListener...容器环境准备好了"+environment.getSystemProperties().get("os.name"));
}
@Override
public void contextPrepared(ConfigurableApplicationContext configurableApplicationContext) {
System.out.println("SpringApplicationRunListener...IOC容器准备好了");
}
@Override
public void contextLoaded(ConfigurableApplicationContext configurableApplicationContext) {
System.out.println("容器环境加载完成....");
}
@Override
public void finished(ConfigurableApplicationContext configurableApplicationContext, Throwable throwable) {
System.out.println("SpringBoot应用完整启动,之后要返回IOC容器了...");
}
}
3. ApplicationRunner
@Component //ApplicationRunner这个事件监听机制需要放进容器中
public class HelloApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationArguments...run...");
}
}
4. CommandLineRunner
@Component //CommandLineRunner这个事件监听机制需要放进容器中
public class HelloCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... strings) throws Exception {
System.out.println("CommandLineRunner....run...."+ Arrays.asList());
}
}
5. ApplicationContextInitializer,SpringApplicationRunListener要在
META-INF/spring.factories文件中进行配置
org.springframework.context.ApplicationContextInitializer=\
com.athuigu.springboot.listener.HelloApplicationContextInitializer
org.springframework.boot.SpringApplicationRunListener=\
com.athuigu.springboot.listener.HelloSpringApplicationRunListener
6. 测试
6. SpringBoot自定义starters
6.1 概述
SpringBoot最强大的一个特点,它将许多的场景都抽取成了一个个的starters(场景启动器)。
我们通过引入SpringBoot为我们提供的这些众多的场景启动器,就能使用到相应的功能。
而且引入了这些场景启动器后,SpringBoot也为我们自动配置好了相应的的配置。我们只需要
在进行少量的定制配置即可满足我们的需求。
当然SpringBoot也无法考虑到所有的场景和情况,所以这时就需要我们自定义一个场景启动器。
来简化我们对SpringBoot的使用。
当我们写好一个场景的starters时,别的开发人员可以直接引用我们这个starters。
如何自定义starters?我们需要明确以下:
1. 这个场景需要用到的依赖是哪些?
2. 有了这些依赖后,我们需要自动配置些什么?
编写自动配置。
6.2 自动配置类的编写规则
@Configuration //指定这个类是一个配置类
@ConditionalOnXXX //在指定条件成立的情况下自动配置类生效
@AutoConfigureAfter //指定自动配置类的顺序
@Bean //给容器中添加组件
@ConfigurationPropertie结合相关xxxProperties类来绑定相关的配置
@EnableConfigurationProperties //让xxxProperties生效加入到容器中
自动配置类要能加载
将需要启动就加载的自动配置类,配置在META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
6.3 看一些SpringBoot是怎么编写starters的
1. 启动器(starter)模块
启动器模块是一个空JAR 文件,仅提供辅助性依赖管理,再专门写一个自动配置模块。
启动器依赖自动配置。
xxxx-starter
2. 自动配置模块
别人只需要引入启动器,就相当于也引入了自动配置模块
xxxx-starter-autoconfigurer
3. 命名推荐:后缀
–模式:自定义启动器名-spring-boot-starter
–举例:mybatis-spring-boot-starter
6.4. 写一个自定义启动器
1. 创建一个空的工程
spring-boot-10-starter
2. 创建好后要添加module --- 启动器(做依赖引入)
atguigu-spring-boot-starter
Maven module
3. 再创建一个module --- 自动配置相关
atguigu-spring-boot-starter-autoconfigurer
不引入模块
4. 在启动器pom中引入自定义的自动配置模块
<dependencies>
<dependency>
<!--引入自定义的自动配置模块-->
<groupId>com.atguigu.starter</groupId>
<artifactId>atguigu-springboot-starter-autoconfigurer</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
5. 删掉 自动配置类中的这些
appication.properties
主配置类
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
6. 只引入一个spring-boot-starter
7. 删掉test
6.5 编写自定义的自动配置类
1. HelloService
用到了HelloProperties中的一些属性
public class HelloService {
@Autowired
HelloProperties helloProperties;
public HelloProperties getHelloProperties() {
return helloProperties;
}
public void setHelloProperties(HelloProperties helloProperties) {
this.helloProperties = helloProperties;
}
public String sayHelloAtguigu(String name){
return helloProperties.getPrefix()+"-"+name+helloProperties.getSuffix();
}
}
2. HelloProperties
/**
* 把所有能配置的属性都绑在这里。
*/
@ConfigurationProperties(prefix = "atguigu.hello")
public class HelloProperties {
private String prefix;
private String suffix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
3. 自动配置类 HelloServiceAutoConfiguration
注意我这里加了个条件:@ConditionalOnWebApplication //Web应用才生效
@Configuration
@ConditionalOnWebApplication //Web应用才生效
@EnableConfigurationProperties(HelloProperties.class) //属性文件生效,可以直接在这个类中使用
public class HelloServiceAutoConfiguration {
//自动配置类能生效:要在spring.factories中配置
/**
* 这个自动配置向IOC中添加了HelloService组件
* HelloService用到的所有属性和HelloProperties绑定的。
*/
@Autowired
HelloProperties helloProperties;
@Bean //向容器中加了一个HelloService,别人想用就能用了
public HelloService helloService(){
HelloService service = new HelloService();
service.setHelloProperties(helloProperties);
return service;
}
}
4. 自动配置类能生效:要在spring.factories中配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.atguigu.springboot.HelloServiceAutoConfiguration
5. atguigu-spring-boot-starter依赖了atguigu-springboot-starter-autoconfigurer
所以安装atguigu-springboot-starter-autoconfigurer到仓库
启动器atguigu-spring-boot-starter安装到仓库中
6. 创建一个新的项目测试
引入自定义的stater坐标
<groupId>org.atguigu</groupId>
<artifactId>atguigu-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>