springboot情操陶冶-@Conditional和@AutoConfigureAfter注解解析
承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上阐述
@AutoConfigureAfter和@Conditional注解的作用与解析
1.@Conditional
根据单词来理解,其就是条件的意思。在分析之前我们可以看下其内部源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition}s that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
其作用于类、方法上,且指定的value值必须是org.springframework.context.annotation.Condition的实现类,供条件判断。
以此为基础而扩展的注解还有@ConditionalBean、@ConditionalOnWebApplication、@ConditionalOnClass、@ConditionalOnMissingBean等等。
@Conditional注解被解析入口
那么我们肯定想知道,其中的注解是如何被解析的呢。其实在前文中的ConfigurationClassParser类中,在执行真正的doProcessConfigurationClass()方法前,会执行如下的代码
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
// 条件判断,满足则直接返回,不进行后续的解析
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
....
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
也就是会执行上述的ConditionEvaluator#shouldSkip()方法,只有条件不满足后才会继续往下执行真正的@Configuration注解解析。
ConditionEvaluator#shouldSkip()
废话不多说,直接上源码
// metadata为被注解的类元素,返回值为true表明条件满足应该被忽略
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
// 1.判断类是否含有@Conditional注解,否则直接返回
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
// 2.获取类上所有含有@Conditional注解的value集合(其会递归找寻注解的注解)
List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
// 3.根据Order来进行排序
AnnotationAwareOrderComparator.sort(conditions);
// 4.对集合内的condition统一调用matches()方法,一旦遇到条件判断不满足的则返回true对此注解类元素进行忽略
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
具体的代码解释已经按照注释给出了,其实也很简单,读者稍微阅读就能明白了。另外额外的注解比如@ConditionalOnMissingBean等读者可自行去阅读代码分析,笔者此处就不展开了
2.@AutoConfigureAfter
与@AutoConfigureBefore类同,代表的含义就是自动注入在什么类加载前或者之后。先来看下其内部源码
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
public @interface AutoConfigureAfter {
/**
* The auto-configure classes that should have already been applied.
* @return the classes
*/
Class<?>[] value() default {};
/**
* The names of the auto-configure classes that should have already been applied.
* @return the class names
* @since 1.2.2
*/
String[] name() default {};
}
只作用于类上,内部属性name表明beanDefinition的类名;内部属性value表明beanDefinition的类。
那么其是如何被解析的呢,也是基于前文的ConfigurationClassParser#parse()方法,具体如下
public void parse(Set<BeanDefinitionHolder> configCandidates) {
this.deferredImportSelectors = new LinkedList<>();
// 解析@Configuration注解
....
// 解析DeferredImportSelector接口类,表面上也就是延迟解析的意思
processDeferredImportSelectors();
}
笔者此处只关注processDeferredImportSelectors()方法,通过此方法便可察觉到@AutoConfigureAfter等注解的蛛丝马迹
ConfigurationClassParser#processDeferredImportSelectors()
直接阅读源码
private void processDeferredImportSelectors() {
// 1.通过processImport()方法得到DeferredImportSelector接口集合,无则直接返回
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
if (deferredImports == null) {
return;
}
// 2.排序
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
// 3.遍历DeferredImportSelector接口集合,获取Group集合类,默认为DefaultDeferredImportSelectorGroup
Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
for (DeferredImportSelectorHolder deferredImport : deferredImports) {
// notice this........
Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
DeferredImportSelectorGrouping grouping = groupings.computeIfAbsent(
(group != null ? group : deferredImport),
key -> new DeferredImportSelectorGrouping(createGroup(group)));
grouping.add(deferredImport);
configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getConfigurationClass());
}
// 4. 遍历Group集合,作用也是调用processImport()方法用于解析@Import
for (DeferredImportSelectorGrouping grouping : groupings.values()) {
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = configurationClasses.get(entry.getMetadata());
try {
processImports(configurationClass, asSourceClass(configurationClass),
asSourceClasses(entry.getImportClassName()), false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configurationClass.getMetadata().getClassName() + "]", ex);
}
});
}
}
笔者和读者此处只需要关注deferredImport.getImportSelector().getImportGroup()这个方法即可,此处以AutoConfigurationImportSelector.class为例
AutoConfigurationImportSelector
首先看下其getImportGroup()方法
public Class<? extends Group> getImportGroup() {
return AutoConfigurationGroup.class;
}
再观察下AutoConfigurationGroup此类的selectImports()方法
public Iterable<Entry> selectImports() {
return sortAutoConfigurations().stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName),
importClassName))
.collect(Collectors.toList());
}
关键点来了,就在sortAutoConfigurations()方法,其会通过AutoConfigurationSorter类来对导入的class类进行排序,至于如何排序我们继续往下看
AutoConfigurationSorter
排序方法getInPriorityOrder(),我们看下源码
public List<String> getInPriorityOrder(Collection<String> classNames) {
AutoConfigurationClasses classes = new AutoConfigurationClasses(
this.metadataReaderFactory, this.autoConfigurationMetadata, classNames);
List<String> orderedClassNames = new ArrayList<>(classNames);
// Initially sort alphabetically.首先根据ASCII来进行排序
Collections.sort(orderedClassNames);
// Then sort by order,再根据Order来进行排序
orderedClassNames.sort((o1, o2) -> {
int i1 = classes.get(o1).getOrder();
int i2 = classes.get(o2).getOrder();
return Integer.compare(i1, i2);
});
// Then respect @AutoConfigureBefore @AutoConfigureAfter
orderedClassNames = sortByAnnotation(classes, orderedClassNames);
return orderedClassNames;
}
可以得出,最关键的排序来自sortByAnnotation()方法,具体就不看了,无非是根据before/after,来对importClassName进行排序得出一个有序的集合。
1.最后再回到ConfigurationClassParser#processDeferredImportSelectors()方法的最后一段,其会对上述的有序的集合遍历操作
processImports()方法,如果对应的class类不存在则会报错,也就满足了AutoConfigureBefore/AutoConfigureAfter的含义。2.上述的@AutoConfigureAfter注解解析只作用于META-INF\spring.factories文件中EnableAutoConfiguration属性对应的class类集合。
(v2.0版本以下支持用户使用该注解直接应用自定义类;v2.0版本以上,如果用户也使用了该注解,也需要在META-INF\spring.factories配置相应的EnableAutoConfiguration属性)
小结
针对@Conditional和@AutoConfigureAfter的具体解析可见上文,本文也是对前文的补充。希望读者在阅读此文的同时务必阅读前文方可理解上述的代码含义。同时因为这两个注解具有条件性,所以springboot多用此两注解来相互搭配构建不同条件的依赖部署,对去配置化起到了很大的作用。以WebMvcAutoConfiguration类作为结尾
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}
springboot情操陶冶-@Conditional和@AutoConfigureAfter注解解析的更多相关文章
- springboot情操陶冶-@SpringBootApplication注解解析
承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上对@SpringBootApplication注解作下简单的分析 @SpringBootApplicat ...
- springboot情操陶冶-@Configuration注解解析
承接前文springboot情操陶冶-SpringApplication(二),本文将在前文的基础上分析下@Configuration注解是如何一步一步被解析的 @Configuration 如果要了 ...
- springboot情操陶冶-@ConfigurationProperties注解解析
承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上阐述@ConfigurationProperties注解的使用 @ConfigurationProper ...
- springboot情操陶冶-jmx解析
承接前文springboot情操陶冶-@Configuration注解解析,近期笔者接触的项目中有使用到了jmx的协议框架,遂在前文的基础上讲解下springboot中是如何整合jmx的 知识储备 J ...
- springboot情操陶冶-web配置(三)
承接前文springboot情操陶冶-web配置(二),本文将在前文的基础上分析下mvc的相关应用 MVC简单例子 直接编写一个Controller层的代码,返回格式为json package com ...
- springboot情操陶冶-web配置(二)
承接前文springboot情操陶冶-web配置(一),在分析mvc的配置之前先了解下其默认的错误界面是如何显示的 404界面 springboot有个比较有趣的配置server.error.whit ...
- springboot情操陶冶-web配置(七)
参数校验通常是OpenApi必做的操作,其会对不合法的输入做统一的校验以防止恶意的请求.本文则对参数校验这方面作下简单的分析 spring.factories 读者应该对此文件加以深刻的印象,很多sp ...
- springboot情操陶冶-web配置(四)
承接前文springboot情操陶冶-web配置(三),本文将在DispatcherServlet应用的基础上谈下websocket的使用 websocket websocket的简单了解可见维基百科 ...
- springboot情操陶冶-web配置(一)
承接前文springboot情操陶冶-@SpringBootApplication注解解析,在前文讲解的基础上依次看下web方面的相关配置 环境包依赖 在pom.xml文件中引入web依赖,炒鸡简单, ...
随机推荐
- 日期类时间类,日期时间类,单例模式,装箱与拆箱,数字类随机数,BigDecimal总结
1.日期类,时间类,日期时间类 初步日期使用方法及格式转换方法(旧方法): 格式://Mon Jul 30 11:26:05 CST 2018 年月日时分秒 CST代表北 ...
- U-Boot Makefile分析(4)具体子Makefile的分析
前面分析的都是多数Makefile要读入的文件,这次我们以drivers/mtd/nand/Makefile为例,分析一个具体的子Makefile是如何工作的. 子Makefile的结构是固定的: i ...
- bzoj3237(cdq+并查集)
这题一眼lct,然而 #include<iostream> #include<cstdio> #include<cmath> #include<cstring ...
- OPC上传ONENET工具
这个去年做过比较死的 今年吸取人家转发网关配置工具的优点重做下
- 31.Stack
在Java中Stack类表示后进先出(LIFO)的对象堆栈.栈是一种非常常见的数据结构,它采用典型的先进后出的操作方式完成的.每一个栈都包含一个栈顶,每次出栈是将栈顶的数据取出,如下: Stack通过 ...
- 背水一战 Windows 10 (99) - 关联启动: 关联指定的文件类型, 关联指定的协议
[源码下载] 背水一战 Windows 10 (99) - 关联启动: 关联指定的文件类型, 关联指定的协议 作者:webabcd 介绍背水一战 Windows 10 之 关联启动 关联指定的文件类型 ...
- 使用 VSTS 进行 CI 的过程中,无法识别 .NET Core 2.x 的情况处理
大概是由于 .NET Core 2.1 还没有正式发布,使用 VSTS 进行持续集成(CI)的过程中,自动 Build 的环节无法识别 .NET Core 2.1 的框架,查看日志会提示如下错误: V ...
- 剑指offer面试题24:二叉搜索树的后序遍历序列
题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是返回true,否则返回false. 假设输入的数组任意两个数字都不相同 解题思路:二叉搜索树的特点是根节点的左子树的值小于等 ...
- 谈谈npm依赖管理
引言 现在的前端开发几乎都离不开nodejs的包管理器npm,比如前端在搭建本地开发服务以及打包编译前端代码等都会用到.在前端开发过程中,经常用到npm install来安装所需的依赖,至于其中的技术 ...
- 【洛谷4172】 [WC2006]水管局长(LCT)
传送门 洛谷 BZOJ Solution 如果不需要动态的话,那就是一个裸的最小生成树上的最大边权对吧. 现在动态了的话,把这个过程反着来,就是加边对吧. 现在问题变成了怎么动态维护加边的最小生成树, ...