Spring定义Bean类(三) 条件装配 @ConditionalOn*

本文介绍Spring中以@ConditionalOn* 开头的注解是如何使用的,以及解析其原理,主要有以下内容

  • 使用方式
  • 实现原理
  • 扩展
  • 总结

使用方式

对于@ConditionalOn* 注解,很多都是是在SpringBoot中添加的,有以下内容:

org.springframework.boot.autoconfigure.condition.ConditionalOnBean
org.springframework.boot.autoconfigure.condition.ConditionalOnClass
org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform
org.springframework.boot.autoconfigure.condition.ConditionalOnExpression
org.springframework.boot.autoconfigure.condition.ConditionalOnJava
org.springframework.boot.autoconfigure.condition.ConditionalOnJndi
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass
org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication
org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
org.springframework.boot.autoconfigure.condition.ConditionalOnResource
org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate
org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication

以上注解有各自的用途,以@ConditionalOnBean 为例,

@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(
    DispatcherServlet dispatcherServlet) {
    ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(
        dispatcherServlet,
        this.serverProperties.getServlet().getServletMapping());
    registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
    registration.setLoadOnStartup(
        this.webMvcProperties.getServlet().getLoadOnStartup());
    if (this.multipartConfig != null) {
        registration.setMultipartConfig(this.multipartConfig);
    }
    return registration;
}

上述代码表示若存在Bean DispatcherServlet 或 BeanName 为dispatcherServlet 时,将执行方法,注册 ServletRegistrationBean 为Bean。

实现原理

对比条件注解@ConditionalOn* 的各个注解,我们可以发现一个共同的现象,每个注解上都有 @Conditional 注解修饰,那我们看下 @Conditional 注解的定义。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition}s that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();

}

@Conditional 定义中可以发现,具有一个属性 value,类型为Condition 接口的实现数组。对于Condition 接口,定义如下:

@FunctionalInterface
public interface Condition {

	/**
	 * Determine if the condition matches.
	 * @param context the condition context
	 * @param metadata 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);

}

Condition 接口被标注为函数式接口,定义了一个方法,返回boolean 类型,从注释中可以看出,这个方法决定条件是否匹配。那我们就可以确定,@ConditionalOn* 注解,是依靠Condition 接口的实现来完成功能的。那么问题来了,这个Condition 在何时被调用的呢。

按照这个思路,我们先确定这个Condition#matchers 方法在哪里被调用。我们从Condition 接口上也可以看出,这个接口是在spring-context中被引入的,所以这个功能不依赖于SpringBoot。结合以上内容,这个接口调用处只有一个org.springframework.context.annotation.ConditionEvaluator#shouldSkip(org.springframework.core.type.AnnotatedTypeMetadata, org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase) ,在这个方法中被调用,方法代码如下:

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    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<>();
    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();
        }
        if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
            return true;
        }
    }

    return false;
}

通过代码调试,进一步确认此方法的调用逻辑,即可看出这个是在定义Bean的时候,确认是否跳过的过程。

扩展

了解了@ConditionalOn* 的实现原理,那我们就可以对其进行扩展了,我们定义一个基于系统属性判断是否加载Bean@ConditionalOn* 注解 @ConditionalOnSystemProperty 代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionalOnSystemProperty {

    /**
     * Java 系统属性名称
     * @return
     */
    String name();

    /**
     * Java 系统属性值
     * @return
     */
    String value();
}

这里用了Condition 的实现类OnSystemPropertyCondition ,定义如下:

public class OnSystemPropertyCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());

        String propertyName = String.valueOf(attributes.get("name"));

        String propertyValue = String.valueOf(attributes.get("value"));

        String javaPropertyValue = System.getProperty(propertyName);

        return propertyValue.equals(javaPropertyValue);
    }
}

在类中取出注解@ConditionalOnSystemProperty 的两个属性值,然后和系统属性进行对比,若相同,这返回true ,不同则放回 false,写个测试类试下:

public class ConditionalOnSystemPropertyBootstrap {

    @Bean
    @ConditionalOnSystemProperty(name = "user.name", value = "xiaobai")
    public String helloWorld() {
        return "Hello World, 爱上编程的小白";
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(ConditionalOnSystemPropertyBootstrap.class)
                .web(WebApplicationType.NONE)
                .run(args);
        // 通过名称和类型获取 helloWorld Bean
        String helloWorld = context.getBean("helloWorld", String.class);

        System.out.println("helloWorld Bean : " + helloWorld);

        // 关闭上下文
        context.close();
    }
}

测试类中确定系统属性user.name 是否为 xiaobai,若是,则注册Bean helloWorld,否则程序将报错,找不到对应的Bean。

总结

  1. @ConditionalOn* 注解的实现有很多种,具有相同的注解@Conditional
  2. @Conditional 注解有一个value 属性,类型为Condition 接口实现的数组。
  3. Condition#match 方法是@ConditionalOn* 实现的关键。
  4. @ConditionOn* 注解是SpringBoot中添加的,但是其原理,在spring-context就已支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值