76-java-springboot(2)-启动原理分析及自定义starter
本文主要分析springboot启动原理分析及自定义starter
一.springboot原理三问
1.首先我们springBoot加载自己编写的bean是怎么加载的?
思考: 加载bean到容器,无外乎,递归扫描基础包,找到相应的注解修饰的类,然后放入容器.所以我们需要指明基础包.
实现: 通过这个注解@AutoConfigurationPackage 获得当前主启动类的包,并且以这个包为基础包
2.其次我们导入其他starter项目的时候,他怎么帮我注入bean到容器的?
思考1: 上面的方式只能将我们自己项目中的类放入容器,怎么将自动将引入的依赖的某些类放入容器呢?
-
修改其他jar包,也将他们的类加上相应的注解?----这个不可能
-
增加一个适配的中间依赖,在依赖中专门规定一个文件,来指定我们需要注入什么类到spring的容器,好像可以
实现1: 统一扫描所有的jar包中的META-INF/spring.factories文件,读取org.springframework.boot.autoconfigure.EnableAutoConfiguration的值,将值的所有类注入容器
aaa@qq.com(AutoConfigurationImportSelector.class) 导入AutoConfigurationImportSelector这个类,而这个类的方法就定义我们扫描所有jar包的指定文件,并实例化对象到容器
思考2: 我们有些类不能直接就实例化到spring容器中,不然可能会报错,但是我们又想自动注入?
实现2: 于是我们以@Conditional注解为基础扩展了其他的注解,可以实现很多中条件下,我们才让本配置类生效.
于是产生了各种的@ConditionalOnxxx注解,用于控制本配置类的生效
3.他帮我们注入bean到容器,我们有是怎么通过配置文件修改那些bean的属性的?
思考: 上面已经帮我们实现了自动注入,但是我们还不能修改对应的bean的属性?
于是我们想到了,在引入相关类的时候,我们可以定义一个专门的properties类来读取默认配置文件中的值,可以利用注解@ConfigurationProperties(prefix = “spring.batch”)和注解
实现:
- 我们在实例化XXXAutoConfiguration类的时候可以指定读取配置文件中的值aaa@qq.com(BatchProperties.class)启动读取配置文件的值
- BatchProperties.class用@ConfigurationProperties注解标注,读取配置文件的值.
- XXXAutoConfiguration类中,注入BatchProperties,并将其属性值,配到我们注入容器中的bean对象的属性中去.
二.springboot启动原理剖析
1.注解@SpringBootApplication
aaa@qq.com
@Configuration 表示配置类,注入spring容器
aaa@qq.com
(1) @AutoConfigurationPackage
原理是这个注解@Import(AutoConfigurationPackages.Registrar.class)
-
@Import()
#注解用于向容器中注入bean实例 -
AutoConfigurationPackages.Registrar.class
#这个类中的有个方法是用于:获取当前启动类的包,并且将该包作为基础扫描包,将该包下配置相应注解的类全部注入容器
(2) @Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector.class
-
这个类有个process方法:这个方法会调用SpringFactoriesLoader.loadFactoryNames()方法,而这个方法会扫描所有jar包将
类路径下 META-INF/spring.factories
里面配置的所有EnableAutoConfiguration的值加入到了容器中; -
将 类路径下 META-INF/spring.factories
里面配置的所有EnableAutoConfiguration的值加入到了容器中;这里的类的名字都是xxxAutoConfiguration
AutoConfigurationImportSelector.getCandidateConfigurations 加载所有需要注入容器的configuration类
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass()
public static final String FACTORIES_RESOURCE_LOCATION = “META-INF/spring.factories”;
aaa@qq.com(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
#配置组件扫描时排除的拦截器类型
2. SpringApplication.run(本类名.class, args);
这个是springboot项目的入口.
(1)启动流程:
流程答题分为两步:
- new 一个springapplication对象
- 执行run方法
new一个SpringApplication对象
private void initialize(Object[] sources) {
//保存主配置类
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//判断当前是否一个web应用
this.webEnvironment = deduceWebEnvironment();
//从类路径下找到META‐INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起 来
setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));
//从类路径下找到ETA‐INF/spring.factories配置的所有ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//从多个配置类中找到有main方法的主配置类
this.mainApplicationClass = deduceMainApplicationClass();
}
执行run方法
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)几个重要的事件回调机制
-
配置在META-INF/spring.factories
- ApplicationContextInitializer
- SpringApplicationRunListener
-
只需要放在ioc容器中
- ApplicationRunner
- CommandLineRunner
自定义监听:
- 1.ApplicationContextInitializer
package com.atguigu.springboot.listener;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("ApplicationContextInitializer...initialize..."+applicationContext);
}
}
- 2.SpringApplicationRunListener
package com.atguigu.springboot.listener;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {
//必须有的构造器
public HelloSpringApplicationRunListener(SpringApplication application, String[] args){
}
@Override
public void starting() {
System.out.println("SpringApplicationRunListener...starting...");
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
Object o = environment.getSystemProperties().get("os.name");
System.out.println("SpringApplicationRunListener...environmentPrepared.."+o);
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...contextPrepared...");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...contextLoaded...");
}
@Override
public void started(ConfigurableApplicationContext context) {
}
@Override
public void running(ConfigurableApplicationContext context) {
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
}
public void finished(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("SpringApplicationRunListener...finished...");
}
}
- 3.配置(META-INF/spring.factories);将以上两个自定义类配置到该factories中
只需要将以下放在ioc容器中
- 4.ApplicationRunner
package com.atguigu.springboot.listener;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class HelloApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner...run....");
}
}
- 5.CommandLineRunner
package com.atguigu.springboot.listener;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
public class HelloCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner...run..."+ Arrays.asList(args));
}
}
这样我们就可以干预springBoot启动过程了,通过干预这些过程,我们可以做一些自定义配置.
三.自定义starter
1.命名规范及设计概述
1.1 命名规范
-
spring-boot-starter-xxx
#这是spring官方的编写的启动环境项的命名规则. -
xxx-spring-boot-starter
#这个是非spring官方编写的启动环境项的命名规则.
1.2 设计概述
实现步骤:
新建一个空包,并且在该空包中新建一个springboot项目 xxx-spring-boot-autoconfigure,并在该项目中抒写
(0) 修改项目结构
pom文件
#根据实际依赖配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>gl-spring-boot-starter-autoconfiguration</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gl-spring-boot-starter-autoconfiguration</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
项目结构
删除启动类/application文件,以及test包
(1) HelloProperties.class 配置文件读取类
作用:读取配置文件中的值
//读取application文件中的一hello为前缀的属性值
@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
private String name;
private String age;
public String getName() {
return name;
}
public HelloProperties setName(String name) {
this.name = name;
return this;
}
public String getAge() {
return age;
}
public HelloProperties setAge(String age) {
this.age = age;
return this;
}
}
(2) hello.class
作用:这个是业务类,也就是我们真正需要的类.一般是我们其他jar包中的类,所以是自己编写的.
public class Hello {
private HelloProperties helloProperties;
public HelloProperties getHelloProperties() {
return helloProperties;
}
public Hello setHelloProperties(HelloProperties helloProperties) {
this.helloProperties = helloProperties;
return this;
}
public String sayHello(){
return "hello:" + helloProperties.getName() + ";年龄:" + helloProperties.getAge();
}
}
(3) HelloAutoConfiguration.class
作用:这个是真正的将hello(业务类)组装属性,注入容器的类
@ConfigurationProperties
@ConditionalOnWebApplication
//启动helloProperties.class类,并注入容器.
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
@Autowired
private HelloProperties helloProperties;
@Bean
public Hello getHello(){
Hello hello = new Hello();
hello.setHelloProperties(helloProperties);
return hello;
}
}
(4) META-INF/spring.factories
将HelloAutoConfiguration编写到factories文件中
在此包中新建一个maven module : xxx-spring-boot-starter
(5)修改项目结构(maven项目)
pom文件
#引入刚刚写的那个项目 xxx-spring-boot-autoconfigure
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>gl-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gl-spring-boot-starter</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>gl-spring-boot-starter-autoconfiguration</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
项目结构
(6) install安装
先安装AutoConfigure项目
再安装starter项目
(7) 测试
重新,新建一个测试项目引入starter依赖
pom文件
application.yml
Controller
@RestController
public class HelloController {
@Autowired //此hello是我们自定义的starter注入容器中的
private Hello hello;
@GetMapping("/hello")
public String hello(){
String s = hello.sayHello();
return s;
}
}
启动容器,访问测试即可.
下一篇: 用springboot创建ssm工程