springboot注解–基础–2.1–Conditional
代码位置
https://gitee.com/DanShenGuiZu/learnDemo/tree/master/annotation-learn/annotation-learn1
1、介绍
- 只有满足条件的类A才能注册到容器中
- 类A必须实现Conditional接口,因为注解的value是Condition的子类或实现类
1.1、注解内容
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* 只有Condition的实现类,满足匹配条件,才能注册到容器中
*/
Class<? extends Condition>[] value();
}
1.2、Conditions接口
@FunctionalInterface
public interface Condition {
/**
* 确定条件是否匹配,该方法决定了是否要注册相应的bean对象
* @param context:condition实现类的上下文
* @param metadata:正在检查的类或方法的元数据
* @return true或者false
* 如果返回true:@Conditional修饰的类可以注册bean到容器中
* 如果返回false:@Conditional修饰的类不可以注册bean到容器中
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
matches方法中有两个参数,参数类型分别是ConditionContext和AnnotatedTypeMetadata,
它们分别用来获取一些环境信息和注解元数据从而用在matches方法中判断是否符合条件。
1.2.1、ConditionContext
主要是跟Condition的上下文有关
用来获取Registry,BeanFactory,Environment,ResourceLoader和ClassLoader等
源码
public interface ConditionContext {
BeanDefinitionRegistry getRegistry();
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
Environment getEnvironment();
ResourceLoader getResourceLoader();
@Nullable
ClassLoader getClassLoader();
}
1.2.1、AnnotatedTypeMetadata
可以拿到某个注解的一些元数据,和对应的注解属性
源码
public interface AnnotatedTypeMetadata {
boolean isAnnotated(String annotationName);
Map<String, Object> getAnnotationAttributes(String annotationName);
Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
}
2、测试
2.1、代码
package com.example.main.Conditional.Conditional;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class TestConditional implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
System.out.println("类元数据---->:" + metadata.toString());
System.out.println("条件上下文---->:" + context.toString());
// 如果ioc容器中有test1实例,我就注册当前类到容器中
if (context.getRegistry().containsBeanDefinition("test1")) {
System.out.println("------容器中存在test1实例,当前类注册到容器中-------");
return true;
}
System.out.println("------容器中不存在test1实例,当前类不注册到容器中-------");
return false;
}
}
package com.example.main.Conditional.Conditional;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class TestConfig {
@Bean
Test1 test1() {
System.out.println("---------test1---------");
return new Test1();
}
@Bean
@Conditional(TestConditional.class)
Test2 test2() {
System.out.println("---------test2---------");
return new Test2();
}
public class Test1 {
}
public class Test2 {
}
public static void main(String[] args) {
//设置日志级别,去掉我不要的信息
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
List<Logger> loggerList = loggerContext.getLoggerList();
loggerList.forEach(logger -> {
logger.setLevel(Level.ERROR);
});
//加载上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class);
}
}
2.2、结果
3、原理分析
3.1、类元数据,条件上下文debug
3.2、原理解析(debug模式)
3.2.1、shouldSkip方法
// 这个方法主要是如果是解析阶段则跳过,如果是注册阶段则不跳过
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
// 若没有被@Conditional或其派生注解所标注,则不会跳过
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
// 没有指定phase(阶段),注意phase可以分为PARSE_CONFIGURATION(解析阶段)或REGISTER_BEAN(注册阶段)类型
if (phase == null) {
// 若标有@Component,@Import,@Bean或@Configuration等注解的话,则说明是PARSE_CONFIGURATION类型
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
// 否则是REGISTER_BEAN类型
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
List<Condition> conditions = new ArrayList<>();
// TODO 获得所有标有@Conditional注解或其派生注解里面的Condition接口实现类并实例化成对象。
// 比如@Conditional(OnBeanCondition.class)则获得OnBeanCondition.class,OnBeanCondition.class实现了Condition接口
for (String[] conditionClasses : getConditionClasses(metadata)) {
// 将类实例化成对象
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
// 排序,即按照Condition的优先级进行排序
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
// 从condition中获得对bean是解析阶段还是注册阶段
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
// (若requiredPhase为null或获取的阶段类型正是当前阶段类型)且不符合condition的matches条件,则跳过
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
shouldSkip逻辑
- 如果是解析阶段则跳过,如果是注册阶段则不跳过;
- 如果是在注册阶段即REGISTER_BEAN阶段的话,此时会得到所有的Condition接口的具体实现类并实例化这些实现类,然后再执行下面关键的代码进行判断是否需要跳过。
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
上面代码最重要的逻辑是调用了Condition接口的具体实现类的matches方法
若matches返回false,则跳过,不进行注册bean的操作
若matches返回true,则不跳过,进行注册bean的操作;
4、Spring的内置Condition接口实现类
Spring的Condition接口的具体实现类(Ctrl+H)
注意:OnBean2Condition和TestConditional是我本人自己实现的,SpringBootCondition是个抽象类。
发现Spring真正的内置的Condition接口的具体实现类只有ProfileCondition一个,非常非常少,这跟SpringBoot的大量派生条件注解形成了鲜明的对比。
ProfileCondition 跟环境有关,比如我们平时一般有dev,test和prod环境,而ProfileCondition就是判断我们项目配置了哪个环境的。
4.1、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")) {
//通过属性值,设置Profile
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}