在前面一文中,我们讲了SpringBoot启动的几个阶段和自动装配的原理,我们会发现,在Spring中,使用了大量的@ConditionXXX
注解,本文就来介绍下@Condition
注解的原理。
问题:
- ConditionXX注解是做什么的?如何使用?
- 如何自定义Condition?
- Condition实现原理是什么?
一、@ConditionXXX注解的作用
常见的一些@Condition相关的注解如上。
该类注解的作用就是在满足某个条件的时候才将某个类进行实例化并加入到Spring容器中来。
如:
-
@ConditionalOnBean
:当容器中有某个Bean的时候才调用当前被修饰的方法、或类,并加入到容器管理@Configuration public class MyConfiguration { @Bean(name = "haha") public People people(){ return new People(); } @Bean @ConditionalOnBean(name = "haha") public People onPeople(){ System.out.println("上下文有名为haha的bean"); return new People(); } }
启动时就加载到了onPeople方法。如果把上面注入的名为haha的bean去掉,则当前方法就不会被调用。
-
ConditionalOnClass
:当工程中有某个类时才调用@Bean @ConditionalOnClass(name = "com.wml.config.User") public People onClass(){ System.out.println("有User类"); return new People(); }
-
同样的还有对应的Missing注解,与上面两个相反:
@Bean("missingPeople") @ConditionalOnMissingClass(value = "com.wml.pojo.Test") public People onMissingClass(){ System.out.println("上下文没有Test类"); return new People(); } @Bean @ConditionalOnMissingBean(name = "didi") public People missingPeople(){ System.out.println("上下文无名为didi的bean"); return new People(); }
在没有对应的类和Bean的时候才生效。
-
ConditionalOnExpression
:表达式成立才生效@Bean @ConditionalOnExpression(value = "${server.port}==8080") public People onExpression(){ System.out.println("端口为8080"); return new People(); }
-
@ConditionalOnProperty
:配置文件的属性匹配时才生效:@Bean @ConditionalOnProperty(prefix = "spring.application",name = "name",havingValue = "test-application") public People prop(){ System.out.println("应用名为test-application"); return new People(); }
暂且列举这么多。
二、自定义Condition注解
观察上面一些Condition注解,以ConditionalOnMissingBean
为例:
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {
}
其上都使用了一个@Conditional(OnBeanCondition.class)
注解,当括号中的类返回true时才会生效。而里面的类,跟进去,最终都是实现了@Condition
接口:
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
而我们要实现自己的Condition
类,就需要实现该接口,并在matches
方法中定义匹配规则。
如:
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String age=context.getEnvironment().getProperty("my.age");
return age != null && (Integer.valueOf(age) == 22);
}
}
获取配置文件中的my.age
属性,如果没配置或者值不是22,则返回false,否则返回true。
my:
age: 22
@Bean
@Conditional(MyCondition.class)
public People cusCondition(){
System.out.println("自定义Condition生效");
return new People();
}
可以看到自定义的Condition生效了。
而如果改成其他的年龄或不写,则不会生效。
三、Condition原理
在看源码前先猜测下可能是如何实现的?
首先,处理@Configuration、@Import等注解,都是在前面说的那个ConfigurationClassPostProcessor
类中进行集中处理的,而我们的Condition注解,那么可能就需要判断某个类上是否有该条件注解,如果有的话,那么肯定会去拿它的条件,如果条件满足,则正常实例化,如果不满足,则不进行实例化。
那么进来具体看看:
具体处理是在这个类的parser.parse
方法里处理:
一直跟进去,配置类会在下面这个方法处理:
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
.............
}
在方法第一行,首先调用了一个shouldSkip
方法,该方法就是用来判断当前类是否需要跳过,如果需要跳过则不装载到容器中,否则正常装载。
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
//1.如果没有被Conditional注解,则直接返回false,不需要跳过
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
......
List<Condition> conditions = new ArrayList<>();
//2.收集当前类中所有Conditional注解的value值,如我们自定义的MyCondition类名,并通过getCondition获取对应的Condition(实现类),即我们的MyCondition类,加入到conditions集合中
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
//这里就会调用接口的matches方法,如果匹配就返回false,不匹配就返回true,不需要加载到容器中
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
但是可以看到matches实现类只有下面四个:
这里只看SpringBootCondition
,为啥呢?因为那几个Condition类都继承了该抽象类。
如@ConditionalOnClass
的条件类OnClassCondition
:
来到SpringBootCondition
的matches
方法:
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
// 钩子方法,调到用具体子类中方法。ConditionOutcome 这个类里面包装了是否需
// 跳过和打印的日志
ConditionOutcome outcome = getMatchOutcome(context, metadata);
logOutcome(classOrMethodName, outcome);
recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();
}
..............
}
其中ConditionOutcome
中有一个布尔类型的match
字段,该字段就是匹配的结果。这里的getMatchOutcome
是一个模板方法,实现交给了具体的子类,我们就以OnClassCondition
为例看下:
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
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));
}
......
}
这里核心方法就是filter
,会反射定义的需要存在的类,如@ConditionalOnClass(name = "com.wml.config.User")
中的User
,如果出现异常,说明不存在该类,则添加到集合中返回,如果missing
集合不为空,则就是目标类不存在,因此构造一个noMath
,里面的match
的值就是false。
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的matches方法进行匹配,因为传入的是MISSING
类型的,因此进入下面的MISSING
类型的枚举:
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) {
.....
try {
//这里就是通过反射调用className
resolve(className, classLoader);
//反射成功,说明存在,返回true
return true;
}catch (Throwable ex) {
//反射失败,说明不存在,返回false
return false;
}
}
}
如果返回false,那么就会添加到filter
方法的matches
集合中,最终返回,有一个类不存在, 集合就非空,那么就会在getMatchOutcome
返回ConditionOutcome.noMatch
,最终会在shouldSkip
方法返回true
,这样就会跳过将该类或方法构造成BeanDefinition
加入到容器中。
其他几个ConditionXXX
注解的原理都类似,就不赘述了,如@ConditionalOnBean
会从beanFactory
中拿目标bean,如果拿不到说明不存在,如果拿到就说明匹配,返回true,就会将被修饰的类或方法加入到容器。
OK,到这开头的三个问题就都解决了。