Spring中的@Conditional注解实现分析
作者:it_lihongmin
@Conditional注解实现分析
@Conditional是Spring 4出现的注解,但是真正露出价值的是Spring Boot的扩展@ConditionalOnBean等。但是任然使用的是Spring框架进行处理,并没有做太多定制的东西,所以还是先看看@Conditional注解的实现。
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { /** * All {@link Condition Conditions} that must {@linkplain Condition#matches match} * in order for the component to be registered. */ Class<? extends Condition>[] value(); }
先看看@Conditional注解的结构比较简单,只需要定义一个Condition子类即可,并且说明只有满足了当前Condition的matches方法时才会将当前@Component注册成Bean。那么再看看Condition接口和体系。
/** * @since 4.0 * @see ConfigurationCondition * @see Conditional * @see ConditionContext */ @FunctionalInterface public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
当前会传入ConditionContext和AnnotatedTypeMetadata进行回调,返回是否匹配,如果不匹配则不会注册成Bean。但是这是在哪里进行回调的呢?
ConfigurationClassParser # processConfigurationClass (ConfigurationClassParser # doProcessConfigurationClass等)
ConditionEvaluator # shouldSkip
比较清楚了,又是在处理@Import、@ComponentScan、ImportSelector等的处理类ConfigurationClassParser执行时机比较清楚了
再看看Condition的结构体系:
大致有四类
1)、ProfileCondition,项目启动后Profile信息存放在ApplicationContext的Environment中,能拿到两者之一就可以判断。
2)、ConfigurationCondition
public interface ConfigurationCondition extends Condition { // 定义了子类必须实现,返回下面枚举的一种 ConfigurationPhase getConfigurationPhase(); // 判断阶段 enum ConfigurationPhase { // Conponent阶段,即将@Component加入到BeanFactory PARSE_CONFIGURATION, // 只有通过getBean才能正在将Bean注册到Ioc容器中。前提是要将BeanDefinition添加到 // BeanFactory中 REGISTER_BEAN } }
3)、ConditionEvalutionReport,Spring Boot报表相关
4)、SpringBootCondition,直接是Spring Boot中扩展的。下一篇博客,具体分析 @ConditionalOnBean等再具体分析。
几个的角色比较清楚了,只要一个@Conditional注解,注解的属性为@Condition或其子类。根据回调@Condition的matches方法,即可判断是否将注册成Bean。
先看看回调时机,都是在处理@Component、@ComponentSacn、ImportSelector等情况注册Bean时。
由于情况比较复杂,可能@Component上有@ComponentScan,则会递归进行处理,总之都会先调用ConfigurationClassParser的ConditionEvaluator conditionEvaluator的shouldSkip方法判断是否跳过。
比如:
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } // 省略其余代码 }
而conditionEvaluator在ConfigurationClassParser的构造器中被初始化,如下:
this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
先看看ConditionEvaluator的类结构
class ConditionEvaluator { private final ConditionContextImpl context; public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry, @Nullable Environment environment, @Nullable ResourceLoader resourceLoader) { this.context = new ConditionContextImpl(registry, environment, resourceLoader); } // 省略其他方法 private static class ConditionContextImpl implements ConditionContext { private final BeanDefinitionRegistry registry; private final ConfigurableListableBeanFactory beanFactory; private final Environment environment; private final ResourceLoader resourceLoader; private final ClassLoader classLoader; public ConditionContextImpl(@Nullable BeanDefinitionRegistry registry, @Nullable Environment environment, @Nullable ResourceLoader resourceLoader) { this.registry = registry; this.beanFactory = deduceBeanFactory(registry); this.environment = (environment != null ? environment : deduceEnvironment(registry)); this.resourceLoader = (resourceLoader != null ? resourceLoader : deduceResourceLoader(registry)); this.classLoader = deduceClassLoader(resourceLoader, this.beanFactory); } } }
也就是说,在ConfigurationClassParser构造器中初始化ConditionEvaluator时候,就初始化了内部类ConditionContextImpl,并且传入了BeanFactory(Bean是否存在可以通过工厂进行判断)、Environment(环境配置、Profile等存放在其中)、ResourceLoader(Spring的Resource加载器)、ClassLoader(类加载器)等。
到这里就比较清楚了,回调Condition的matches接口时传入这些组件的类ConditionContextImpl,要实现@ConditionalOnBean、@OnPropertyCondition、@Profile、@ConditionalOnClass等都比较简单了。
在调用Condition的matches时,不仅传入了ConditionContextImpl,还出入了AnnotatedTypeMetadata,这是当前注解结合被Spring封装的注解元信息。理解比较抽象,比如自动装配EmbeddedTomcat时需要同时存在Servlet、Tomcat、upgradeProtocol类;并且没有将ServletWebServerFactory注册成Bean,此时Component才能真正生效,如下:
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedTomcat { // 省略其他代码 }
具体再看看ConditionEvaluator的shouldSkip方法的实现:
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { // 注解信息不能为空, 并且必须有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); } List<Condition> conditions = new ArrayList<>(); // 获取多有的Condition类型,如上面的EmbeddedTomcat同时需要多个条件成立 for (String[] conditionClasses : getConditionClasses(metadata)) { for (String conditionClass : conditionClasses) { Condition condition = getCondition(conditionClass, this.context.getClassLoader()); conditions.add(condition); } } // 条件排序(根据Spring的那三个排序方式定义) AnnotationAwareOrderComparator.sort(conditions); for (Condition condition : conditions) { ConfigurationPhase requiredPhase = null; if (condition instanceof ConfigurationCondition) { requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); } // 判断阶段为空(非ConfigurationCondition的子类)、不需要判断阶段,则直接返回true // 否则才调用matches接口判断 if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true; } } return false; }
1)、注解信息不能为空, 并且必须有Conditional或者其子类
2)、阶段为null,则根据情况设置阶段后再递归调用该方法
3)、获取所有的Condition列表
4)、进行排序
5)、遍历Condition,是否在该阶段进行判断。需要则再调用该Condition的matches方法
总结:添加了@Conditional或者@ConditionXXX注解,其value值会对应一个Condition或者子类的Class。在处理@ComponentScan、ImportSelector等时会根据判断阶段,调用Condition的matches方法判断是否进行注册成Bean。从而实现各种复杂的动态判断注册成Bean的情况。
到此这篇关于Spring中的@Conditional注解实现分析的文章就介绍到这了,更多相关@Conditional注解实现分析内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!