简介
@Conditional
是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册Bean。
作用
条件装配,满足Conditional
指定的条件,则进行组件注入
根注解与派生注解
@Conditional是一个根注解,这个根注解下面派生了许多派生注解
- @ConditionalOnCloudPlatform
- 不常用
- 当指定的云平台处于活动状态时匹配才能注入
- @ConditionalOnBean
- 常用
- 当容器中存在指定的Bean时,在执行指定操作
- @ConditionalOnMissingBean
- 常用
- 当容器中不存在指定的Bean时,才进行指定事情
- @ConditionalOnWarDeployment
- 不常用
- 当应用程序是传统的 WAR 部署时匹配,进行某些事情;对于带有嵌入式服务器的应用程序,此条件将返回 false
- @Profile
- 常用
- 当指定的配置文件处于活动状态的时候(即当前程序使用了这个配置文件),才进行指定事情
- 与@Configuration一起使用,详情参考Spring Boot 注解——@Configuration
- @ConditionalOnNotWebApplication
- 不常用
- 当应用程序不是 Web 应用程序时,执行某些操作。
- @ConditionalOnWebApplication
- 不常用
- 当应用程序是 Web 应用程序时,执行某些操作。 默认情况下,任何 Web 应用程序都会匹配,但可以使用type()属性缩小范围
- @ConditionalOnMissingClass
- 不常用(对于开发框架,应该会比较常用)
- 仅当指定的类不在类路径上时才匹配。
- @ConditionalOnClass
- 不常用(对于开发框架,应该会比较常用)
- 仅当指定的类在类路径上时才匹配。可以在@Configuration类上安全地指定value()因为在加载类之前使用 ASM 解析注释元数据。 放置在@Bean方法上时需要格外小心,考虑在单独的Configuration类中隔离条件,特别是如果方法的返回类型与target of the condition的target of the condition匹配
- 简单来说就是,如果指定的类在项目中存在,就会执行指定操作,如果不存在,就不会执行
- @ConditionalOnExpression
- 不常用
- 取决于SpEL表达式值的条件元素的配置注释。
- @ConditionalOnResource
- 常用
- 当且仅当项目中存在某个资源的时候,才执行相关操作
- @ConditionalOnRepositoryType
- 不常用
- 仅在启用特定类型的 Spring Data 存储库时匹配
- @ConditionalOnProperty
- 常用
- 当指定的属性上有特定的值时,才执行相关操作
- @ConditionalOnJava
- 不常用
- 根据运行应用程序的 JVM 版本匹配
- @ConditionalOnSingleCandidate
- 常用
- 仅当指定类的 bean 已包含在BeanFactory并且可以确定单个候选时才匹配。如果BeanFactory中已经包含多个匹配的 bean 实例但已经定义了一个主要候选者,则条件也将匹配; 本质上,如果自动装配具有定义类型的 bean 将成功,则条件匹配。该条件只能匹配到目前为止已由应用程序上下文处理的 bean 定义,因此,强烈建议仅在自动配置类上使用此条件。 如果候选 bean 可能是由另一个自动配置创建的,请确保使用此条件的那个 bean 之后运行
- 简单理解:表示当指定Bean在容器中只有一个,或者虽然有多个但是指定首选Bean。
- @ConditionalOnEnabledResourceChain
- 不常用
- 检查是否启用了 Spring 资源处理链。 如果ResourceProperties.Chain.getEnabled()为true或webjars-locator-core在类路径上,则webjars-locator-core 。
常用注解源码解析和具体使用方法
@Conditional
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* 必须匹配才能注册组件的所有Conditions
*/
Class<? extends Condition>[] value();
}
Conditional
是一个根注解,一系列的判断条件需要自己去实现,根据源码可以看到,@Conditional
拥有一个value属性,接收的参数类型是Class数组
,这个Class类型继承Condition接口,也就是说这个条件判断需要自己编程实现
@FunctionalInterface
public interface Condition {
/**
* 确定条件是否匹配。
* @param context 条件上下文
* @param metadata 被检查的class或method的元数据
* @return true如果条件匹配并且组件可以被注册,或false否决注释组件的注册
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
自己编写的类,需要继承Condition这个接口,如果matches返回的是true,则注入容器,反之,不注入
示例
- 普通实体类
public class Person {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
- 配置类
@Configuration
public class BeanConfig {
/**
* 如果WindowsCondition的实现方法返回true,则注入这个bean
*/
@Conditional({WindowsCondition.class})
@Bean(name = "bill")
public Person person1(){
return new Person("Bill Gates",62);
}
/**
* 如果LinuxCondition的实现方法返回true,则注入这个bean
*/
@Conditional({LinuxCondition.class})
@Bean("linus")
public Person person2(){
return new Person("Linus",48);
}
}
- 继承Condition接口
public class WindowsCondition implements Condition {
/**
* @param conditionContext:判断条件能使用的上下文环境
* @param annotatedTypeMetadata:注解所在位置的注释信息
* */
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//获取ioc使用的beanFactory
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
//获取类加载器
ClassLoader classLoader = conditionContext.getClassLoader();
//获取当前环境信息
Environment environment = conditionContext.getEnvironment();
//获取bean定义的注册类
BeanDefinitionRegistry registry = conditionContext.getRegistry();
//获得当前系统名
String property = environment.getProperty("os.name");
//包含Windows则说明是windows系统,返回true
if (property.contains("Windows")){
return true;
}
return false;
}
}
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
String property = environment.getProperty("os.name");
if (property.contains("Linux")){
return true;
}
return false;
}
}
- 测试类
@Test
public void test1(){
String osName = applicationContext.getEnvironment().getProperty("os.name");
System.out.println("当前系统为:" + osName);
Map<String, Person> map = applicationContext.getBeansOfType(Person.class);
System.out.println(map);
}
运行结果如下:
@ConditionalOnBean
源码分析
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 使用父注解@Conditional并自行实现Condition接口
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
/**
* 应该检查的 bean 的类型。 当BeanFactory中包含所有指定类的 bean 时,条件匹配
* @return 要检查的 bean 的类型
*/
Class<?>[] value() default {};
/**
* 应该检查的 bean 的类型。 当BeanFactory中包含所有指定类的 bean 时,条件匹配
* @return @return 要检查的 bean 的类型名称
*/
String[] type() default {};
/**
* 装饰应检查的 bean 的注释类型。 当在BeanFactory bean 上定义了所有指定的注释时,条件匹配
* @return 要检查的类级注释类型
*/
Class<? extends Annotation>[] annotation() default {};
/**
* 要检查的 bean 的名称。 当所有指定的 bean 名称都包含在BeanFactory中时,条件匹配
* @return 要检查的 bean 的名称
*/
String[] name() default {};
/**
* 决定是否应考虑应用程序上下文层次结构(父上下文)的策略
* @return the search strategy
*/
SearchStrategy search() default SearchStrategy.ALL;
/**
* 可能在其泛型参数中包含指定 bean 类型的其他类。
* 例如,声明 value = Name.class和 parameterizedContainer = NameRegistration.class
* 将检测Name和NameRegistration<Name>。
* @return 容器类型
* @since 2.1.0
*/
Class<?>[] parameterizedContainer() default {};
}
以上就是@ConditionalOnBean
注解的具体实现,可以看的出,这个注解实际使用了父注解@Conditional
和一个处理类OnBeanCondition
,真正的判断逻辑就在这个OnBeanCondition
中。
首先看这个类头,OnBeanCondition
中并没有实现Condition
,但是他继承了一个叫FilteringSpringBootCondition
的类,在进入这个类看看
FilteringSpringBootCondition
这个类也没有实现Condition
这个类,不过他也继承了一个叫SpringBootsCondition
的类
进入SpringBootCondition
这个类后,终于实现了Condition
,实现Condition
这个接口,那必定实现这个matches
方法,下面我们就详细看一下这个方法
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取当前注解标注的类名或方法名
String classOrMethodName = getClassOrMethodName(metadata);
try {
// 主要的判断逻辑,返回是否允许注入组件
ConditionOutcome outcome = getMatchOutcome(context, metadata);
// 日志记录
logOutcome(classOrMethodName, outcome);
// 日志记录
recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {
throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
+ ex.getMessage() + " not found. Make sure your own configuration does not rely on "
+ "that class. This can also happen if you are "
+ "@ComponentScanning a springframework package (e.g. if you "
+ "put a @ComponentScan in the default package by mistake)", ex);
}
catch (RuntimeException ex) {
throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
}
}
这里看看第7行最重要的逻辑
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
MergedAnnotations annotations = metadata.getAnnotations();
// 判断是否标注了@ConditionalOnBean
if (annotations.isPresent(ConditionalOnBean.class)) {
// 获取注解上的属性和对应的值
Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
// 在容器中查询,放回是否具有对应注解的类,MatchResult是由多个map和list组成,
// 是判断容器里面的类,是否有注解上面的标记。
// 重要逻辑
MatchResult matchResult = getMatchingBeans(context, spec);
// 判断是否存在由@ConditionalOnBean标注的类,如果有,返回true,没有返回,false
if (!matchResult.isAllMatched()) {
String reason = createOnBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(spec.message().because(reason));
}
matchMessage = spec.message(matchMessage)
.found("bean", "beans")
.items(Style.QUOTE,matchResult.getNamesOfAllMatches());
}
// 判断是否标注了@ConditionalOnSingleCandidate
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
MatchResult matchResult = getMatchingBeans(context, spec);
if (!matchResult.isAllMatched()) {
return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
}
else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(),
spec.getStrategy() == SearchStrategy.ALL)) {
return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans")
.items(Style.QUOTE, matchResult.getNamesOfAllMatches()));
}
matchMessage = spec.message(matchMessage)
.found("a primary bean from beans")
.items(Style.QUOTE,matchResult.getNamesOfAllMatches());
}
// 判断是否标注了@ConditionalOnMissingBean
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
ConditionalOnMissingBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
if (matchResult.isAnyMatched()) {
String reason = createOnMissingBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(spec.message().because(reason));
}
matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
}
return ConditionOutcome.match(matchMessage);
}
分析getMatchOutcome
可以看到,getMatchOutcome
支持@ConditionalOnSingleCandidate
、@ConditionalOnMissingBean
和@ConditionalOnBean
,也就是说,对于这三个注解的判断逻辑都在这里。其中最重要的逻辑就在getMatchingBeans
这个方法,这个方法比较复杂,也比较简单,就是根据当前上下文容器,查找是否存在对应的类。SearchStrategy
这个枚举定义了搜索的范围:
- All搜索整个上下文,父子容器等等
- ANCESTORS搜索所有祖先,除开当前上下文
- CURRENT搜索当前上下文。
@ConditionalOnProperty
源码解析
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
/**
* name的别名
* @return the names
*/
String[] value() default {};
/**
* 应该应用于每个属性的前缀。
* 如果未指定,前缀会自动以点结尾。
* 一个有效的前缀由一个或多个用点分隔的单词定义(例如"acme.system.feature" )
* @return 前缀名称
*/
String prefix() default "";
/**
* 要测试的属性的名称。如果已定义前缀,则将其应用于计算每个属性的完整键。
* 例如,如果前缀是app.config并且一个值是my-value ,则完整键将是app.config.my-value
* 使用虚线符号来指定每个属性,即全部小写,并用“-”分隔单词(例如: my-long-property )
* @return the names
*/
String[] name() default {};
/**
* 可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置
*/
String havingValue() default "";
/**
* 缺少该配置属性时是否可以加载。如果为true,没有该配置属性时也会正常加载;反之则不会生效
*/
boolean matchIfMissing() default false;
}
注解中的属性使用方法说明见上面代码注释
分析@ConditionalOnProperty
可知,和其他的一样,也是使用了父注解@Conditional
和逻辑判断类OnPropertyCondition
,下面看一下这个判断逻辑类的组成
该类也是继承了SpringBootCondition
这个类,参考@ConditionalOnBean
源码,可以知道,SpringBootCondition
实现了Condition
接口,其中最主要的方法就是getMatchOutcome
。
OnPropertyCondition
中的getMatchOutcome
逻辑:
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取ConditionalOnProperty注解属性和对应的值
List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
List<ConditionMessage> noMatch = new ArrayList<>();
List<ConditionMessage> match = new ArrayList<>();
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
// 判断是否与条件匹配
// 最主要的逻辑
ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
// 如果结果匹配,结果信息存在match,反之存在noMatch中
(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
}
if (!noMatch.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
}
return ConditionOutcome.match(ConditionMessage.of(match));
}
其中最主要的逻辑为determineOutcome
,这个方法根据@ConditionalOnProperty
中的需要做判断的属性去判断,如果当前属性不存在且不允许缺少属性时加载或属性存在,但值不对应,则认为不匹配,反之,认为匹配。
示例
@Configuration
//在application.properties配置"mf.assert",对应的值为true
@ConditionalOnProperty(prefix="mf",name = "assert", havingValue = "true", matchIfMissing = true)
public class AssertConfig {
@Autowired
private HelloServiceProperties helloServiceProperties;
@Bean
public HelloService helloService(){
HelloService helloService = new HelloService();
helloService.setMsg(helloServiceProperties.getMsg());
return helloService;
}
}
- 当配置文件中存在
mf.assert
,值为false
,该配置类不会注入 - 当配置文件中不存在
mf.assert
,且matchIfMissing=true
, 该配置类不会注入 - 当配置文件中存在
mf.assert
,值为true
,则注入该配置类