1. 简介
不知道大家第一次搭SpringBoot环境的时候,有没有觉得非常简单。无须各种的配置文件,无须各种繁杂的pom坐标, 一个main方法,就能run起来了。与其他框架整合也贼方便,使用EnableXXXXX注解就可以搞起来了!
2. 原理
2.1 总体流程图
在看源码之前,我们不妨先想一下,如果是我们自己实现这个功能,要怎么实现呢?
- 从META-INF.spring.factories读取key为EnableAutoConfiguration的value值,放入一个数据结构中
- 从这个数据结构里按需加载这些类
- 将这些类注册为一个一个的Bean
- 最终由Spring管理使用
2.2 源码阅读
2.2.1 入口
首先从@SpringBootApplication注解开始看。
可以看到@EnableAutoConfiguration这个注解,从名字可以猜测,这个注解就是开启自动装配的注解。
进入这个注解可以看到@Import了AutoConfigurationImportSelector这个类。
所以这个类就是我们源码阅读的入口类。
2.2.2 DeferredImportSelector
从代码中可以看到该类实现了DeferredImportSelector,该类可以和@Conditional组合使用。 可以根据Conditional的条件来选择是否开启某功能或加载某个Bean。
比如:看如下的AopAutoConfiguration类,是SpringBoot里AOP的Bean,该类里边有这么一个静态类:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = false)
static class JdkDynamicAutoProxyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class CglibAutoProxyConfiguration {
}
}
表示当系统里有Advice类时,才会开启动态代理配置,而且默认是CGLIB的代理。随便创建一个SpringBoot的启动项目,在配置文件中加入"debug = true"这一行,启动项目。看到有如下输出:
AopAutoConfiguration.ClassProxyingConfiguration matched:
- @ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition)
- @ConditionalOnProperty (spring.aop.proxy-target-class=true) matched (OnPropertyCondition)
说明系统中没有Advice类,无须动态代理。
DeferredImportSelector该类还有另外两个功能,一个是分组,一个是排序。具体应用是这样,有的时候我们可能会自己定制一个Bean,我们希望自己定义的Bean优先加载,但是由于这是一个Bean,无论如何都是由Spring IOC容器管理的,所以可以让自己定义的Bean实现DeferredImportSelector,将我们自己的类进行分组,区别于Spring的Bean,并把我们自定义的类先行注册、加载,这样Spring再进行管理Bean的时候,发现系统中已经有我们自己定义的Bean了,就不会对Bean进行注册加载了。
2.2.3 ConfigurationClassParser类
入口虽然是AutoConfigurationImportSelector类,但是具体的调用链,请看下图:
ConfigurationClassParser.getImports()方法里,可以看到会先调用系统中所有DeferredImportSelector实现类的process方法,然后再调用selectImports方法。
public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}
2.2.4 AutoConfigurationImportSelector
2.2.4.1 process()方法
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
//重点方法
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
2.2.4.2 getAutoConfigurationEntry()方法
该方法的作用是获取所有有效的自动装配类。
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
//
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//从所有的jar包中和类路径中读取META-INF/spring.factories文件,获取所有自动装配类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//删除重复的自动装配类
configurations = removeDuplicates(configurations);
//获取显示的过滤条件
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//检查过滤条件
checkExcludedClasses(configurations, exclusions);
//根据过滤条件删除类
configurations.removeAll(exclusions);
//从pom文件中读取有效的配置类,并删除无效的配置类
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
下面分析重点方法
2.2.4.3 getCandidateConfigurations()
从META-INF/spring.factories文件中读取key为EnableAutoConfiguration的所有配置类。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
2.2.4.4 selectImports()方法
这个方法就是将自动配置类进行排序,根据@Order、@AutoConfigureBefore、@AutoConfigureAfter这个顺序进行排序。
至此所有流程分析完毕,让我们总结下总体流程。
3. 自定义Starter
3.1 简介
SpringBoot 最强大的功能就是把我们常用的场景抽取成了一个个starter(场景启动器),我们通过引入springboot 为我提供的这些场景启动器,我们再进行少量的配置就能使用相应的功能。即使是这样,springboot也不能囊括我们所有 的使用场景,往往我们需要自定义starter,来简化我们对springboot的使用。比如笔者就在项目中写过akka的starter。
3.2 如何自定义
- 创建父项目:springboot_custome_starter
- 创建子项目:zyz-spring-boot-starter
- 创建子项目:zyz-spring-boot-starter-autoconfigurer
- zyz-spring-boot-starter项目依赖zyz-spring-boot-starter-autoconfigurer
- 在zyz-spring-boot-starter-autoconfigurer里创建具体逻辑
- 将zyz-spring-boot-starter-autoconfigurer项目打成jar包,并上传至本地Maven私有库
- 新建测试项目运行
3.3 代码实现
3.3.1 新建父工程
springboot_custome_starter
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<modules>
<module>zyz-spring-boot-starter</module>
<module>zyz-spring-boot-starter-autoconfigurer</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>springboot_custome_starter</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
3.3.2 新建子项目zyz-spring-boot-starter
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot_custome_starter</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>zyz-spring-boot-starter</artifactId>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>zyz-spring-boot-starter-autoconfigurer</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
3.3.3 新建子项目zyz-spring-boot-starter-autoconfigurer
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot_custome_starter</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>zyz-spring-boot-starter-autoconfigurer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
package com.starter.zyz;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnProperty(value = "zyz.hello.name")
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfitguration {
@Autowired
HelloProperties helloProperties;
@Bean
public IndexController indexController(){
return new IndexController(helloProperties);
}
}
package com.starter.zyz;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("zyz.hello")
public class HelloProperties {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.starter.zyz;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class IndexController {
HelloProperties helloProperties;
public IndexController(HelloProperties helloProperties) {
this.helloProperties = helloProperties;
}
@RequestMapping("/")
public String index(){
return helloProperties.getName()+"欢迎你";
}
}
resources目录中新建META-INF目录,并在该目录下新建文件spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.starter.zyz.HelloAutoConfitguration
3.3.4 上传至本地私有库
分别将两个子项目打成Jar包,并把zyz-spring-boot-starter项目上传至本地Maven仓库
install:install-file -Dfile=<Jar包的地址>
-DgroupId=<Jar包的GroupId>
-DartifactId=<Jar包的引用名称>
-Dversion=<Jar包的版本>
-Dpackaging=<Jar的打包方式>
3.3.5 新建测试项目运行
<?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.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>zyz-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
启动类
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
启动项目,在浏览器里输入:http://localhost:8080
输出为:
"zyz"欢迎你
成功