@Configuration
class A{
}
@Configuration
class B{
@Bean
@ConditionalOnBean(A.class)
public C getC(){
return new C();
}
}
class C{
}
我们通常能看到类似如上的代码:在注入Bean C的时候,加了一个注解:@ConditionalOnBean(A.class),这个注解想必大家也都明白,就是必须A.class也被注入.其实类似的注解有很多,作用和原理都是差不多的,但是原理是什么呢?下面就以@ConditionalOnBean为主看下:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
// 省略内容,不是关键
}
在注解@ConditionalOnBean的定义中,可以看到上面有个@Conditional注解,作为元注解使用,参数是OnBeanCondition类,下面再看下@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();
}
我们可以看到注解@Conditional的value的类型有泛型约定必须是Condition类型的子类,很显然OnBeanCondition就是Condition的子类,再看下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方法的返回结果,是个布尔值,如果是true,那么就满足条件,false自然就不能满足条件,也就是说子类需要实现matches方法,编写自己的判断逻辑,结合我们上面的例子,就是说如果OnBeanCondition的matches方法按照自己的判断逻辑,如果返回的是true,那么Bean C就可以被注入,否则则不能.
我们这里看下OnBeanCondition类:
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
}
这里的ConfigurationCondition 是继承了上面的Condition,下面看下ConfigurationCondition内容:
public interface ConfigurationCondition extends Condition {
/**
* Return the {@link ConfigurationPhase} in which the condition should be evaluated.
*/
ConfigurationPhase getConfigurationPhase();
/**
* The various configuration phases where the condition could be evaluated.
*/
enum ConfigurationPhase {
/**
* The {@link Condition} should be evaluated as a {@code @Configuration}
* class is being parsed.
* If the condition does not match at this point, the {@code @Configuration}
* class will not be added.
*/
PARSE_CONFIGURATION,
/**
* The {@link Condition} should be evaluated when adding a regular
* (non {@code @Configuration}) bean. The condition will not prevent
* {@code @Configuration} classes from being added.
* At the time that the condition is evaluated, all {@code @Configuration}
* classes will have been parsed.
*/
REGISTER_BEAN
}
}
这个接口是SpringBoot中控制bean能否注入到容器中的核心接口.这个接口相比于Condition,其实就是多了个枚举,这里面的枚举值,是用来控制配置类的解析时机控制.
enum ConfigurationPhase {
/**
* 这个枚举值就是用来控制校验配置类(被@Configuration注解)的解析时做校验的,什么样的场景才会用呢?就比如一个配置类里面有很多配置bean要被注入,
* 但是你希望通过某个条件作为前置条件,如果此时matches返回对的结果是false,那么配置类里面的bean都不会再被解析.
**/
PARSE_CONFIGURATION,
/**
* 这个枚举值就是用来控制Bean(非@Configuration)的注入,如果matches函数返回的是true,则Bean类可以注入成功,
* 如果是false,则整个Bean类就不会再进行解析注入到容器了
**/
REGISTER_BEAN
}
StackOverflow相关解释ConfigurationPhase Two Phase
下面再讲一下@ConditionalOnBean、@ConditionalOnClass等注解是如何起作用的:
其实就是这些注解定义的元注解@Conditional起得作用,springboot只通过ConditionEvaluator来处理@Conditional注解, @Conditional(OnBeanCondition.class)里面不同的 Condition实现类,其实就是对应不同的条件注解的解析生效.就比如@ConditionalOnClass的定义里面元注解@Conditional(OnClassCondition.class)的入参就是OnClassCondition类,只不过这个类实现了自己的判断规则,也就是自己实现了Condition接口的matches方法.