前言:
@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。
@Conditional的定义:
//此注解可以标注在类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
从代码中可以看到,需要传入一个Class数组,并且需要继承Condition接口:
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
Condition是个接口,需要实现matches方法,返回true则注入bean,false则不注入。
示例:
首先,创建Person类:
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 + '}';
}
}
创建BeanConfig类,用于配置两个Person实例并注入,一个是比尔盖茨,一个是林纳斯。
@Configuration
public class BeanConfig {
@Bean(name = "bill")
public Person person1(){
return new Person("Bill Gates",62);
}
@Bean("linus")
public Person person2(){
return new Person("Linus",48);
}
}
接着写一个测试类进行验证这两个Bean是否注入成功。
public class ConditionalTest {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
@Test
public void test1(){
Map<String, Person> map = applicationContext.getBeansOfType(Person.class);
System.out.println(map);
}
}
运行,输出结果是这样的,两个Person实例被注入进容器。
这是一个简单的例子,现在问题来了,如果我想根据当前操作系统来注入Person实例,windows下注入bill,linux下注入linus,怎么实现呢?
这就需要我们用到@Conditional注解了,前言中提到,需要实现Condition接口,并重写方法来自定义match规则。
首先,创建一个WindowsCondition类:
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;
}
}
matches方法的两个参数的意思在注释中讲述了,值得一提的是,conditionContext提供了多种方法,方便获取各种信息,也是SpringBoot中 @ConditonalOnXX注解多样扩展的基础。
接着,创建LinuxCondition类:
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;
}
}
接着就是使用这两个类了,因为此注解可以标注在方法上和类上,所以分开测试:
标注在方法上:
修改BeanConfig:
@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);
}
}
修改测试方法,使其可以打印当前系统名:
@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);
}
运行结果如下:
我是运行在windows上的所以只注入了bill,嗯,没毛病。
接着实验linux下的情况,不能运行在linux下,但可以修改运行时参数:
修改后启动测试方法:
一个方法只能注入一个bean实例,所以@Conditional标注在方法上只能控制一个bean实例是否注入。
标注在类上:
一个类中可以注入很多实例,@Conditional标注在类上就决定了一批bean是否注入。
我们试一下,将BeanConfig改写,这时,如果WindowsCondition返回true,则两个Person实例将被注入(注意:上一个测试将os.name改为linux,这是我将把这个参数去掉):
@Conditional({WindowsCondition.class})
@Configuration
public class BeanConfig {
@Bean(name = "bill")
public Person person1(){
return new Person("Bill Gates",62);
}
@Bean("linus")
public Person person2(){
return new Person("Linus",48);
}
}
结果两个实例都被注入:
如果将类上的WindowsCondition.class改为LinuxCondition.class,结果应该可以猜到:
结果就是空的,类中所有bean都没有注入。
多个条件类:
前言中说,@Conditional注解传入的是一个Class数组,存在多种条件类的情况。
这种情况貌似判断难度加深了,测试一波,新增新的条件类,实现的matches返回false(这种写死返回false的方法纯属测试用,没有实际意义O(∩_∩)O)
public class ObstinateCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return false;
}
}
BeanConfig修改一下:
@Conditional({WindowsCondition.class,ObstinateCondition.class})
@Configuration
public class BeanConfig {
@Bean(name = "bill")
public Person person1(){
return new Person("Bill Gates",62);
}
@Bean("linus")
public Person person2(){
return new Person("Linus",48);
}
}
结果:
现在如果将ObstinateCondition的matches方法返回值改成true,两个bean就被注入进容器:
结论得:
第一个条件类实现的方法返回true,第二个返回false,则结果false,不注入进容器。
第一个条件类实现的方法返回true,第二个返回true,则结果true,注入进容器中。
继@Conditional注解后,又基于此注解推出了很多派生注解,比如@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnExpression、@ConditionalOnClass......动态注入bean变得更方便了。本篇将讲解@ConditionalOnBean注解。
配置类中有两个Computer类的bean,一个是笔记本电脑,一个是备用电脑。如果当前容器中已经有电脑bean了,就不注入备用电脑,如果没有,则注入备用电脑,这里需要使用到@ConditionalOnMissingBean。
@Configuration
public class BeanConfig {
@Bean(name = "notebookPC")
public Computer computer1(){
return new Computer("笔记本电脑");
}
@ConditionalOnMissingBean(Computer.class)
@Bean("reservePC")
public Computer computer2(){
return new Computer("备用电脑");
}
}
这个注解就实现了功能,这个@ConditionalOnMissingBean为我们做了什么呢?我们来一探究竟.。
一探究竟:
首先,来看@ConditionalOnMissingBean的声明:
//可以标注在类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//使用了@Conditional注解,条件类是OnBeanCondition
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnMissingBean {
Class<?>[] value() default {};
String[] type() default {};
Class<?>[] ignored() default {};
String[] ignoredType() default {};
Class<? extends Annotation>[] annotation() default {};
String[] name() default {};
SearchStrategy search() default SearchStrategy.ALL;
}
这时候,我们就看到了我们熟悉的@Conditional注解,OnBeanCondition作为条件类。
OnBeanCondition类的声明:
//定义带注释的组件的排序顺序,2147483647即为默认值
@Order(2147483647)
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {
它继承了SpringBootCondition类,OnBeanCondition类中没有matches方法,而SpringBootCondition类中有实现matches方法。OnBeanCondition还实现了ConfigurationCondition,ConfigurationCondition接口不熟悉的读者可以到Spring ConfigurationCondition接口详解 了解接口。OnBeanCondition类重写了getConfigurationPhase()方法,表示在注册bean的时候注解生效:
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
就从matches方法开始:
//SpringBootCondition类中的matches方法
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取当前的类名或者方法名(由标注的位置决定)
String classOrMethodName = getClassOrMethodName(metadata);
try {
//关键代码:这里就会判断出结果
ConditionOutcome outcome = this.getMatchOutcome(context, metadata);
//存入日志
this.logOutcome(classOrMethodName, outcome);
//存入记录
this.recordEvaluation(context, classOrMethodName, outcome);
//最后返回ConditionOutcome的isMatch就是返回boolean类型结果
return outcome.isMatch();
} catch (NoClassDefFoundError var5) {
throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + var5.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)", var5);
} catch (RuntimeException var6) {
throw new IllegalStateException("Error processing condition on " + this.getName(metadata), var6);
}
}
关键代码在OnBeanCondition的getMatchOutcome方法上:
/**
* 获得判断结果的方法,ConditionOutcome类中存着boolean类型的结果
*/
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
//返回一个新的ConditionMessage
ConditionMessage matchMessage = ConditionMessage.empty();
OnBeanCondition.BeanSearchSpec spec;
List matching;
//这是metadata会调用isAnnotated方法判断当前标注的注解是不是ConditionalOnMissingBean
//其实@ConditionalOnBean、@ConditionalOnMissingBean和@ConditionalOnSingleCandidate都是使用这个条件类,所以这里做判断
if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnBean.class);
matching = this.getMatchingBeans(context, spec);
if (matching.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnBean.class, new Object[]{spec}).didNotFind("any beans").atAll());
}
matchMessage = matchMessage.andCondition(ConditionalOnBean.class, new Object[]{spec}).found("bean", "beans").items(Style.QUOTE, matching);
}
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
OnBeanCondition.BeanSearchSpec spec = new OnBeanCondition.SingleCandidateBeanSearchSpec(context, metadata, ConditionalOnSingleCandidate.class);
matching = this.getMatchingBeans(context, spec);
if (matching.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSingleCandidate.class, new Object[]{spec}).didNotFind("any beans").atAll());
}
if (!this.hasSingleAutowireCandidate(context.getBeanFactory(), matching, spec.getStrategy() == SearchStrategy.ALL)) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSingleCandidate.class, new Object[]{spec}).didNotFind("a primary bean from beans").items(Style.QUOTE, matching));
}
matchMessage = matchMessage.andCondition(ConditionalOnSingleCandidate.class, new Object[]{spec}).found("a primary bean from beans").items(Style.QUOTE, matching);
}
//如果当前注入的bean是@ConditionalOnMissingBean
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
//返回一个spec(说明),这里的spec规定了搜索的内容,比如搜索策略、需要搜索的类名......
spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class);
//主要的搜索实现在这个方法里,最后返回一个list
matching = this.getMatchingBeans(context, spec);
//判断搜索出来的结果
if (!matching.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingBean.class, new Object[]{spec}).found("bean", "beans").items(Style.QUOTE, matching));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, new Object[]{spec}).didNotFind("any beans").atAll();
}
return ConditionOutcome.match(matchMessage);
}
spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnBean.class);
这句中,相当于从内部类中将标注@ConditionalOnMissingBean注解时的属性都取出来:
BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata, Class<?> annotationType) {
this.annotationType = annotationType;
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);
//将attributes这个map中的数据放到对应的list成员变量中
this.collect(attributes, "name", this.names);
this.collect(attributes, "value", this.types);
this.collect(attributes, "type", this.types);
this.collect(attributes, "annotation", this.annotations);
this.collect(attributes, "ignored", this.ignoredTypes);
this.collect(attributes, "ignoredType", this.ignoredTypes);
this.strategy = (SearchStrategy)metadata.getAnnotationAttributes(annotationType.getName()).get("search");
OnBeanCondition.BeanTypeDeductionException deductionException = null;
try {
if (this.types.isEmpty() && this.names.isEmpty()) {
this.addDeducedBeanType(context, metadata, this.types);
}
} catch (OnBeanCondition.BeanTypeDeductionException var7) {
deductionException = var7;
}
this.validate(deductionException);
}
//验证的方法
protected void validate(OnBeanCondition.BeanTypeDeductionException ex) {
if (!this.hasAtLeastOne(this.types, this.names, this.annotations)) {
String message = this.annotationName() + " did not specify a bean using type, name or annotation";
if (ex == null) {
throw new IllegalStateException(message);
} else {
throw new IllegalStateException(message + " and the attempt to deduce the bean's type failed", ex);
}
}
}
看一下OnBeanCondition类中的getMatchingBeans方法,里面有用到搜索策略,详见搜索策略介绍
private List<String> getMatchingBeans(ConditionContext context, OnBeanCondition.BeanSearchSpec beans) {
//获得当前bean工厂
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//判断当前的搜索策略是否是PARENTS或者ANCESTORS,默认是ALL
if (beans.getStrategy() == SearchStrategy.PARENTS || beans.getStrategy() == SearchStrategy.ANCESTORS) {
BeanFactory parent = beanFactory.getParentBeanFactory();
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, "Unable to use SearchStrategy.PARENTS");
//如果是PARENTS或者ANCESTORS,当前bean工厂就用父工厂
beanFactory = (ConfigurableListableBeanFactory)parent;
}
if (beanFactory == null) {
return Collections.emptyList();
} else {
List<String> beanNames = new ArrayList();
//如果当前搜索策略等于CURRENT,为true
boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
//这里的type就是需要查找的bean的类型
//下面,会从属性中找bean
Iterator var6 = beans.getTypes().iterator();
String beanName;
while(var6.hasNext()) {
beanName = (String)var6.next();
//如果找到了类型,接下来就是根据类型找bean的实例名,找示例名的方法在下方,实际上就是一个getNamesForType
beanNames.addAll(this.getBeanNamesForType(beanFactory, beanName, context.getClassLoader(), considerHierarchy));
}
var6 = beans.getIgnoredTypes().iterator();
while(var6.hasNext()) {
beanName = (String)var6.next();
beanNames.removeAll(this.getBeanNamesForType(beanFactory, beanName, context.getClassLoader(), considerHierarchy));
}
var6 = beans.getAnnotations().iterator();
while(var6.hasNext()) {
beanName = (String)var6.next();
beanNames.addAll(Arrays.asList(this.getBeanNamesForAnnotation(beanFactory, beanName, context.getClassLoader(), considerHierarchy)));
}
var6 = beans.getNames().iterator();
while(var6.hasNext()) {
beanName = (String)var6.next();
if (this.containsBean(beanFactory, beanName, considerHierarchy)) {
beanNames.add(beanName);
}
}
//将存放bean实例名的list返回
return beanNames;
}
}
//根据类型获取bean的name
private Collection<String> getBeanNamesForType(ListableBeanFactory beanFactory, String type, ClassLoader classLoader, boolean considerHierarchy) throws LinkageError {
try {
Set<String> result = new LinkedHashSet();
this.collectBeanNamesForType(result, beanFactory, ClassUtils.forName(type, classLoader), considerHierarchy);
return result;
} catch (ClassNotFoundException var6) {
return Collections.emptySet();
} catch (NoClassDefFoundError var7) {
return Collections.emptySet();
}
}
private void collectBeanNamesForType(Set<String> result, ListableBeanFactory beanFactory, Class<?> type, boolean considerHierarchy) {
result.addAll(BeanTypeRegistry.get(beanFactory).getNamesForType(type));
if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) {
BeanFactory parent = ((HierarchicalBeanFactory)beanFactory).getParentBeanFactory();
if (parent instanceof ListableBeanFactory) {
this.collectBeanNamesForType(result, (ListableBeanFactory)parent, type, considerHierarchy);
}
}
}
找完bean了之后,回到刚才的代码里:
//如果当前注入的bean是@ConditionalOnMissingBean
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
//返回一个spec(说明),这里的spec规定了搜索的内容,比如搜索策略、需要搜索的类名......
spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class);
matching = this.getMatchingBeans(context, spec);
if (!matching.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingBean.class, new Object[]{spec}).found("bean", "beans").items(Style.QUOTE, matching));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, new Object[]{spec}).didNotFind("any beans").atAll();
}
如果第5行返回的list不是空的,就会返回ConditionOutcome对象noMatch方法,表示不匹配。ConditionOutcome类用于存放过滤结果,只有两个变量:
/**
* 过滤结果类
*/
public class ConditionOutcome {
/**
* 匹配结果 true or false
*/
private final boolean match;
/**
* 匹配结果信息
*/
private final ConditionMessage message;
两者区别:
@ConditionOnBean在判断list的时候,如果list没有值,返回false,否则返回true
@ConditionOnMissingBean在判断list的时候,如果list没有值,返回true,否则返回false,其他逻辑都一样
例子:
@ConditionalOnBean(javax.sql.DataSource.class)
Spring容器或者所有父容器中需要存在至少一个javax.sql.DataSource类的实例
另外其他几个扩展:
@ConditionalOnBean(仅仅在当前上下文中存在某个对象时,才会实例化一个Bean)
@ConditionalOnClass(某个class位于类路径上,才会实例化一个Bean)
@ConditionalOnExpression(当表达式为true的时候,才会实例化一个Bean)
@ConditionalOnMissingBean(仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean)
@ConditionalOnMissingClass(某个class类路径上不存在的时候,才会实例化一个Bean)
@ConditionalOnNotWebApplication(不是web应用)
@ConditionalOnProperty 指在application.yml里配置的属性是否为true
@Configuration
@ConditionalOnProperty(
value = {"abc.property"},
matchIfMissing = false
)
@ConditionalOnClass(Abc.class)
public class Multi {
@Bean
@ConditionalOnMissingBean({Abc.class})
public String check() {
System.err.println("multi check");
return "check";
}
在配置里加上abc.property = true这个配置就可以测试上面的代码了。
参考地址:
https://blog.csdn.net/xcy1193068639/article/details/81517456
https://blog.csdn.net/tianyaleixiaowu/article/details/78201587