文章目录
前言
首先,自动装配包含了哪些的装配?
- 自动将包下的被打上@Component注解以及其派生注解的类作为Bean注册到IOC容器中
- 自动注册jar包中需要被注册的Bean到IOC容器中
这篇文章将从底层源码分析,SpringBoot是如何做到自动装配Bean的。其中起到关键作用的两大关键注解是
- @EnableAutoConfiguration(关于该注解的分析可以查看 Enable模式手动挡装配Bean)
- @ComponentScan
而以上两大关键注解都元标注了@SpringBootApplication注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// ...
}
我们通常会将被标注该注解的类注册到Spring容器中,之后,就会自动帮我们装配jar中配置的一些需要的Bean了。就像下面这样
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
// 将UserServiceApplication作为Bean注册到Spring中
SpringApplication.run(UserServiceApplication.class, args);
}
}
这样,就开启了自动挡,自动装配Bean到Spring中。
希望读者具有ConfigurationClassPostProcessor配置注解处理类的基础,才能更好的了解自动装配的魔法。
关于ConfigurationClassPostProcessor的分析可以查看 配置注解驱动的处理
自动装配的魔法
开头的分析就给了我们一个入口,也就是@EnableAutoConfiguration、@ComponentScan这两大注解,接下来我们就以这两个注解为入口,分析自动装配的魔法。
@ComponentScan扫描并注册
首先,来一个开胃小菜,这块内容相对比较简单,所以先来分析Bean的扫描并注册的过程。
我们知道,在SpringBoot中我们需要将引导类放在包的外层,然后那些@Service、@Controller等等@Component的派生注解都会被注册到Spring中,作为SpringBean。例如下面这样的结构
那么这是为什么呢?Spring解析配置注解驱动类ConfigurationClassPostProcessor负责@ComponentScan注解的解析工作,在解析时,由如下逻辑进行处理
其实这段逻辑在 配置注解驱动的处理 这篇文章中就已经讲述过,这里只做大致的描述。
// Process any @ComponentScan annotations
// 这里主要拿到@ComponentScan注解的元信息,里面包含注解中的属性值
// 如果是ComponentScans,也会分解成一个个的ComponentScan
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
// 从这里可以看出,这里也会进行@Conditional的条件过滤
// 并且阶段是REGISTER_BEAN,表示要开始注册Bean了
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
// 扫描并注册Bean
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
// 这里还会判断是否需要像配置类解析那样进行解析
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
// 如果需要,则解析之,此方法就是刚开始的方法
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
这里最为关键的是componentScanParser的parse方法,扫描并注册Bean
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
// 主要扫描的类
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
// ...略过一些ComponentScan注解属性的配置
Set<String> basePackages = new LinkedHashSet<>();
// 获取basePackages属性值
// 下面主要针对属性值,获取需要被扫描的包名
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
// 获取需要被注册的Class
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 如果没有配置需要扫描的包,就使用declaringClass的包名
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
// ...
// 扫描并注册
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
如果没有配置包名,将扫描默认包
// 如果没有配置需要扫描的包,就使用declaringClass的包名
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
而这个默认包值declaringClass是多少呢?可以回顾上面,此参数是配置类的全限定类名,对应最开头的例子,就是UserServiceApplication的全限定类名,假设此时获得的类名为com.microservice.original.UserServiceApplication,继续会调用getPackageName方法,拿到包名
public static String getPackageName(String fqClassName) {
Assert.notNull(fqClassName, "Class name must not be null");
// 拿到最后一个.的位置
int lastDotIndex = fqClassName.lastIndexOf(PACKAGE_SEPARATOR);
// 截取,这样包名就有了
return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : "");
}
此时,拿到的包名就是com.microservice.original,看到这里,应该就可以理解,为什么SpringBoot引导类放置的位置这么有讲究了。当然你可以在项目中打上多个@ComponentScan,或者是在注解中的basePackage属性中指定要扫描的包名,这样,扫描的范围就会很大了。
至于扫描并注册的方法scanner.doScan这里不多赘述,此内容是AnnotationConfigApplicationContext注解配置上下文的内容。
@EnableAutoConfiguration自动注册Bean
这里就是我们的重头戏了。此注解是注册jar包里的Bean的关键注解。我们之前讲过的Enable模式,其实质上是利用了@Import注解进行的Bean注册,所以这个注解也不例外,核心就在@Import注解中
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
可以发现,其元标注了Import注解,而该注解的值AutoConfigurationImportSelector即为我们重点关注的对象。而AutoConfigurationImportSelector这个类是DeferredImportSelector的子类,也就是延迟加载的Import,会在普通Bean注册之后才会进行注册,时机是相对比较后面的。由前两篇Enable模式和注解驱动解析的铺垫,我们这里就有一个第一反应,看AutoConfigurationImportSelector的selectImports方法
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// 拿到需要被装配的配置类信息
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
// 这里将需要被装配的配置类全类名字符串数组返回出去,然后会被处理注册到Sring中
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
其方法返回值会被Spring处理,然后注册到IOC容器中去,这一点在上两篇文章都有详细的分析,不做过多的赘述。所以这里我们的关键分析点在于,如何拿到那些jar包中需要被装配的配置类信息呢?看看getAutoConfigurationEntry方法都做了什么
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取需要被装配的类的类名
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 去重,主要将其交给Set集合完成去重工作
configurations = removeDuplicates(configurations);
// 获取需要排除的列表
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
// 排除上面的列表包含的类
configurations.removeAll(exclusions);
// 过滤
configurations = filter(configurations, autoConfigurationMetadata);
// 发布一个自动装配的事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
到这里可以看到,获取候选的Bean列表有以下几个步骤:
- 获取需要被装配的类名
- 排除用户定义的列表
- 过滤掉不需要被装配的Bean
下面,我们就这几个步骤进行详细的分析。
获取候选装配的Bean类名
直接进入getCandidateConfigurations方法,看看是如何获取需要被装配的Bean的类名的
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
// ...
return configurations;
}
其实,这里使用了一种SpringFactory机制去加载,先看看getSpringFactoriesLoaderFactoryClass方法做了什么
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
也就是说,这里调用了SpringFactoriesLoader的loadFactoryNames方法,传的第一个参数是EnableAutoConfiguration这个注解类的全限定类名,第二个参数是一个类加载器,继续深入SpringFactoriesLoader的loadFactoryNames方法看看
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// EnableAutoConfiguration全限定类名
String factoryClassName = factoryClass.getName();
// 先loadSpringFactories获取SpringFactories的信息
// 然后getOrDefault获取EnableAutoConfiguration全限定类名对应的信息
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
这里首先会获取SpringFactories的信息,那么是什么信息呢?接下去看看
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 缓存机制
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 使用classLoader拿到FACTORIES_RESOURCE_LOCATION路径下的资源
// 资源是一个URL对象的形式
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
// 如果资源中有内容
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 将其转换为Properties
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// Properties中key为factoryClassName
String factoryClassName = ((String) entry.getKey()).trim();
// value为一个字符串数组
// 也就是说factoryClassName会对应多个value
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
// 放入缓存
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
那么,FACTORIES_RESOURCE_LOCATION路径是什么呢?
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
原来,这里会拿到classPath下的META-INF下的spring.factories文件,这里我们全局搜索一下这个文件里都有哪些内容
# 截取一小段内容作为示例
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
也就是说,这里会将AutoConfigurationImportFilter、EnableAutoConfiguration作为key,其value作为其对应的value保存下来,这里,我们可以总结一下SpringFactories加载机制
SpringFactories加载机制
注意,这很重要,因为此加载机制贯穿了SpringBoot的很多地方。
在整个项目打包成jar的过程中, resource下的资源会合并在一个文件夹,此时就会被SpringFactories加载机制所读取,上述是会读取META-INF/spring.factories路径的文件,将其保存为一个Map<ClassLoader, Map<String, String>>这样一个数据结构,这就是SpringFactories加载机制。
获取SpringFactories中指定的factory
到这里,就加载好了spring.factories文件的内容,将调用getOrDefault方法,获取指定factoryClassName对应的value,也就是我们上面所说的,这里是EnableAutoConfiguration全类名对应的value。
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
// 获取EnableAutoConfiguration全类名对应的value
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
回顾一下我们上面所说的spring.factories文件中的内容,其中EnableAutoConfiguration全类名对应的value有非常多,也就是说,此value有多少,就会返回多少出去,这里的value就是需要被自动装配的Bean。
看到这里,就明白了自动装配的魔法,其实是将需要被装配的Bean提前定义在spring.factories文件中(需要在META-INF下),然后必须以org.springframework.boot.autoconfigure.EnableAutoConfiguration为key,其value就是自动装配的Bean。
我们可以看到,在SpringBoot的jar包中,就有如下结构
这里,瞬间可以明白,原来自动装配的魔法,都是SpringBoot开发人员提前定义好的,也就是说,在此文件中写好一次,就可以供广大使用者直接使用,在各种starter的jar包中都是这种模式,例如你开发了一个mybatis,你就可以将其变成一个SpringBoot-starter,引入jar包就可以马上使用mybatis的功能,不需要客户端定义配置Bean。开发人员定义一次,所有用户省去重复装配劳动,可谓是一个伟大的发明。
排除候选装配的Bean
可以看到,这里拿到需要被装配的类名单之后,会做一系列的排除操作。
我们知道,要失效某些Bean的自动装配,需要如下操作
- 代码配置方式
- @EnableAutoConfiguration
- exclude()
- excludeName()
- @EnableAutoConfiguration
- 外部化配置方式
- 配置属性:spring.autoconfigure.exclude
以上方式在官方文档中也有描述。而以上操作就对应于上面方法中这一段代码
// 获取需要排除的列表
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
// 排除上面的列表包含的类
configurations.removeAll(exclusions);
看看getExclusions方法是如何获取需要被排除的列表
protected Set<String> getExclusions(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet<>();
// 获取注解中的exclude值
excluded.addAll(asList(attributes, "exclude"));
// 获取注解中的excludeName值
excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
// 获取配置属性spring.autoconfigure.exclude的值
excluded.addAll(getExcludeAutoConfigurationsProperty());
return excluded;
}
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
private List<String> getExcludeAutoConfigurationsProperty() {
// ...
// 获取spring.autoconfigure.exclude对应的值
String[] excludes = getEnvironment()
.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}
需要被排除的列表就是上述的那几个方式,很简单。
过滤候选装配的Bean
这里将过滤一些Bean,其中大部分自动装配的Bean都在这里被过滤掉了。那么是根据什么进行过滤的呢?又为什么要过滤呢?带着这两个问题往下看
configurations = filter(configurations, autoConfigurationMetadata);
private List<String> filter(List<String> configurations,
AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
// 获取filter
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
invokeAwareMethods(filter);
// match列表,下标和上面configurations列表是对应的
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
// 如果对应下标的match=false
if (!match[i]) {
skip[i] = true;
// 将其从列表中移除
candidates[i] = null;
skipped = true;
}
}
}
// 如果skipped=false,证明没有Bean被排除,返回原列表即可
if (!skipped) {
return configurations;
}
List<String> result = new ArrayList<>(candidates.length);
for (int i = 0; i < candidates.length; i++) {
if (!skip[i]) {
result.add(candidates[i]);
}
}
// ...
// 返回最终过滤的结果
return new ArrayList<>(result);
}
这段逻辑,最为关键的就是getAutoConfigurationImportFilters获取过滤器和过滤器的match方法了。首先来看一下是如何获取过滤器的
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
this.beanClassLoader);
}
又是很熟悉的SpringFactories加载机制,只不过这里获取AutoConfigurationImportFilter为key的value值,这个loadFactories方法和上面介绍的不一样,上面介绍的加载机制是loadFactoryNames方法,但也类似,因为loadFactories方法内部调用了loadFactoryNames方法,先获取了对应key的value列表,不同点只在于其会判断是否是FactroyClass的子类,也就是上述获取出来的value列表将判断是否是AutoConfigurationImportFilter的子类,如果不是会抛出异常。
总之万变不离其宗,只不过多了判断是否是子类,并且实例化出来的这一步。
这里我们搜索一下AutoConfigurationImportFilter在spring.factories文件中对应哪些value
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
其实,就是三个@Conditional的条件过滤。不过,这里的条件从哪来呢?关有filter没条件也不行啊。
这里我们追踪一下filter.match(candidates, autoConfigurationMetadata)的第二参数是从哪里传进来的。回到最开始的selectImports方法
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 条件元信息
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
原来,条件是从一开始我们没有分析的那一行代码中传进来的。让我们来看看这一行做了什么
protected static final String PATH = "META-INF/"
+ "spring-autoconfigure-metadata.properties";
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
原来,这里会加载META-INF下的spring-autoconfigure-metadata.properties文件中的信息,并封装为AutoConfigurationMetadata对象。这里我们看看这个文件的内容
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration=
org.springframework.boot.autoconfigure.kafka.KafkaAnnotationDrivenConfiguration.Configuration=
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration.ConditionalOnClass=org.influxdb.InfluxDB
这里定义了那些自动装配的Bean的装配条件。例如InfluxDbAutoConfiguration的自动装配条件是ConditionalOnClass,也就是说,其会在类路径下存在org.influxdb.InfluxDB这样的Class,就符合自动装配的条件。其作用和@ConditionalOnClass是一样的。
那么现在就可以解答我们上面的两个问题了
-
是根据什么进行过滤的呢?
答:根据META-INF下的spring-autoconfigure-metadata.properties文件中的信息,格式需要以需要过滤的Bean的全限定类名为key,加上你需要过滤的类型,例如ConditionalOnClass,就在key后面加上ConditionalOnClass,然后value是条件值,之后有配置这个的Bean就会像Conditional那样条件过滤了
-
又为什么要过滤呢?
答:由于很多自动装配的Bean不需要被装配,因为根本没有引入这样的jar包,比如JPA,我没有引入这个jar包,在classPath下自然就不会存在JAP对应的某些关键Class,此时就可以用ConditionalOnClass这个条件过滤提前把这个过滤掉,表示JPA根本不需要被自动装配到Spring中。虽然在自动装配类上打上注解进行过滤也是可以做到条件过滤的,但个人认为,这种提前过滤机制,可以极大增加加载自动装配类的速度。如果不进行提前过滤,那么将把比如100多个Bean全加载出来,然后一个个被Spring进行注解解析然后判断,这样效率太低了,在预加载其类名的时候就把它截断会来的更快一些。
发布自动装配获取完成事件
接下来就是发布一个事件了。回忆一下,发布事件的代码是这样的
protected AutoConfigurationEntry getAutoConfigurationEntry(
// ...
// 发布一个自动装配的事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
private void fireAutoConfigurationImportEvents(List<String> configurations,
Set<String> exclusions) {
// 获取监听器
List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
if (!listeners.isEmpty()) {
// 封装一个事件
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
configurations, exclusions);
for (AutoConfigurationImportListener listener : listeners) {
invokeAwareMethods(listener);
// 调用监听器的onAutoConfigurationImportEvent方法
listener.onAutoConfigurationImportEvent(event);
}
}
}
这里首先调用了getAutoConfigurationImportListeners方法获取到监听器列表,然后逐个调用监听器的onAutoConfigurationImportEvent方法。那么是如何获取到列表的呢?
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class,
this.beanClassLoader);
}
又是熟悉的SpringFactories加载机制。这里获取AutoConfigurationImportListener实例。接下来我们自定义一个监听器
自定义自动装配监听器
首先先定义一个AutoConfigurationImportListener监听器实现类
public class CustomerAutoConfigurationListener implements AutoConfigurationImportListener {
@Override
public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
// 获取classLoader
ClassLoader classLoader = event.getClass().getClassLoader();
// 候选的自动装配Bean列表
List<String> candidates = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, classLoader);
// 实际的自动装配Bean列表
List<String> candidateConfigurations = event.getCandidateConfigurations();
// 排除的Bean列表
Set<String> exclusions = event.getExclusions();
System.out.println("自动装配Class名单");
System.out.println("候选数量: " + candidates.size());
System.out.println("实际数量: " + candidateConfigurations.size());
System.out.println("被排除的数量: " + exclusions.size());
System.out.println("实际被装配的Bean名单");
candidateConfigurations.forEach(System.out::println);
System.out.println("被排除的Bean名单");
exclusions.forEach(System.out::println);
}
}
在MATA-INF下创建一个spring.fatcories文件,将以上实现放入该文件中
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
com.mytest.condition.listener.CustomerAutoConfigurationListener
接着编写引导类,我们这里排除一个会被自动装配的Bean
@EnableAutoConfiguration(exclude = SpringApplicationAdminJmxAutoConfiguration.class)
public class ContextBootstrap {
public static void main(String[] args) {
// 配置ContextBootstrap为引导Bean
new SpringApplicationBuilder(ContextBootstrap.class)
// 非WEB方式
.web(WebApplicationType.NONE)
// 启动上下文
.run(args)
// 关闭上下文
.close();
}
这里的EnableAutoConfiguration注解会使其产生自动装配,从而会发布一个自动装配事件,事件将会被我们上面的监听器所处理到。接下来看看结果
可以看到,有118个Bean会被自动装配,实际上排除+过滤了110个Bean,在最开始的地方截断了大部分的Bean的装配,有利于SpringBoot应用启动的时间。
总结
到这里,整个SpringBoot自动装配的流程大致就结束了。在@EnableAutoConfiguration的Import处理中,最终会返回一个需要被装配的Bean列表,其将会交给ConfigurationClassPostProcessor类进行处理,最终将此列表注册到Spring容器中,并将其视作配置类进行解析(@Bean、@ComponentScan、@Import之类的解析工作),而这段解析工作在 配置注解驱动的处理 这篇文章中将会有详细的描述。
其实,SpringBoot自动装配的魔法源自于Spring的能力,@Import导入类+SpringFactories加载机制,这些都是Spring3.0、4.0为注解驱动作出的贡献,在Spring5.0中,注解驱动逐渐完善。然而施法的人是开发jar包的人,这些人可以自定义starter(带有魔法的符咒)给用户使用,用户只需要加入starter的jar包,即可享受带有魔法的自动装配。一次劳动解决了万千开发重复配置的劳动。