【Spring Boot源码分析】@EnableAutoConfiguration注解(一)@AutoConfigurationImportSelector注解的处理
一、概述
1. 功能的实现:(Spring Boot部分)
2. 接口的调度:(Spring部分)
3. 接口在框架中的位置:(其中一条路径,由顶向下)
二、源码细节
(SpringBoot) boot.autoconfigure.EnableAutoConfiguration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) // 载入selector,识别AutoConfigutaion类并import
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
// @interface的参数,以方法形式声明
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
(SpringBoot) boot.autoconfigure.AutoConfigurationImportSelector类
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
//......
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 如果AutoConfiguration没开,返回{}
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 将spring-autoconfigure-metadata.properties的键值对配置载入到PropertiesAutoConfigurationMetadata对象中并返回
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// 基于各种配置计算需要import的configuration和exclusion
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
} // 判断AudoConfiguration是否开启
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
// 如果配置文件中有"spring.boot.enableautoconfiguration",返回该字段的值;否则返回true
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
} protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取注解的属性值
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 从META-INF/spring.factories文件中获取EnableAutoConfiguration所对应的configurations
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重,List转Set再转List
configurations = removeDuplicates(configurations);
// 从注解的exclude/excludeName属性中获取排除项
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 对于不属于AutoConfiguration的exclude报错
checkExcludedClasses(configurations, exclusions);
// 从configurations去除exclusions
configurations.removeAll(exclusions);
// 由所有AutoConfigurationImportFilter类的实例再进行一次筛选,去
configurations = filter(configurations, autoConfigurationMetadata);
// 把AutoConfigurationImportEvent绑定在所有AutoConfigurationImportListener子类实例上
fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回(configurations, exclusions)组
return new AutoConfigurationEntry(configurations, exclusions);
}
// ......
}
(Spring) context.annotation.ConfigurationClassParser.doProcessConfigurationClass()方法
其中,configClass是一个ConfigurationClass实例,记录了bean name(返回的bean名)和meta data(配置数据);sourceClass是简单封装后的有注解的类,主要方便对类的注解的使用,初始值是封装过的configClass。
@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// ......
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true); // ......
// Process superclass, if any
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
} // No superclass -> processing is complete
return null;
}
其中,processImports()方法的第三个参数中,getImports()方法嵌套的遍历了sourceClass的注解,搜集所有@Import注解的值,即被Import的类名集合。
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
// 如果是头一次添加
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
// 当前注解的类名是否是Import
if (!annName.equals(Import.class.getName())) {
// 嵌套遍历被Import的类
collectImports(annotation, imports, visited);
}
}
// 增加Import注解的值,即被Import的类名
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
(Spring) context.annotation.ConfigurationClassParser.processImports()方法
processImports() 的第一个参数configClass,是上层函数processConfigurationClass()的唯一参数,即被处理的Configuration类。第二个参数currentSourceClass是configClass的SourceClass类封装。第三个参数是嵌套遍历出的所有需要被Import的类。第四个参数指定是否检查循环import。
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) { // ......
for (SourceClass candidate : importCandidates) {
// 如果candidate(即被@Import的类)是ImportSelector的子类
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
// 生成candidate class(一个ImportSelector子类)的实例
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
// 将ImportSelector子类实例挂载为对应功能的Aware类(用于消息通知?)
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
// DeferredImportSelector是一种特殊的ImportSelector,这里单独处理
if (selector instanceof DeferredImportSelector) {
// 挂到deferredImportSelectors列表上
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
// 筛选符合要求的@Import的名字
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
// 转换成名字对应的类的集合
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 嵌套判断被@Import的类是不是还有@Import注解
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
// ......
}
// ......
}
(Spring) context.annotation.ConfigurationClassParser.DefferedImportSelectorHandler私有类
deferredImportSelectors已被初始化为ArrayList<>(),因此全部走else分支。
private class DeferredImportSelectorHandler {
@Nullable
private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();
public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(
configClass, importSelector); // 一个封装后的pair
// deferredImportSelectors被初始化为ArrayList<>(),所以if分支永远不会执行到?
if (this.deferredImportSelectors == null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
handler.register(holder); // 注册到DeferredImportSelectorGroupingHandler.configurationClasses中
handler.processGroupImports(); // -> processImports()
}
else {
// 所有的DeferredImportSelector类实例都挂到deferredImportSelectors列表上
this.deferredImportSelectors.add(holder);
}
}
// ......
}
(Spring) context.annotation.ConfigurationClassParser.processConfigurationClass() 方法
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
// ...
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
// 如果sourceClass有父类会返回父类,否则返回null
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
(Spring) context.annotation.ConfigurationClassParser.parse() 方法
public void parse(Set<BeanDefinitionHolder> configCandidates) {
// 处理所有的ImportSelector类,其中DeferredImportSelector类只挂在deferredImportSelectorHandler列表上不处理,其他均处理,即嵌套遍历被Import的类
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
// 处理deferredImportSelectorHandler上挂着的DeferredImportSelector类
this.deferredImportSelectorHandler.process();
}
protected final void parse(@Nullable String className, String beanName) throws IOException {
Assert.notNull(className, "No bean class name for configuration class bean definition");
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
processConfigurationClass(new ConfigurationClass(reader, beanName));
}
protected final void parse(Class<?> clazz, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(clazz, beanName));
}
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
再来看看DeferredImportSelector实例是如何被处理的。
(Spring) context.annotation.ConfigurationClassParser.DefferedImportSelectorHandler私有类
和普通ConfigurationClass一样,DefferedImportSelector最后也是先注册到列表中,再依次嵌套处理,只不过在import前多了一个根据order排序。
// 对@Order(...)进行排序
private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR =
(o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getImportSelector(), o2.getImportSelector()); private class DeferredImportSelectorHandler {
// ...
public void process() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
// 添加到handler的configurationClasses列表中
deferredImports.forEach(handler::register);
// 对handler中每个grouping的每个configClass,调用processImports()
handler.processGroupImports();
}
}
finally {
this.deferredImportSelectors = new ArrayList<>();
}
}
}
DONE.
【Spring Boot源码分析】@EnableAutoConfiguration注解(一)@AutoConfigurationImportSelector注解的处理的更多相关文章
- 精尽Spring Boot源码分析 - 剖析 @SpringBootApplication 注解
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - @ConfigurationProperties 注解的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 序言
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 支持外部 Tomcat 容器的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - Condition 接口的扩展
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 文章导读
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- Spring Boot源码分析-配置文件加载原理
在Spring Boot源码分析-启动过程中我们进行了启动源码的分析,大致了解了整个Spring Boot的启动过程,具体细节这里不再赘述,感兴趣的同学可以自行阅读.今天让我们继续阅读源码,了解配置文 ...
- Spring Boot源码分析-启动过程
Spring Boot作为目前最流行的Java开发框架,秉承"约定优于配置"原则,大大简化了Spring MVC繁琐的XML文件配置,基本实现零配置启动项目. 本文基于Spring ...
随机推荐
- SpringCloud(二)- 服务注册与发现Eureka
离上一篇微服务的基本概念已经过去了几个月,在写那篇博客之前,自己还并未真正的使用微服务架构,很多理解还存在概念上.后面换了公司,新公司既用了SpringCloud也用了Dubbo+Zookeeper, ...
- SPOJ - QTREE5 Query on a tree V 边分治
题目传送门 题意:给你一棵树, 然后树上的点都有颜色,且原来为黑,现在有2个操作,1 改变某个点的颜色, 2 询问树上的白点到u点的最短距离是多少. 题解: 这里用的还是边分治的方法. 把所有东西都抠 ...
- codeforces 811 C. Vladik and Memorable Trip(dp)
题目链接:http://codeforces.com/contest/811/problem/C 题意:给你n个数,现在让你选一些区间出来,对于每个区间中的每一种数,全部都要出现在这个区间. 每个区间 ...
- [Swoole入门到进阶] [精选公开课] Swoole服务器-Server的四层生命周期
PHP 完整生命周期 执行PHP文件 PHP扩展模块初始化(MINIT) PHP扩展请求初始化(RINIT) 执行 PHP 逻辑 PHP扩展请求结束(RSHUTDOWN) PHP脚本清理 PHP扩展模 ...
- List集合的排序
最近在写需求时,将某张表中的短信信息拿出,sql写完后,取出来后的结构是List<Map>,在进行到某一步时需要将这个List<Map>进行逆序排序, 当时第一想法便是写一个f ...
- 【Offer】[34] 【二叉树中和为某一值的路径】
题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径.从树的根节点开始往下一直到叶节点所经过的节点形成一条路径.  ...
- 设置普通用户输入sudo,免密进入root账户
满足给开发用户开权限,赋予sudo权限.又不让其输入密码的方式: 方式一: 开始系统内部的wheel用户组, 在/etc/suoers 中编辑配置文件如下: %wheel ALL=(ALL) NOPA ...
- IntelliJ IDEA远程连接tomcat,实现单步调试
web项目部署到tomcat上之后,有时需要打断点单步调试,如果用的是Intellij idea,可以通过如下方法实现: 开启debug端口,启动tomcat 以tomcat7.0.75为例,打开bi ...
- Kubernetes监控实践
一.Kubernetes介绍 Kubernetes(K8s)是一个开源平台,能够有效简化应用管理.应用部署和应用扩展环节的手动操作流程,让用户更加灵活地部署管理云端应用. 作为可扩展的容错平台,K8s ...
- STL中bitset的用法
终于又来写博客了 == bitset存储的是二进数位,就和一个bool性数组差不多.用法上和数组的操作方式也差不多. 每位只占一个字节,大大优化了空间,可以通过数组形式访问. bitset定义 可以用 ...