精尽Spring Boot源码分析 - Condition 接口的扩展
该系列文章是笔者在学习 spring boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 spring boot 源码分析 github 地址 进行阅读
spring boot 版本:2.2.x
最好对 spring 源码有一定的了解,可以先查看我的 《死磕 spring 之 ioc 篇 - 文章导读》 系列文章
如果该篇内容对您有帮助,麻烦点击一下“推荐”,也可以关注博主,感激不尽~
该系列其他文章请查看:《精尽 spring boot 源码分析 - 文章导读》
概述
在上一篇《剖析 @springbootapplication 注解》文章分析了 spring boot 的自动配置功能,在通过 @enableautoconfiguration
注解驱动整个自动配置模块的过程中,并不是所有的自动配置类都需要被注入,不同的自动配置类需要满足一定条件后,才应该进行自动配置。
那么 spring boot 怎么知道满足一定条件呢?spring boot 对 spring 的 condition 接口进行了扩展,然后结合自定义的注解,则可以判断自动配置类是否符合条件。
例如 @conditionalonclass
可以指定必须存在哪些 class 对象才注入这个 bean。
那么接下来,我们一起来看看 spring 的 condition 接口以及 spring boot 对其的扩展
condition 演进史
profile 的出场
在 spring 3.1 的版本,为了满足不同环境注册不同的 bean ,引入了 @profile
注解。例如:
@configuration public class datasourceconfiguration { @bean @profile("dev") public datasource devdatasource() { // ... 单机 mysql } @bean @profile("prod") public datasource proddatasource() { // ... 集群 mysql } }
- 在测试环境下,我们注册单机 mysql 的 datasource bean
- 在生产环境下,我们注册集群 mysql 的 datasource bean
spring 3.1.x 的 @profile
注解如下:
@retention(retentionpolicy.runtime) @target(elementtype.type) public @interface profile { /** * the set of profiles for which this component should be registered. */ string[] value(); }
可以看到,最开始 @profile
注解并没有结合 @conditional
注解一起使用,而是在后续版本才引入的
condition 的出现
在 spring 4.0 的版本,出现了 condition 功能,体现在 org.springframework.context.annotation.condition
接口,如下:
@functionalinterface public interface condition { /** * determine if the condition matches. * @param context the condition context * @param metadata the metadata of the {@link org.springframework.core.type.annotationmetadata class} * or {@link org.springframework.core.type.methodmetadata method} being checked * @return {@code true} if the condition matches and the component can be registered, * or {@code false} to veto the annotated component's registration */ boolean matches(conditioncontext context, annotatedtypemetadata metadata); }
函数式接口,只有一个 matches(..)
方法,判断是否匹配,从入参中可以知道,它是和注解配合一起实现 condition 功能的,也就是 @conditional
注解,如下:
@target({elementtype.type, elementtype.method}) @retention(retentionpolicy.runtime) @documented public @interface conditional { /** * all {@link condition} classes that must {@linkplain condition#matches match} * in order for the component to be registered. */ class<? extends condition>[] value(); }
随之 @profile
注解也进行了修改,和 @conditional
注解配合使用
spring 5.1.x 的 @profile
注解如下:
@target({elementtype.type, elementtype.method}) @retention(retentionpolicy.runtime) @documented @conditional(profilecondition.class) public @interface profile { /** * the set of profiles for which the annotated component should be registered. */ string[] value(); }
这里指定的的 condition 实现类是 profilecondition,如下:
class profilecondition implements condition { @override public boolean matches(conditioncontext context, annotatedtypemetadata metadata) { multivaluemap<string, object> attrs = metadata.getallannotationattributes(profile.class.getname()); if (attrs != null) { for (object value : attrs.get("value")) { if (context.getenvironment().acceptsprofiles(profiles.of((string[]) value))) { return true; } } return false; } return true; } }
逻辑很简答,从当前 spring 应用上下文的 environment 中判断 @profile
指定的环境是否被激活,被激活了表示匹配成功,则注入对应的 bean,否则,不进行操作
但是 spring 本身提供的 condition 实现类不多,只有一个 profilecondition 对象
springbootcondition 的进击
spring boot 为了满足更加丰富的 condition 场景,对 spring 的 condition 接口进行了扩展,提供更多的实现类,如下:
上面仅列出了部分 springbootcondition 的子类,同时这些子类与对应的注解配置一起使用
-
@conditionalonclass
:必须都存在指定的 class 对象们 -
@conditionalonmissingclass
:指定的 class 对象们必须都不存在 -
@conditionalonbean
:必须都存在指定的 bean 们 -
@conditionalonmissingbean
:指定的 bean 们必须都不存在 -
@conditionalonsinglecandidate
:必须存在指定的 bean -
@conditionalonproperty
:指定的属性是否有指定的值 -
@conditionalonwebapplication
:当前的 web 应用类型是否在指定的范围内(any、servlet、reactive) -
@conditionalonnotwebapplication
:不是 web 应用类型
上面列出了 spring boot 中常见的几种 @conditionxxx
注解,他们都配合 @conditional
注解与对应的 condition 实现类一起使用,提供了非常丰富的 condition 场景
condition 在哪生效?
spring 提供了 condition 接口以及 @conditional
注解,那么在 spring 中哪里体现,或者说是哪里进行判断的呢?
其实我在 《死磕spring之ioc篇 - @bean 等注解的实现原理》 这篇文章中有提到过,我们稍微回顾一下,有两种情况:
- 通过
@component
注解(及派生注解)标注的 bean -
@configuration
标注的配置类中的@bean
标注的方法 bean
普通 bean
第一种情况是在 spring 扫描指定路径下的 .class 文件解析成对应的 beandefinition(bean 的前身)时,会根据 @conditional
注解判断是否符合条件,如下:
// classpathbeandefinitionscanner.java public int scan(string... basepackages) { // <1> 获取扫描前的 beandefinition 数量 int beancountatscanstart = this.registry.getbeandefinitioncount(); // <2> 进行扫描,将过滤出来的所有的 .class 文件生成对应的 beandefinition 并注册 doscan(basepackages); // register annotation config processors, if necessary. // <3> 如果 `includeannotationconfig` 为 `true`(默认),则注册几个关于注解的 postprocessor 处理器(关键) // 在其他地方也会注册,内部会进行判断,已注册的处理器不会再注册 if (this.includeannotationconfig) { annotationconfigutils.registerannotationconfigprocessors(this.registry); } // <4> 返回本次扫描注册的 beandefinition 数量 return (this.registry.getbeandefinitioncount() - beancountatscanstart); } // classpathscanningcandidatecomponentprovider.java private boolean isconditionmatch(metadatareader metadatareader) { if (this.conditionevaluator == null) { this.conditionevaluator = new conditionevaluator(getregistry(), this.environment, this.resourcepatternresolver); } return !this.conditionevaluator.shouldskip(metadatareader.getannotationmetadata()); }
上面只是简单的提一下,可以看到会通过 conditionevaluator 计算器进行计算,判断是否满足条件
配置类
第二种情况是 spring 会对 配置类进行处理,扫描到带有 @bean
注解的方法,尝试解析成 beandefinition(bean 的前身)时,会根据 @conditional
注解判断是否符合条件,如下:
// configurationclassbeandefinitionreader.java private void loadbeandefinitionsforconfigurationclass( configurationclass configclass, trackedconditionevaluator trackedconditionevaluator) { // <1> 如果不符合 @conditional 注解的条件,则跳过 if (trackedconditionevaluator.shouldskip(configclass)) { string beanname = configclass.getbeanname(); if (stringutils.haslength(beanname) && this.registry.containsbeandefinition(beanname)) { this.registry.removebeandefinition(beanname); } this.importregistry.removeimportingclass(configclass.getmetadata().getclassname()); return; } // <2> 如果当前 configurationclass 是通过 @import 注解被导入的 if (configclass.isimported()) { // <2.1> 根据该 configurationclass 对象生成一个 beandefinition 并注册 registerbeandefinitionforimportedconfigurationclass(configclass); } // <3> 遍历当前 configurationclass 中所有的 @bean 注解标注的方法 for (beanmethod beanmethod : configclass.getbeanmethods()) { // <3.1> 根据该 beanmethod 对象生成一个 beandefinition 并注册(注意这里有无 static 修饰会有不同的配置) loadbeandefinitionsforbeanmethod(beanmethod); } // <4> 对 @importresource 注解配置的资源进行处理,对里面的配置进行解析并注册 beandefinition loadbeandefinitionsfromimportedresources(configclass.getimportedresources()); // <5> 通过 @import 注解导入的 importbeandefinitionregistrar 实现类往 beandefinitionregistry 注册 beandefinition // mybatis 集成 spring 就是基于这个实现的,可查看 mybatis-spring 项目中的 mapperscannerregistrar 这个类 // https://github.com/liu844869663/mybatis-spring/blob/master/src/main/java/org/mybatis/spring/annotation/mapperscannerregistrar.java loadbeandefinitionsfromregistrars(configclass.getimportbeandefinitionregistrars()); }
上面只是简单的提一下,可以看到会通过 trackedconditionevaluator 计算器进行计算,判断是否满足条件
这里提一下,对于 @bean
标注的方法,会得到 cglib 的提升,也就是返回的是一个代理对象,设置一个拦截器专门对 @bean
方法进行拦截处理,通过依赖查找的方式从 ioc 容器中获取 bean 对象,如果是单例 bean,那么每次都是返回同一个对象,所以当主动调用这个方法时获取到的都是同一个对象。
springbootcondition
org.springframework.boot.autoconfigure.condition.springbootcondition
抽象类,实现了 condition 接口,spring boot 扩展 condition 的抽象基类,主要用于打印相应的日志,并记录每次的匹配结果,如下:
/** * base of all {@link condition} implementations used with spring boot. provides sensible * logging to help the user diagnose what classes are loaded. * * @author phillip webb * @author greg turnquist * @since 1.0.0 */ public abstract class springbootcondition implements condition { private final log logger = logfactory.getlog(getclass()); @override public final boolean matches(conditioncontext context, annotatedtypemetadata metadata) { // <1> 从注解元信息中获取所标注的`类名`(或者`类名#方法名`) string classormethodname = getclassormethodname(metadata); try { // <2> 获取匹配结果(包含匹配消息),抽象方法,交由子类实现 conditionoutcome outcome = getmatchoutcome(context, metadata); // <3> 打印匹配日志 logoutcome(classormethodname, outcome); // <4> 向 conditionevaluationreport 中记录本次的匹配结果 recordevaluation(context, classormethodname, outcome); // <5> 返回匹配结果 return outcome.ismatch(); } catch (noclassdeffounderror ex) { // 抛出异常 } catch (runtimeexception ex) { // 抛出异常 } } }
实现的 condition 接口方法处理过程如下:
-
从注解元信息中获取所标注的
类名
(或者类名#方法名
)private static string getclassormethodname(annotatedtypemetadata metadata) { if (metadata instanceof classmetadata) { classmetadata classmetadata = (classmetadata) metadata; return classmetadata.getclassname(); } methodmetadata methodmetadata = (methodmetadata) metadata; return methodmetadata.getdeclaringclassname() + "#" + methodmetadata.getmethodname(); }
-
调用
getmatchoutcome(..)
方法,获取匹配结果(包含匹配消息),抽象方法,交由子类实现 -
打印匹配日志
protected final void logoutcome(string classormethodname, conditionoutcome outcome) { if (this.logger.istraceenabled()) { this.logger.trace(getlogmessage(classormethodname, outcome)); } }
-
向 conditionevaluationreport 中记录本次的匹配结果
private void recordevaluation(conditioncontext context, string classormethodname, conditionoutcome outcome) { if (context.getbeanfactory() != null) { conditionevaluationreport.get(context.getbeanfactory()).recordconditionevaluation(classormethodname, this, outcome); } }
-
返回匹配结果
springbootcondition 的实现类
onclasscondition
org.springframework.boot.autoconfigure.condition.onclasscondition
,继承 springbootcondition 抽象类,如下:
@order(ordered.highest_precedence) class onclasscondition extends filteringspringbootcondition { /** * 该方法来自 {@link springbootcondition} 判断某个 bean 是否符合注入条件(`@conditionalonclass` 和 `conditionalonmissingclass`) */ @override public conditionoutcome getmatchoutcome(conditioncontext context, annotatedtypemetadata metadata) { classloader classloader = context.getclassloader(); conditionmessage matchmessage = conditionmessage.empty(); // <1> 获取这个类上面的 `@conditionalonclass` 注解的值 // 也就是哪些 class 对象必须存在 list<string> onclasses = getcandidates(metadata, conditionalonclass.class); if (onclasses != null) { // <1.1> 找到这些 class 对象中哪些是不存在的 list<string> missing = filter(onclasses, classnamefilter.missing, classloader); // <1.2> 如果存在不存在的,那么不符合条件,返回不匹配 if (!missing.isempty()) { return conditionoutcome.nomatch(conditionmessage.forcondition(conditionalonclass.class) .didnotfind("required class", "required classes").items(style.quote, missing)); } // <1.3> 添加 `@conditionalonclass` 满足条件的匹配信息 matchmessage = matchmessage.andcondition(conditionalonclass.class) .found("required class", "required classes") .items(style.quote, filter(onclasses, classnamefilter.present, classloader)); } // <2> 获取这个类上面的 `@conditionalonmissingclass` 注解的值 // 也就是这些 class 对象必须都不存在 list<string> onmissingclasses = getcandidates(metadata, conditionalonmissingclass.class); if (onmissingclasses != null) { // <2.1> 找到这些 class 对象中哪些是存在的 list<string> present = filter(onmissingclasses, classnamefilter.present, classloader); // <2.2> 如果有一个存在,那么不符合条件,返回不匹配 if (!present.isempty()) { return conditionoutcome.nomatch(conditionmessage.forcondition(conditionalonmissingclass.class) .found("unwanted class", "unwanted classes").items(style.quote, present)); } // <2.3> 添加 `@conditionalonmissingclass` 满足条件的匹配信息 matchmessage = matchmessage.andcondition(conditionalonmissingclass.class) .didnotfind("unwanted class", "unwanted classes") .items(style.quote, filter(onmissingclasses, classnamefilter.missing, classloader)); } // <3> 返回符合条件的结果 return conditionoutcome.match(matchmessage); } }
判断是否匹配的过程如下:
-
获取这个类上面的
@conditionalonclass
注解的值,也就是哪些 class 对象必须存在-
找到这些 class 对象中哪些是不存在的
protected final list<string> filter(collection<string> classnames, classnamefilter classnamefilter, classloader classloader) { // 如果为空,则返回空结果 if (collectionutils.isempty(classnames)) { return collections.emptylist(); } list<string> matches = new arraylist<>(classnames.size()); // 使用 `classnamefilter` 对 `classnames` 进行过滤 for (string candidate : classnames) { if (classnamefilter.matches(candidate, classloader)) { matches.add(candidate); } } // 返回匹配成功的 `classname` 们 return matches; }
-
如果存在不存在的,那么不符合条件,返回不匹配
-
添加
@conditionalonclass
满足条件的匹配信息
-
-
获取这个类上面的
@conditionalonmissingclass
注解的值,也就是这些 class 对象必须都不存在- 找到这些 class 对象中哪些是存在的,和上面的
1.1
差不多,只不过这里传的是 classnamefilter.present 过滤器 - 如果有一个存在,那么不符合条件,返回不匹配
- 添加
@conditionalonmissingclass
满足条件的匹配信息
- 找到这些 class 对象中哪些是存在的,和上面的
-
返回符合条件的结果
上面使用到的 classnamefilter 如下:
protected enum classnamefilter { /** 指定类存在 */ present { @override public boolean matches(string classname, classloader classloader) { return ispresent(classname, classloader); } }, /** 指定类不存在 */ missing { @override public boolean matches(string classname, classloader classloader) { return !ispresent(classname, classloader); } }; abstract boolean matches(string classname, classloader classloader); static boolean ispresent(string classname, classloader classloader) { if (classloader == null) { classloader = classutils.getdefaultclassloader(); } try { // 加载指定类,加载成功表示存在这个类 resolve(classname, classloader); return true; } catch (throwable ex) { // 加载失败表示不存在这个类 return false; } } }
逻辑很简单,就是判断 class 对象是否存在或者不存在
其它实现类
关于 springbootcondition 其他的实现类逻辑都差不多,感兴趣的可以去看看
回顾自动配置
在上一篇《剖析 @springbootapplication 注解》 文章分析通过 @enableautoconfiguration
注解驱动整个自动配置模块的过程中,会通过指定的 autoconfigurationimportfilter 对所有的自动配置类进行过滤,满足条件才进行自动配置
可以回顾一下上一篇文章的 2. getautoconfigurationentry 方法 小节和 3. filter 方法 小节
protected autoconfigurationentry getautoconfigurationentry(autoconfigurationmetadata autoconfigurationmetadata, annotationmetadata annotationmetadata) { // <1> 如果通过 `spring.boot.enableautoconfiguration` 配置关闭了自动配置功能 if (!isenabled(annotationmetadata)) { // 则返回一个“空”的对象 return empty_entry; } // <2> 获取 `@enableautoconfiguration` 注解的配置信息 annotationattributes attributes = getattributes(annotationmetadata); // <3> 从所有的 `meta-inf/spring.factories` 文件中找到 `@enableautoconfiguration` 注解对应的类(需要自动配置的类) list<string> configurations = getcandidateconfigurations(annotationmetadata, attributes); // <4> 对所有的自动配置类进行去重 configurations = removeduplicates(configurations); // <5> 获取需要排除的自动配置类 // 可通过 `@enableautoconfiguration` 注解的 `exclude` 和 `excludename` 配置 // 也可以通过 `spring.autoconfigure.exclude` 配置 set<string> exclusions = getexclusions(annotationmetadata, attributes); // <6> 处理 `exclusions` 中特殊的类名称,保证能够排除它 checkexcludedclasses(configurations, exclusions); // <7> 从 `configurations` 中将 `exclusions` 需要排除的自动配置类移除 configurations.removeall(exclusions); /** * <8> 从 `meta-inf/spring.factories` 找到所有的 {@link autoconfigurationimportfilter} 对 `configurations` 进行过滤处理 * 例如 spring boot 中配置了 {@link org.springframework.boot.autoconfigure.condition.onclasscondition} * 在这里提前过滤掉一些不满足条件的自动配置类,在 spring 注入 bean 的时候也会判断哦~ */ configurations = filter(configurations, autoconfigurationmetadata); fireautoconfigurationimportevents(configurations, exclusions); // <10> 将所有的自动配置类封装成一个 autoconfigurationentry 对象,并返回 return new autoconfigurationentry(configurations, exclusions); }
我们看到第 8
步,调用 filter(..)
方法, 目的就是过滤掉一些不符合 condition 条件的自动配置类
private list<string> filter(list<string> configurations, autoconfigurationmetadata autoconfigurationmetadata) { long starttime = system.nanotime(); // <1> 将自动配置类保存至 `candidates` 数组中 string[] candidates = stringutils.tostringarray(configurations); boolean[] skip = new boolean[candidates.length]; boolean skipped = false; /* * <2> 从 `meta-inf/spring.factories` 找到所有的 autoconfigurationimportfilter 对 `candidates` 进行过滤处理 * 有 onclasscondition、onbeancondition、onwebapplicationcondition */ for (autoconfigurationimportfilter filter : getautoconfigurationimportfilters()) { // <2.1> aware 回调 invokeawaremethods(filter); // <2.2> 对 `candidates` 进行匹配处理,获取所有的匹配结果 boolean[] match = filter.match(candidates, autoconfigurationmetadata); // <2.3> 遍历匹配结果,将不匹配的自动配置类至空 for (int i = 0; i < match.length; i++) { if (!match[i]) { skip[i] = true; candidates[i] = null; skipped = true; } } } // <3> 如果没有不匹配的结果则全部返回 if (!skipped) { return configurations; } // <4> 获取到所有匹配的自动配置类,并返回 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); }
可以看到第 2
步,会从 meta-inf/spring.factories
中找到对应的 autoconfigurationimportfilter 实现类对所有的自动配置类进行过滤
# 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
这里,我们一定要注意到入参中的 autoconfigurationmetadata
对象,它里面保存了 meta-inf/spring-autoconfigure-metadata.properties
文件中 spring boot 的自动配置类的注解元信息(sprng boot 编译时生成的),如何来的请回顾上一篇文章
autoconfigurationimportfilter
org.springframework.boot.autoconfigure.autoconfigurationimportfilter
接口,用于过滤掉无需自动引入的自动配置类
/** * filter that can be registered in {@code spring.factories} to limit the * auto-configuration classes considered. this interface is designed to allow fast removal * of auto-configuration classes before their bytecode is even read. * * @author phillip webb * @since 1.5.0 */ @functionalinterface public interface autoconfigurationimportfilter { boolean[] match(string[] autoconfigurationclasses, autoconfigurationmetadata autoconfigurationmetadata); }
可以看到它的注释,因为自动配置类会很多,如果无需使用,而创建对应的 bean(字节码)到 jvm 内存中,将是一种浪费
可以看到它的最终实现类,都是构建在 springbootcondition 之上。
上一篇: Nmap网络安全扫描器说明(3)