SpringBoot 最强大的功能之一就是把我们常用的场景抽取成了一个个starter(场景启动器),可插拔的特性。
spring-starter的官方命名规范:
自定义命名空间
项目结构:
首先创建一个空的maven项目,添加maven依赖:
4.0.0
com.chuan
dataTest-spring-boot-starter
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.2.3.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter
然后定义一个实体类,@ConfigurationProperties为获取配置里的值,在没写配置类之前这个会报错,暂时忽略
@ConfigurationProperties("data.service")
public class DataTest {
private String dataString;
public String getDataString() {
return dataString;
}
public void setDataString(String dataString) {
this.dataString = dataString;
}
}
一个平淡无奇的服务类
package service;
public class DataService {
private DataTest dataTest;
public DataTest getDate() {
return dataTest;
}
public void setDate(DataTest date) {
this.dataTest = date;
}
public String show(){
return dataTest.getDataString();
}
}
最后是配置类,
当@EnableConfigurationProperties
注解应用到你的@Configuration
时, 任何被@ConfigurationProperties
注解的beans将自动被Environment属性配置。 这种风格的配置特别适合与SpringApplication的外部YAML配置进行配合使用。
@Configuration
@EnableConfigurationProperties(DataTest.class)
public class DataConfig {
@Autowired
private DataTest dataTest;
@Bean
public DataService dataService(){
DataService dataService = new DataService();
dataService.setDate(dataTest);
return dataService;
}
}
最重要的一点:创建一个META-INF/spring.factories,并将写好的配置类的路径放在里面。为什么这么做后面解释。
最后打包 package
mvn install:install-file -DgroupId=com.chuan -DartifactId=dataTest-spring-boot-starter -Dversion=1.0.0 -Dpackaging=jar -Dfile=C:\Users\chuan\Desktop\glzxCode\hello\target\dataTest-spring-boot-starter-1.0-SNAPSHOT.jar
再创建一个空的maven项目,修改maven依赖:加入了自定义的starter
4.0.0
com.chuan
test
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.2.3.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-web
com.chuan
dataTest-spring-boot-starter
1.0.0
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-maven-plugin
编写配置文件application.properties
data.service.dataString=2020
编写一个测试类
@RestController
@RequestMapping("/aaa")
public class HelloController {
@Autowired
private DataService dataService;
@GetMapping("/hello")
public String hello(){
return dataService.show();
}
}
运行测试:
测试类启动入口:1.查看@SpringBootApplication注解
@SpringBootApplication
public class HelloApplication {
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class,args);
}
}
里面有三个注解:
@SpringBootConfiguration ==@Configuration
@EnableAutoConfiguration 自动配置
@ComponentScan 扫描
2.进入@EnableAutoConfiguration注解
里面又有两个注解:
@AutoConfigurationPackage 添加该注解的类所在的package 作为 自动配置package 进行管理
@Import({AutoConfigurationImportSelector.class}) 导入一个AutoConfigurationImportSelector实例
3.进入AutoConfigurationImportSelector.class查看方法,里面有selectImports,按道理讲所有实现了ImportSelector接口的实现类都会在启动时执行selectImports方法,而AutoConfigurationImportSelector也实现了该接口的子接口DeferredImportSelector,但通过断点并没有执行该方法,于是乎深入了解一下流程。
具体源码跟踪,为什么没有执行这个https://www.jianshu.com/p/2a04f7028ee6
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
3.5通过断点并没有发现AutoConfigurationImportSelector#selectImports方法被执行,但可以知道调用了第四步的方法。具体实现在
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process
方法里
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);
}
}
4.继续查看getAutoConfigurationEntry方法
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//继续向里面看
List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
//下面是一层层的判重验证等处理
Set exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
5.继续查看getCandidateConfigurations方法
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
6.继续查看loadFactoryNames方法
public static List loadFactoryNames(Class> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
7.继续查看loadSpringFactories方法,在这里就能看到之前装配的内容为什么要放在这个目录。
暂时先简单跟一下源码。