1前言
在springboot的自动装配过程中,执行完候选配置类导入后,会进行条件过滤。那么在讲解条件过滤前,我们先来了解springboot常用的条件注解,以及它们底层执行原理。
在Spring Boot中,条件(Condition)是一种机制,它允许您基于应用程序中的配置或环境来自动装配或排除bean或配置类。条件是在应用程序启动期间评估的,如果条件为真,则相应的bean或配置类将被创建或注册,否则将被忽略。
Spring Boot为我们提供了许多内置条件,例如@ConditionalOnClass、@ConditionalOnBean和@ConditionalOnProperty等,这些条件可以根据我们的需要进行自定义和组合。
使用条件注解可以提高应用程序的可移植性,让应用程序在不同的环境中以不同的方式运行。例如,在测试环境中,我们可以使用虚拟的依赖来代替实际依赖,以便进行快速测试。在生产环境中,我们可以启用安全功能以保护应用程序。
2 Condition
做为条件注解的顶级接口,代码如下:
@FunctionalInterface
public interface Condition {
/**
* 条件匹配,注册被注解的组件;否则不注册
* @param context 条件上下文
* @param metadata 被注解的类或方法的元数据
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
看下它的继承关系如下图2-1所示:
其子类SpringBootConditon实现该方法,代码如下:
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
ConditionOutcome outcome = getMatchOutcome(context, metadata);
logOutcome(classOrMethodName, outcome);
recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();
}
// 省略异常处理
}
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
也就是说具体的条件判断逻辑方法getMatchOutcome由其子类实现,那么下面我们就来看看不同的条件注解的判断逻辑。
2 OnClassCondition
OnClassCondition是实现Spring Boot @ConditionalOnClass、@ConditionalOnMissing注解的底层实现
2.1 @ConditionalOnClass
@ConditionalOnClass是一个Spring Boot提供的条件注解之一。当指定的类路径下有指定的类时,该注解修饰的配置类或Bean才会被加载。如果类不存在,则该配置类或Bean将不会被加载。
源代码2.1-1如下所示:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
/**
* 指定类路径下存在的类名
*/
Class<?>[] value() default {};
/**
* 指定必需的类路径下类的字符串表示
*/
String[] name() default {};
}
我们继续看下OnClassCondition#getMatchOutcome()方法,代码2.1-2如下所示:
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
// 获取ConditionalOnClass注解value和name对应的候选类
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
// 过滤出不存在的候选类
List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
if (!missing.isEmpty()) {
// 如果过来结果不为空,返回没匹配结果
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class", "required classes")
.items(Style.QUOTE, missing));
}
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
.found("required class", "required classes")
.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
}
// 获取ConditionalOnMissingClass注解value和name对应的候选类
List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
// 过滤出存在的候选类
List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
if (!present.isEmpty()) {
// 如果过来结果不为空,返回没匹配结果
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
.found("unwanted class", "unwanted classes")
.items(Style.QUOTE, present));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
.didNotFind("unwanted class", "unwanted classes")
.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
}
// 返回匹配上的结果
return ConditionOutcome.match(matchMessage);
}
- ConditionalOnClass和ConditionalOnMissingClass条件判断逻辑为同一个类的同一个方法;
- 执行相似的判断逻辑
- 获取对应注解value或者name的值(候选类);
- 通过过滤候选类,判断过滤不符合条件的候选类;
- 结果不为空,即条件不符合,返回没匹配上的结果;否则返回匹配上的结果。
获取候选类getCandidates()方法源代码2.1-3如下所示:
private List<String> getCandidates(AnnotatedTypeMetadata metadata, Class<?> annotationType) {
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);
if (attributes == null) {
return null;
}
List<String> candidates = new ArrayList<>();
addAll(candidates, attributes.get("value"));
addAll(candidates, attributes.get("name"));
return candidates;
}
- 获取注解属性value和name的值
不符合条件候选类过滤filter()方法源代码2.1-4如下所示:
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());
for (String candidate : classNames) {
if (classNameFilter.matches(candidate, classLoader)) {
// 符合过滤条件,加入集合
matches.add(candidate);
}
}
return matches;
}
下面我们继续看下ClassNameFilter源代码2.1-5如下所示:
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;
}
}
}
判断是否存在isPresen()执行逻辑:
- resove通过加载器解析类名;
- 没抛出异常,说明类路径下该类存在;否则说明类不存在。
我们继续追踪resolve()源代码2.1-6如下所示:
protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
if (classLoader != null) {
return Class.forName(className, false, classLoader);
}
return Class.forName(className);
}
Class.forName眼熟的很,不解释(因为底层原理暂时没研究😄)
2.2 @ConditionalOnMissingClass
@ConditionalOnMissingClass是Spring Boot提供的一个条件注解之一,用于在指定的类不存在于classpath中时才装配bean或配置类。该注解可以用于避免由于某些依赖项缺失而导致应用程序无法启动的情况。
注解条件相同@Conditional(OnBeanCondition.class)
,底层源码执行同上,不在详述,不同的是在获取候选类时传递的注解和过滤时候的条件不同,其他相同。
3 OnBeanCondition
OnBeanCondition是实现Spring Boot @ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnSingleCandidate注解的底层实现。
3.1 @ConditionalOnBean
@ConditionalOnBean是Spring Boot提供的一个条件注解之一,用于在指定的bean存在于应用程序上下文中时才装配bean或配置类。该注解可以用于确保特定的bean已经被装配才能创建和装配其他bean。
看下OnBeanCondition#getMatchOutcome()方法源代码3.1-1如下所示:
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
// 获取被注解类上所有注解
MergedAnnotations annotations = metadata.getAnnotations();
// 判断是否存在ConditionalOnBean注解
if (annotations.isPresent(ConditionalOnBean.class)) {
Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
// 判断是否有任意一个没匹配
if (!matchResult.isAllMatched()) {
// 返回没匹配原因
String reason = createOnBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(spec.message().because(reason));
}
matchMessage = spec.message(matchMessage)
.found("bean", "beans")
.items(Style.QUOTE, matchResult.getNamesOfAllMatches());
}
// 判断是否存在ConditionalOnSingleCandidate注解
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
MatchResult matchResult = getMatchingBeans(context, spec);
// 判断是否有任意一个没匹配
if (!matchResult.isAllMatched()) {
return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
}
Set<String> allBeans = matchResult.getNamesOfAllMatches();
// 判断匹配bean是否只有1个
if (allBeans.size() == 1) {
// 返回匹配上信息
matchMessage = spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans);
}
else {
// 匹配多个bean
// 获取有@Primary标记的bean
List<String> primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans,
spec.getStrategy() == SearchStrategy.ALL);
// 判断有@Primary注解的beans是否为空
if (primaryBeans.isEmpty()) {
// 返回没匹配上信息
return ConditionOutcome
.noMatch(spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans));
}
// 判断有@Primary注解的beans数量是否大于1
if (primaryBeans.size() > 1) {
// 返回没匹配上信息
return ConditionOutcome
.noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans));
}
// 匹配多个beans,但是只有1个@Primary bean ,返回匹配上信息
matchMessage = spec.message(matchMessage)
.found("a single primary bean '" + primaryBeans.get(0) + "' from beans")
.items(Style.QUOTE, allBeans);
}
}
// 判断是否存在ConditionalOnMissingBean注解
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
ConditionalOnMissingBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
// 判断是否匹配上任意一个值
if (matchResult.isAnyMatched()) {
String reason = createOnMissingBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(spec.message().because(reason));
}
matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
}
return ConditionOutcome.match(matchMessage);
}
3.2 @ConditionalOnMissingBean
@ConditionalOnMissingBean是Spring Boot提供的一个条件注解之一,用于在指定的bean不存在于应用程序上下文中时才装配bean或配置类。该注解可以用于避免重复创建bean或覆盖应用程序上下文中的bean。
3.3 @ConditionalOnSingleCandidate
@ConditionalOnSingleCandidate是Spring Boot提供的一个条件注解之一,用于在应用程序上下文中只有一个候选bean时才启用bean或配置类。如果存在多个候选bean或没有候选bean,则不满足条件,bean或配置类将不会被启用。
该注解通常与@Autowired或@Qualifier注解结合使用,用于指定依赖注入的bean,并确保只有一个符合条件的候选bean存在。
例如,以下代码展示了如何使用@ConditionalOnSingleCandidate注解,确保在应用程序上下文中只有一个实现了DataSource接口的bean时才启用MyRepository bean:
@Configuration
public class MyConfig {
@Bean
@ConditionalOnSingleCandidate(DataSource.class)
public MyRepository myRepository(DataSource dataSource) {
// 配置MyRepository bean
}
}
在这个例子中,@ConditionalOnSingleCandidate注解用于指定DataSource.class作为候选bean的类型。只有当应用程序上下文中存在唯一一个实现了DataSource接口的bean时,myRepository bean才会被启用。
注意,如果应用程序上下文中存在多个候选bean或没有候选bean,@ConditionalOnSingleCandidate条件将不会满足,myRepository bean将不会被启用。
@ConditionalOnSingleCandidate注解还可以与其他条件注解组合使用,以实现更复杂的条件判断。例如,可以将@ConditionalOnSingleCandidate与@ConditionalOnBean注解组合使用,以确保只有在特定bean存在且该bean是唯一候选bean时才启用bean或配置类。
4 OnPropertyCondition
4.1 简介
OnPropertyCondition是Spring Framework提供的一个条件注解之一,它用于在应用程序的配置文件中指定的属性满足条件时才启用bean或配置类。OnPropertyCondition是实现Spring Boot @ConditionalOnProperty注解的底层实现之一。
4.2 @ConditionalOnProperty
- 简介
@ConditionalOnProperties是Spring Boot提供的一个条件注解之一,用于在应用程序的配置文件中指定的属性满足条件时才启用bean或配置类。它可以根据配置文件中的属性值来决定是否装配bean或配置类。
- 匹配规则
使用@ConditionalOnProperties注解时,需要指定一个或多个属性键值对作为参数。当这些属性在应用程序的配置文件中存在,并且属性值与指定的值匹配时,条件被认为是满足的,bean或配置类将被启用。
- 源码分析
@ConditionalOnProperty源代码分析
package org.springframework.boot.autoconfigure.condition;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
/**
* names的别名
* @return the names
*/
String[] value() default {};
/**
* 属性前缀
* @return the prefix
*/
String prefix() default "";
/**
* 属性key的名称,如果有前缀,key拼接规则:前缀.名称
*
* 使用-分隔名称
* @return the names
*/
String[] name() default {};
/**
* 期望的属性值
* @return the expected value
*/
String havingValue() default "";
/**
* 如果属性没配置是否要匹配,默认false
* @return if the condition should match if the property is missing
*/
boolean matchIfMissing() default false;
}
havingValue(期望值)与property vlaue(配置值)匹配规则表如下所示:
Property Value | {@code havingValue=""} | {@code havingValue="true"} | {@code havingValue="false"} | {@code havingValue="foo"} |
---|---|---|---|---|
{@code "true"} | yes | yes | no | no |
{@code "false"} | no | no | yes | no |
{@code "foo"} | yes | no | no | yes |
说明:
- matchIfMissing默认为false,匹配结果如上表所示;如果matcIfMissing为true,当配置项缺失或者如上表为yes的时候,结果为匹配,其他情况就是不匹配。
下面我们通过分析OnPropertyCondition源码看看为什么有上面的匹配规则,源码如下4.2-1所示:
// 省略
@Order(Ordered.HIGHEST_PRECEDENCE + 40)
class OnPropertyCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取注解属性
List<AnnotationAttributes> allAnnotationAttributes = metadata.getAnnotations()
.stream(ConditionalOnProperty.class.getName())
.filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes))
.map(MergedAnnotation::asAnnotationAttributes)
.collect(Collectors.toList());
List<ConditionMessage> noMatch = new ArrayList<>();
List<ConditionMessage> match = new ArrayList<>();
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
// 根据环境变量中配置信息确定是否匹配
ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
}
// 如果不匹配集合非空,返回不匹配结果
if (!noMatch.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
}
// 返回匹配结果
return ConditionOutcome.match(ConditionMessage.of(match));
}
private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) {
// 创建规格对象
Spec spec = new Spec(annotationAttributes);
List<String> missingProperties = new ArrayList<>();
List<String> nonMatchingProperties = new ArrayList<>();
// 搜集属性
spec.collectProperties(resolver, missingProperties, nonMatchingProperties);
// 如果缺死属性集合不为空,返回未找到信息
if (!missingProperties.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
.didNotFind("property", "properties")
.items(Style.QUOTE, missingProperties));
}
// 如果不匹配属性集合不为空,返回不匹配信息
if (!nonMatchingProperties.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
.found("different value in property", "different value in properties")
.items(Style.QUOTE, nonMatchingProperties));
}
// 返回匹配信息
return ConditionOutcome
.match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched"));
}
private static class Spec {
// 前缀
private final String prefix;
// 期望值
private final String havingValue;
// 名称
private final String[] names;
// 如果丢失是否匹配
private final boolean matchIfMissing;
Spec(AnnotationAttributes annotationAttributes) {
String prefix = annotationAttributes.getString("prefix").trim();
if (StringUtils.hasText(prefix) && !prefix.endsWith(".")) {
prefix = prefix + ".";
}
this.prefix = prefix;
this.havingValue = annotationAttributes.getString("havingValue");
this.names = getNames(annotationAttributes);
this.matchIfMissing = annotationAttributes.getBoolean("matchIfMissing");
}
// 获取注解中value或者name属性
private String[] getNames(Map<String, Object> annotationAttributes) {
String[] value = (String[]) annotationAttributes.get("value");
String[] name = (String[]) annotationAttributes.get("name");
Assert.state(value.length > 0 || name.length > 0,
"The name or value attribute of @ConditionalOnProperty must be specified");
Assert.state(value.length == 0 || name.length == 0,
"The name and value attributes of @ConditionalOnProperty are exclusive");
return (value.length > 0) ? value : name;
}
// 搜集属性值
private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) {
// 遍历注解属性名称
for (String name : this.names) {
// 拼接prefix组成配置key
String key = this.prefix + name;
// 判断配置项中算法包含该key
if (resolver.containsProperty(key)) {
// 如果获取key对应的值与期望值不匹配
if (!isMatch(resolver.getProperty(key), this.havingValue)) {
// 加入不匹配集合
nonMatching.add(name);
}
}
else {
// 判断如果matchIfMissing为false
if (!this.matchIfMissing) {
// 加入丢失集合
missing.add(name);
}
}
}
}
// 是否匹配
private boolean isMatch(String value, String requiredValue) {
if (StringUtils.hasLength(requiredValue)) {
return requiredValue.equalsIgnoreCase(value);
}
return !"false".equalsIgnoreCase(value);
}
// 省略
}
}
- 示例
例如,以下代码展示了如何使用@ConditionalOnProperties注解,仅当配置文件中的属性app.feature.enabled
值为true
时,才启用MyFeature类:
@Configuration
@ConditionalOnProperties("app.feature.enabled=true")
public class MyConfig {
@Bean
public MyFeature myFeature() {
// 配置MyFeature bean
}
}
在这个例子中,@ConditionalOnProperties注解的参数指定了属性键值对,即app.feature.enabled=true
。只有当应用程序的配置文件中存在名为app.feature.enabled
的属性,并且其值为true
时,MyConfig配置类才会被启用。
@ConditionalOnProperties注解还支持指定多个属性键值对,以及使用逻辑运算符(例如AND、OR)来组合条件。例如:
@Configuration
@ConditionalOnProperties({
"app.feature.enabled=true",
"app.feature.someProperty=abc"
})
public class MyConfig {
// ...
}
在这个例子中,只有当配置文件中同时存在app.feature.enabled=true
和app.feature.someProperty=abc
这两个属性时,MyConfig配置类才会被启用。
需要注意的是,如果配置文件中没有指定的属性,或者属性值与指定的值不匹配,@ConditionalOnProperties条件将不会满足,相应的bean或配置类将不会被启用。
5 OnJavaCondition
- 简介
OnJavaCondition是Spring Framework提供的一个条件注解之一,它用于在JVM java版本满足注解指定的java版本范围时才启用bean或配置类。OnJavaCondition是实现Spring Boot @ConditionalOnJava注解的底层实现之一。
5.1 @ConditionalOnJava
-
源代码5.1-1如下所示
-
package org.springframework.boot.autoconfigure.condition; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.system.JavaVersion; import org.springframework.context.annotation.Conditional; /** * 当前JVM java版本是否匹配指定的java版本范围 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnJavaCondition.class) public @interface ConditionalOnJava { /** * java版本范围,默认大于等于 */ Range range() default Range.EQUAL_OR_NEWER; /** * java版本 */ JavaVersion value(); /** * java版本范围枚举 */ enum Range { /** * 大于等于 */ EQUAL_OR_NEWER, /** * 小于 */ OLDER_THAN } }
-
匹配规则
使用@ConditionalOnJava注解时,需要指定范围和java版本。当前JVM java版本在知道的范围时时,条件被认为是满足的,bean或配置类将被启用。
- 示例
@Component
@ConditionalOnJava(range = EQUAL_OR_NEWER, value = JavaVersion.EIGHT)
public class UserService {
// 省略
}
JVM java版本大于等于1.8时,当前bean才会被启用。
5.2 源码解析
OnJavaCondition源代码如下5.2-1所示
package org.springframework.boot.autoconfigure.condition;
import java.util.Map;
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.Range;
import org.springframework.boot.system.JavaVersion;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnJavaCondition extends SpringBootCondition {
// 获取当前JVM java版本
private static final JavaVersion JVM_VERSION = JavaVersion.getJavaVersion();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取ConditionalOnJava注解属性
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnJava.class.getName());
// 获取注解指定范围
Range range = (Range) attributes.get("range");
// 获取注解指定java版本
JavaVersion version = (JavaVersion) attributes.get("value");
// 判断是否匹配
return getMatchOutcome(range, JVM_VERSION, version);
}
// 判断是否匹配
protected ConditionOutcome getMatchOutcome(Range range, JavaVersion runningVersion, JavaVersion version) {
// 判断是否匹配
boolean match = isWithin(runningVersion, range, version);
String expected = String.format((range != Range.EQUAL_OR_NEWER) ? "(older than %s)" : "(%s or newer)", version);
ConditionMessage message = ConditionMessage.forCondition(ConditionalOnJava.class, expected)
.foundExactly(runningVersion);
return new ConditionOutcome(match, message);
}
/**
* 判断当前java版本是否在指定的java版本范围内
*/
private boolean isWithin(JavaVersion runningVersion, Range range, JavaVersion version) {
// 判断范围是否为大于等于
if (range == Range.EQUAL_OR_NEWER) {
return runningVersion.isEqualOrNewerThan(version);
}
// 判断范围是否为小于
if (range == Range.OLDER_THAN) {
return runningVersion.isOlderThan(version);
}
throw new IllegalStateException("Unknown range " + range);
}
}
判断逻辑我们需要看下JavaVersion源代码如下5.2-2所示:
package org.springframework.boot.system;
import java.io.Console;
import java.lang.invoke.MethodHandles;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Future;
import java.util.stream.Stream;
import org.springframework.util.ClassUtils;
/**
* Known Java versions.
*
* @author Oliver Gierke
* @author Phillip Webb
* @since 2.0.0
*/
public enum JavaVersion {
/**
* Java 1.8.
*/
EIGHT("1.8", Optional.class, "empty"),
/**
* Java 9.
*/
NINE("9", Optional.class, "stream"),
/**
* Java 10.
*/
TEN("10", Optional.class, "orElseThrow"),
/**
* Java 11.
*/
ELEVEN("11", String.class, "strip"),
/**
* Java 12.
*/
TWELVE("12", String.class, "describeConstable"),
/**
* Java 13.
*/
THIRTEEN("13", String.class, "stripIndent"),
/**
* Java 14.
*/
FOURTEEN("14", MethodHandles.Lookup.class, "hasFullPrivilegeAccess"),
/**
* Java 15.
*/
FIFTEEN("15", CharSequence.class, "isEmpty"),
/**
* Java 16.
*/
SIXTEEN("16", Stream.class, "toList"),
/**
* Java 17.
*/
SEVENTEEN("17", Console.class, "charset"),
/**
* Java 18.
*/
EIGHTEEN("18", Duration.class, "isPositive"),
/**
* Java 19.
*/
NINETEEN("19", Future.class, "state");
private final String name;
private final boolean available;
JavaVersion(String name, Class<?> clazz, String methodName) {
this.name = name;
// 通过加载版本对应的类,判断是否有java版本对应的特定方法,确定java版本是否可用
this.available = ClassUtils.hasMethod(clazz, methodName);
}
@Override
public String toString() {
return this.name;
}
/**
* 获取当前java版本
*/
public static JavaVersion getJavaVersion() {
// 获取所有java枚举版本
List<JavaVersion> candidates = Arrays.asList(JavaVersion.values());
// 逆序排列,默认时java版本升序排列
Collections.reverse(candidates);
for (JavaVersion candidate : candidates) {
// 返回第一个可用java版本
if (candidate.available) {
return candidate;
}
}
return EIGHT;
}
/**
* 是否大于等于给定版本
*/
public boolean isEqualOrNewerThan(JavaVersion version) {
return compareTo(version) >= 0;
}
/**
* 是否小于给定的版本
*/
public boolean isOlderThan(JavaVersion version) {
return compareTo(version) < 0;
}
}
判断逻辑为:判断指定类是否有版本特有的方法(通过遍历类的方法,通过方法名equals判断),来决定是否是对应版本。
思考:
- 为什么要通过上述方式获取java版本?
- 有没有更简单的方式呢?比如通过设置唯一值判断java版本?
6 OnWebApplicationCondition
- 简介
OnWebApplicationCondition是Spring Framework提供的一个条件注解之一,它用于在应用是否为web应用或者指定web类型应用时才启用bean或配置类。OnWebApplicationCondition是实现Spring Boot @ConditionalOnWebApplication注解的底层实现之一。
6.1 @ConditionalOnWebApplication
-
ConditionalOnWebApplication源代码如下6.1-1所示:
-
package org.springframework.boot.autoconfigure.condition; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Conditional; @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnWebApplicationCondition.class) public @interface ConditionalOnWebApplication { /** * web应用类型,默认任意 */ Type type() default Type.ANY; /** * web应用类型 */ enum Type { /** * 任意web应用类型 */ ANY, /** * sevlet web应用 */ SERVLET, /** * reactive web应用 */ REACTIVE } }
-
匹配规则
- Type.ANY:当前应用时sevlet或者reactive web应用匹配;
- Type.SERVLET:当前为servlet web应用时匹配;
- Type.REACTIVE:当前为reactive web应用时匹配。
6.2 源码解析
OnWebApplicationCondition源代码如下6.2-1所示:
package org.springframework.boot.autoconfigure.condition;
import java.util.Map;
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebEnvironment;
import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.ConfigurableWebEnvironment;
import org.springframework.web.context.WebApplicationContext;
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends FilteringSpringBootCondition {
// 运行servlet 应用加载的类
private static final String SERVLET_WEB_APPLICATION_CLASS = "org.springframework.web.context.support.GenericWebApplicationContext";
// 运行reactive 应用加载的类
private static final String REACTIVE_WEB_APPLICATION_CLASS = "org.springframework.web.reactive.HandlerResult";
@Override
protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
for (int i = 0; i < outcomes.length; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
outcomes[i] = getOutcome(
autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication"));
}
}
return outcomes;
}
private ConditionOutcome getOutcome(String type) {
if (type == null) {
return null;
}
ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class);
if (ConditionalOnWebApplication.Type.SERVLET.name().equals(type)) {
if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
}
}
if (ConditionalOnWebApplication.Type.REACTIVE.name().equals(type)) {
if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
return ConditionOutcome.noMatch(message.didNotFind("reactive web application classes").atAll());
}
}
if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())
&& !ClassUtils.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
return ConditionOutcome.noMatch(message.didNotFind("reactive or servlet web application classes").atAll());
}
return null;
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 是否有ConditionalOnWebApplication注解
boolean required = metadata.isAnnotated(ConditionalOnWebApplication.class.getName());
// 是否是web应用
ConditionOutcome outcome = isWebApplication(context, metadata, required);
// 判断是否有ConditionalOnWebApplication注解且不匹配
if (required && !outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
// 判断没有ConditionalOnWebApplication注解且匹配
if (!required && outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
// 返回匹配结果
return ConditionOutcome.match(outcome.getConditionMessage());
}
private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata,
boolean required) {
switch (deduceType(metadata)) {
case SERVLET:
return isServletWebApplication(context);
case REACTIVE:
return isReactiveWebApplication(context);
default:
return isAnyWebApplication(context, required);
}
}
private ConditionOutcome isAnyWebApplication(ConditionContext context, boolean required) {
ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class,
required ? "(required)" : "");
ConditionOutcome servletOutcome = isServletWebApplication(context);
if (servletOutcome.isMatch() && required) {
return new ConditionOutcome(servletOutcome.isMatch(), message.because(servletOutcome.getMessage()));
}
ConditionOutcome reactiveOutcome = isReactiveWebApplication(context);
if (reactiveOutcome.isMatch() && required) {
return new ConditionOutcome(reactiveOutcome.isMatch(), message.because(reactiveOutcome.getMessage()));
}
return new ConditionOutcome(servletOutcome.isMatch() || reactiveOutcome.isMatch(),
message.because(servletOutcome.getMessage()).append("and").append(reactiveOutcome.getMessage()));
}
// 是否是servlet web应用
private ConditionOutcome isServletWebApplication(ConditionContext context) {
ConditionMessage.Builder message = ConditionMessage.forCondition("");
// 是否加载指定类
if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, context.getClassLoader())) {
return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
}
// 通过条件上下文获取beanFactory如果不为空
if (context.getBeanFactory() != null) {
// 获取作用域
String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
// 判断作用域包含session
if (ObjectUtils.containsElement(scopes, "session")) {
return ConditionOutcome.match(message.foundExactly("'session' scope"));
}
}
// 通过条件上下文获取环境变量对象,判断是否是ConfigurableWebEnvironment实例
if (context.getEnvironment() instanceof ConfigurableWebEnvironment) {
return ConditionOutcome.match(message.foundExactly("ConfigurableWebEnvironment"));
}
// 通过条件上下文获取资源加载器对象,判断是否是WebApplicationContext实例
if (context.getResourceLoader() instanceof WebApplicationContext) {
return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
}
// 否则返回不匹配结果
return ConditionOutcome.noMatch(message.because("not a servlet web application"));
}
// 是否是reactive web应用
private ConditionOutcome isReactiveWebApplication(ConditionContext context) {
ConditionMessage.Builder message = ConditionMessage.forCondition("");
if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, context.getClassLoader())) {
return ConditionOutcome.noMatch(message.didNotFind("reactive web application classes").atAll());
}
if (context.getEnvironment() instanceof ConfigurableReactiveWebEnvironment) {
return ConditionOutcome.match(message.foundExactly("ConfigurableReactiveWebEnvironment"));
}
if (context.getResourceLoader() instanceof ReactiveWebApplicationContext) {
return ConditionOutcome.match(message.foundExactly("ReactiveWebApplicationContext"));
}
return ConditionOutcome.noMatch(message.because("not a reactive web application"));
}
// 获取ConditionalOnWebApplication注解type属性值
private Type deduceType(AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnWebApplication.class.getName());
if (attributes != null) {
return (Type) attributes.get("type");
}
return Type.ANY;
}
}
判断逻辑:判断是否加载指定的类或者上下文满足特定条件。
结语
如果小伙伴什么问题或者指教,欢迎交流。
❓QQ:806797785
⭐️源代码仓库地址:https://gitee.com/gaogzhen/springboot-custom
参考:
[1]Springboot视频教程[CP/OL].P14-18.