5. spring-容器: 注解:@Conditional

本节学习一下条件化向容器中注册Bean。这是一个非常实用的功能。比如根据配置开关决定是否加载某组Bean。根据当前是否已有某个Bean决定是否加载该类型的Bean(如用自定义DataSource替换默认的DataSource Bean)。这个功能是后续SpringBoot能实现自动化配置的重要基础。

@Conditional注解概述

注解源码如下

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Indicates that a component is only eligible for registration when all
 * {@linkplain #value specified conditions} match.
 *
 * <p>A <em>condition</em> is any state that can be determined programmatically
 * before the bean definition is due to be registered (see {@link Condition} for details).
 *
 * <p>The {@code @Conditional} annotation may be used in any of the following ways:
 * <ul>
 * <li>as a type-level annotation on any class directly or indirectly annotated with
 * {@code @Component}, including {@link Configuration @Configuration} classes</li>
 * <li>as a meta-annotation, for the purpose of composing custom stereotype
 * annotations</li>
 * <li>as a method-level annotation on any {@link Bean @Bean} method</li>
 * </ul>
 *
 * <p>If a {@code @Configuration} class is marked with {@code @Conditional},
 * all of the {@code @Bean} methods, {@link Import @Import} annotations, and
 * {@link ComponentScan @ComponentScan} annotations associated with that
 * class will be subject to the conditions.
 *
 * <p><strong>NOTE</strong>: Inheritance of {@code @Conditional} annotations
 * is not supported; any conditions from superclasses or from overridden
 * methods will not be considered. In order to enforce these semantics,
 * {@code @Conditional} itself is not declared as
 * {@link java.lang.annotation.Inherited @Inherited}; furthermore, any
 * custom <em>composed annotation</em> that is meta-annotated with
 * {@code @Conditional} must not be declared as {@code @Inherited}.
 *
 * @author Phillip Webb
 * @author Sam Brannen
 * @since 4.0
 * @see Condition
 */
@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注解可以用到以下场景:
    作为一个类级的注解,可以和@Component, @Configuration搭配使用
    作为一个元注解,可以被其他自定义注解组合
    可以作为方法级注解,跟@Bean一起使用

  • 如果一个注解了@Configuration的类被标注了@Conditional, 所有的方法,以及注解@Import, @ComponentScan等全部会受到影响

  • 注意:@Conditional注解不支持继承的特性。因此@Conditional注解本身没有被标记为@Inherited。如果自定义的注解组合了@Conditional,也建议不要加@Interited。

Condition

@Conditional注解的属性是Condition类的数组,如下:

Class<? extends Condition>[] value();

因此实际配置@Conditional注解时,需要指定已有的或自定义的Condition实现类。

Condition接口的源码:

package org.springframework.context.annotation;

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * A single {@code condition} that must be {@linkplain #matches matched} in order
 * for a component to be registered.
 *
 * <p>Conditions are checked immediately before the bean-definition is due to be
 * registered and are free to veto registration based on any criteria that can
 * be determined at that point.
 *
 * <p>Conditions must follow the same restrictions as {@link BeanFactoryPostProcessor}
 * and take care to never interact with bean instances. For more fine-grained control
 * of conditions that interact with {@code @Configuration} beans consider the
 * {@link ConfigurationCondition} interface.
 *
 * @author Phillip Webb
 * @since 4.0
 * @see ConfigurationCondition
 * @see Conditional
 * @see ConditionContext
 */
@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是在BeanDefinition被注册之前进行检查,根据匹配的情况决定是否注册。
  • Condition必须遵守和BeanFactoryPostProcessor同样的限制,并且要注意不要和Bean实例有交互。
  • 如果是加注到@Configuration上,可用更细粒度控制的接口 ConfigurationCondition
示例

通过当前OS不同加载不同的Bean。

先自定义Condition

package win.elegentjs.spring.ioc.conditional;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * 自定义condition,实现根据OS环境启动对应的组件才加载
 */
public class EnableMacCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 获取当前的环境信息
        Environment environment = context.getEnvironment();
        String os = environment.getProperty("os.name");

        return os.contains("Mac");
    }
}

package win.elegentjs.spring.ioc.conditional;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * 自定义condition,实现根据OS环境启动对应的组件才加载
 */
public class EnableWindowsCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 获取当前的环境信息
        Environment environment = context.getEnvironment();
        String os = environment.getProperty("os.name");

        return os.contains("windows");
    }
}

通过自定义Condition加载Bean
package win.elegentjs.spring.ioc.conditional.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import win.elegentjs.spring.ioc.conditional.EnableMacCondition;
import win.elegentjs.spring.ioc.conditional.EnableWindowsCondition;

/**
 * 通过自定义Condition加载bean
 * 两个bean方法加载了@Conditional注解,容器启动时自动根据当前条件是否满足从而决定是否加载对应的bean
 */
@Configuration
public class CustomConditionConfig {

    @Bean
    @Conditional(EnableMacCondition.class)
    public String myMacOs() {
        return "mac";
    }

    @Bean
    @Conditional(EnableWindowsCondition.class)
    public String myWindowsOs() {
        return "windows";
    }
}

测试bean加载情况
package win.elegentjs.spring.ioc.conditional;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import win.elegentjs.spring.ioc.compos.CustomScopeMyComponent;
import win.elegentjs.spring.ioc.conditional.config.CustomConditionConfig;
import win.elegentjs.spring.ioc.config.PersonConfig;
import win.elegentjs.spring.ioc.scope.MyThreadScope;

/**
 * 测试 customer Condition
 */
@Slf4j
public class CustomConditionSample {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomConditionConfig.class);
        boolean myMacOs = context.containsBean("myMacOs");
        boolean myWindowsOs = context.containsBean("myWindowsOs");

        log.info("==> myMacOs: " + myMacOs);
        log.info("==> myWindowsOs: " + myWindowsOs);
    }
}

// result:
2021-05-21 18:01:59.201 [main] INFO  w.e.spring.ioc.conditional.CustomConditionSample-==> myMacOs: true
2021-05-21 18:01:59.203 [main] INFO  w.e.spring.ioc.conditional.CustomConditionSample-==> myWindowsOs: false

可以看出当前我在mac上运行的,就只启动了对应的bean。

小结

@Conditional注解可灵活的处理bean的加载与否,通过实现Condition接口来自定义。在springboot 的spring-boot-autoconfigure包中有很多实用注解,截图如下:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值