本节学习一下条件化向容器中注册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包中有很多实用注解,截图如下: