目录
写在前面
spring的自动配置,帮我们预置了一些常用对象,如servlet,Filter等。
通过自动配置,将这些对象加入到IOC容器中。实现自自动配置的核心是:
-
核心注解@import
-
核心接口 DeferredImportSelector(继承了ImportSelector接口)
-
核心类AutoConfigurationImportSelector(实现了DeferredImportSelector接口)
-
核心文件META-INF/spring.factories
-
核心代码
// 读取所有calsspath下的META-INF/spring.factories文件 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); return configurations; }
声明一点:实现了DeferredImportSelector接口的类,一般都会执行ImportSelector#selectImports方法,但是AutoConfigurationImportSelector却没有执行selectImports方法。
关于@import注解
import可以把bean加入到IOC容器中。写个demo看下现象。
-
import导入普通类
@SpringBootApplication
@Import(value = {Dept.class})
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Main.class);
// System.out.println(context.getBean(User.class));
System.out.println(Dept.class);
}
}
public class Dept {
}
输出结果
class com.demo.model.Dept
-
import导入实现了ImportSelector接口的类
@SpringBootApplication
@Import(value = {Dept.class, MyDeferredImportSelector.class})
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Main.class);
System.out.println(Dog.class);
}
}
package com.demo.importSelecotr;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyDeferredImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.demo.model.Dog"};
}
}
package com.demo.model;
public class Dog {
}
输出结果
class com.demo.model.Dog
-
import导入实现了ImportSelector接口的类2 ( ImportSelector的selectImports方法导入的是配置类)
// 排除需要扫描的类UserAutoConfig。 使用import导入
@SpringBootApplication(exclude={com.demo.autoConfig.UserAutoConfig.class})
@Import(value = {Dept.class, MyDeferredImportSelector.class, MyDeferredImportSelector2.class})
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Main.class);
System.out.println(context.getBean("defaultUser"));
}
}
public class MyDeferredImportSelector2 implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// UserAutoConfig 是个配置类,该类有@Configuration注解。(springboot自动配置类均有该注解)
return new String[]{"com.demo.autoConfig.UserAutoConfig"};
}
}
@Configuration
// DefaultUserConfig 属性类,和application.yml中配置对应
@EnableConfigurationProperties(DefaultUserProperties.class)
@ConditionalOnClass(User.class)
public class UserAutoConfig {
@Bean(name = "defaultUser")
@ConditionalOnMissingBean(User.class)
public User initDefaultUser(DefaultUserProperties config) {
User user = new User();
user.setName(config.getConfigName());
user.setPassword(config.getConfigPassword());
return user;
}
}
@Data
@NoArgsConstructor
@ConfigurationProperties(prefix="user")
public class DefaultUserProperties {
private String configName;
private String configPassword;
}
application.yml
user:
configName: admin
configPassword: 111111
输出结果
User(name=admin, password=111111)
小结
我们通过import注解导入了实现DeferredImportSelector接口的类。依赖于ImportSelector#selectImports方法可以将bean加入到IOC容器中。
猜想1:如果在ImportSelector接口的selectImports方法中,读取我们的配置文件META-INF/spring.factories是否就可以实现自动配置?
验证猜想1
参考spirngboot定义的spring.factories文件,定义自己的spring.factories。
参考AutoConfigurationImportSelector的getCandidateConfigurations方法,使用SpringFactoriesLoader.loadFactoryNames方法读取META-INF\spring.factories。
// 排除需要扫描的类UserAutoConfig。 使用import导入
@SpringBootApplication(exclude={com.demo.autoConfig.UserAutoConfig.class})
@Import(value = {MyDeferredImportSelector3.class})
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Main.class);
System.out.println(context.getBean("defaultUser"));
}
}
public class MyDeferredImportSelector3 implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> configurations =
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,ClassLoader.getSystemClassLoader());
boolean contains = configurations.contains("com.demo.autoConfig.UserAutoConfig");
if (contains) {
System.out.println("MyDeferredImportSelector3 --->com.demo.autoConfig.UserAutoConfig");
return new String[]{"com.demo.autoConfig.UserAutoConfig"};
}
return new String[0];
}
}
resources\META-INF\spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.demo.autoConfig.UserAutoConfig
输入结果
MyDeferredImportSelector3 --->com.demo.autoConfig.UserAutoConfig
......
User(name=admin, password=111111)
小结:通过读取文件中配置信息,也可以将bean加入到IOC容器中。
猜想2:springboot实现自动配置是调用了DeferredImportSelector接口的selectImports方法吗?(不是)
验证猜想2
打断点调试发现没有调用AutoConfigurationImportSelector#selectImports方法,很是诧异
class: org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
源码调试
通过打断点调试 发现getCandidateConfigurations调用栈为
org.springframework.boot.autoconfigure.
AutoConfigurationImportSelector#getCandidateConfigurations
org.springframework.boot.autoconfigure.
AutoConfigurationImportSelector#getAutoConfigurationEntry
org.springframework.boot.autoconfigure.
AutoConfigurationImportSelector.AutoConfigurationGroup#process
org.springframework.context.annotation.ConfigurationClassParser.
DeferredImportSelectorGrouping#getImports
org.springframework.context.annotation.ConfigurationClassParser.
DeferredImportSelectorGroupingHandler#processGroupImports
class :org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGroupingHandler
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
Predicate<String> exclusionFilter = grouping.getCandidateFilter();
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
// 该 entry包含了spring.factories中的配置类信息
// 也包含从一般的DeferredImportSelector.selectImports返回的calss信息
// 将entry交给processImports处理,进一步注册到BeanDefinitionMap中,为bean创建做准备。
processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
exclusionFilter, false);
}
catch (BeanDefinitionStoreException ex) {}
catch (Throwable ex) {}
});
}
}
class: org.springframework.context.annotation.**ConfigurationClassParser.DeferredImportSelectorGrouping#getImports
/**
* Return the imports defined by the group.
*/
public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
// group做自己的处理
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
// 处理完后,返回统一数据类型
return this.group.selectImports();
}
this.group.process(),是通过group对象调用process方法的。group接口实现类:
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process
org.springframework.context.annotation.ConfigurationClassParser.DefaultDeferredImportSelectorGroup#process
AutoConfigurationImportSelector 所数组为AutoConfigurationGroup
一般的DeferredImportSelector类所属组为DefaultDeferredImportSelectorGroup
AutoConfigurationImportSelector 走的是AutoConfigurationGroup#process,该方法中并没有调用selectImports方法。
class: org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process
filed: Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
// 获取spring.factories中配置key=org.springframework.boot.autoconfigure.EnableAutoConfiguration=\的值
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
// 将读取的配置信息加入到entries中
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
@Override
public Iterable<Entry> selectImports() {
.......
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
一般的DeferredImportSelector类走的是DefaultDeferredImportSelectorGroup#process,该方法中并有调用了selectImports方法。
class: org.springframework.context.annotation.ConfigurationClassParser.DefaultDeferredImportSelectorGroup
filed : private final List<Entry> imports = new ArrayList<>();
@Override
public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
// 调用了selectImports方法
for (String importClassName : selector.selectImports(metadata)) {
this.imports.add(new Entry(metadata, importClassName));
}
}
@Override
public Iterable<Entry> selectImports() {
return this.imports;
}
小结:
-
实现了DeferredImportSelector接口的类,并不一定会执行ImportSelector#selectImports方法。
-
如果DeferredImportSelector属于默认组则一定执行;否则需要看group的具体实现。
实现一个自动配置
使用springBoot自动配置的机制,实现一个自动配置。最后可以打成jar,如果需要可以直接依赖。
创建步骤:
-
calsspath下创建 META-INF\spring.factories文件
-
写配置类xxxxAutoConfiguration @Configuration
-
写属性类xxxxxProperties @EnableConfigurationProperties
项目的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 http://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.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.demo</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>1.0-SNAPSHOT</version>
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>