@Conditional
来源于spring-context
包下的一个注解。Conditional中文是条件的意思,@Conditional注解它的作用是按照一定的条件进行判断,满足条件给容器注册bean。
通过他的注解内部可以发现,他就是一个纯功能性注解,他并没有依赖于其他注解,类上只有三个元注解。
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;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
@Conditional
只有一个参数,并且这个参数要求是继承与Condition
类,并且参数是个数组,也就是可以 传多个的。Condition
类是一个函数式接口(只有一个方法的接口被称为函数式接口)。matches
方法就是比较方法,如果为true
则注入,如果为false
则不注入。
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
而除了@Conditional注解外,springboot通过@Conditional注解又扩展了很多注解出来,如下@ConditionalOnBean、@ConditionalOnClass等等…
(1)自定义Condition实现类
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String isOpen = environment.getProperty("systemLog.isOpen");
// 就算没有设置systemLog.isOpen,那么isOpen就等于null,Boolean.valueOf对于null照样会返回false的
return Boolean.valueOf(isOpen);
}
}
(2)自定义一个实体类,用于测试
public class TestBean1 {
@Override
public String toString() {
return super.toString() + "--我是TestBean1";
}
}
(3)添加配置类,并在类上使用@Conditional
@Configuration
@Conditional(MyCondition.class)
public class Myconfig {
@Bean
public TestBean1 testBean1(){
return new TestBean1();
}
}
(4)添加测试类
@RestController
public class CommonController {
@Autowired(required = false)
private Myconfig myconfig;
@Autowired(required = false)
private TestBean1 testBean1;
@RequestMapping("/import")
public void printImportBeanInfo() {
System.out.println(myconfig);
System.out.println(testBean1);
}
}
(5)启动测试: 访问http://localhost:8080/import
,可见Myconfig类并没有注入到容器,按正常来说被@Configuration修饰之后是会存放到容器当中的,但是显然因为@Conditional判断为false所以没有注入到容器当中。
通过这个测试不难发现假如@Bean所在的类没有注入到容器当中,那么他也不会被注入到容器当中。
(6)在application.yml
当中添加如下配置,然后再启动测试。
systemLog:
isOpen: true
(7)@Conditional还可以应用于方法上,我们可以让他和@Bean注解来配合使用
@Configuration
public class Myconfig {
@Bean
@Conditional(MyCondition.class)
public TestBean1 testBean1(){
return new TestBean1();
}
}
前言中说,@Conditional注解传入的是一个Class数组,存在多种条件类的情况。
这种情况貌似判断难度加深了,测试一波,新增新的条件类,实现的matches
返回false
(这种写死返回false的方法纯属测试用,没有实际意义O(∩_∩)O)
public class ObstinateCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return false;
}
}
@Configuration
@Conditional({MyCondition.class,ObstinateCondition.class})
public class Myconfig {
@Bean
public TestBean1 testBean1(){
return new TestBean1();
}
}
测试结果得知:
关于这些扩展注解其实在官网源码当中是有注释的,感兴趣的可以看一下:
https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition
主要是判断是否存在这个类文件,如果有这个文件就相当于满足条件,然后可以注入到容器当中。
当然并不是说容器里面是否有这个类哈,不要理解错了,这也就是我们有时候使用springboot只需要引入个依赖,框架就可以用的原因!
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
// 必须出现的类
Class<?>[] value() default {};
// 必须存在的类名,必须是全限类名,也就是包含包名+类名。
String[] name() default {};
}
用法示例:
@Configuration
@ConditionalOnClass({TestBean2.class})
public class Myconfig {
@Bean
@ConditionalOnClass(name = "com.gzl.cn.springbootcache.config.TestBean3")
public TestBean1 testBean1(){
return new TestBean1();
}
}
@ConditionalOnMissingClass
只有一个value
属性。他和@ConditionalOnClass
功能正好相反,@ConditionalOnClass是class存在为true,而@ConditionalOnMissingClass是不存在为true,也就是存在为false。为fasle就意味着不注入。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnMissingClass {
// 必须不存在的类名称,全类名
String[] value() default {};
}
@ConditionalOnMissingClass("com.gzl.cn.springbootcache.config.TestBean5")
或者
@ConditionalOnMissingClass(value = "com.gzl.cn.springbootcache.config.TestBean5")
bean存在的时候注入,不存在的时候不注入,这块就是指的spring的ioc容器了。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnBean {
// 指定bean的类类型。当所有指定类的bean都包含在容器中时,条件匹配。
Class<?>[] value() default {};
// 指定bean的全类名。当指定的所有类的bean都包含在容器中时,条件匹配。
String[] type() default {};
// bean所声明的注解,当ApplicationContext中存在声明该注解的bean时返回true
Class<? extends Annotation>[] annotation() default {};
// bean的id,当ApplicationContext中存在给定id的bean时返回true,这个id指的就是容器当中对象的id
String[] name() default {};
// 搜索容器层级,默认是所有上下文搜索
SearchStrategy search() default SearchStrategy.ALL;
// 可能在其泛型参数中包含指定bean类型的其他类
Class<?>[] parameterizedContainer() default {};
}
(1)代码示例:假如我把testBean2方法删掉,那么testBean1也将会不注入。
@Configuration
public class Myconfig {
@Bean
public TestBean2 testBean2(){
return new TestBean2();
}
@Bean
@ConditionalOnBean(TestBean2.class)
public TestBean1 testBean1() {
return new TestBean1();
}
}
(2)测试类
@RestController
public class CommonController {
@Autowired(required = false)
private TestBean1 testBean1;
@Autowired(required = false)
private TestBean2 testBean2;
@Autowired(required = false)
private Myconfig myconfig;
@RequestMapping("/import")
public void printImportBeanInfo() {
System.out.println(myconfig);
System.out.println(testBean1);
System.out.println(testBean2);
}
}
(3)运行输出结果:会发现三个对象是都注入到容器当中了。
(4)目前存在的问题
注意这里还存在一个执行顺序问题,假如我把以下代码放到启动类当中,而不是
Myconfig
配置文件当中,这时候会发现一个问题,testBean2注入进去了,但是带有@ConditionalOnBean(TestBean2.class)
条件的testBean1
没有注入进去。原因其实很简单,执行testBean1
的时候,testBean2并没有注入进去,然后testBean1就注入失败了,紧接着失败后testBean2又注入进来了。
@Bean
public TestBean2 testBean2(){
return new TestBean2();
}
(5)紧接着我又做了个试验,把启动类当中的testBean2删掉了,又创建了如下一个配置类,但是testBean1还是注入失败。
@Configuration
public class MyconfigTest {
@Bean
public TestBean2 testBean2(){
return new TestBean2();
}
}
(6)想要解决这个问题很简单,想办法让MyconfigTest
比Myconfig
先加载就可以了
于是我在MyconfigTest 添加了如下order注解
@Order(Ordered.HIGHEST_PRECEDENCE) //最高优先级
然后在Myconfig 也添加了Order注解
@Order(Ordered.LOWEST_PRECEDENCE) //最低优先级
但是仍然没有解决该问题。@Configuration并不能通过@Order指定顺序。
(7)大胆猜测下: @Configuration通过配置类名的自然顺序来加载的。
将MyconfigTest改名字:还别说改完名字真的就可以了!
(8)不可能每次遇到这种问题都改名字吧,经查文档,终于找到了需要的东西:我们可以通过@AutoConfigureBefore,@AutoConfigureAfter来控制配置类的加载顺序。
在MyconfigTest类上添加@AutoConfigureBefore(Myconfig.class)
,意思是在Myconfig实例化之前加载。如果要让@AutoConfigureBefore生效,还需要在META-INF/spring.factories文件中添加如下内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.gzl.cn.springbootcache.config.MyconfigTest,\
com.gzl.cn.springbootcache.config.Myconfig
bean不存在的时候注入,存在的时候为false。跟@ConditionalOnBean正好是相反的。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnMissingBean {
Class<?>[] value() default {};
String[] type() default {};
// 识别匹配 bean 时,可以被忽略的 bean 的 class 类型
Class<?>[] ignored() default {};
//识别匹配 bean 时,可以被忽略的 bean 的 class 类型名称
String[] ignoredType() default {};
Class<? extends Annotation>[] annotation() default {};
String[] name() default {};
SearchStrategy search() default SearchStrategy.ALL;
Class<?>[] parameterizedContainer() default {};
}
@ConditionalOnMissingBean
代表的是如果容器里面没有TestBean1
的实例,那么就运行@Bean修饰的方法注入对象,不管注入的什么对象。
@Configuration
public class Myconfig {
@Bean
@ConditionalOnMissingBean(TestBean1.class)
public TestBean1 testBean1() {
return new TestBean1();
}
}
注意:
@ConditionalOnMissingBean
和@ConditionalOnBean
使用的时候是可以不带任何属性的,不带任何属性的时候他就是判断的当前注入的类型。@ConditionalOnBean是判断当没有的时候进行注入,例如如下:他只会注入一个TestBean1进去。
@Configuration
public class Myconfig {
@Bean
@ConditionalOnMissingBean
public TestBean1 testBean1() {
System.out.println("1111111111");
return new TestBean1();
}
@Bean
@ConditionalOnMissingBean
public TestBean1 testBean2() {
System.out.println("2_222_222_222");
return new TestBean1();
}
}
@ConditionalOnProperty
主要可用于通过和springboot
当中application
配置文件来使用。在实战当中我们也可以通过他来实现配置化管理bean。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnPropertyCondition.class})
public @interface ConditionalOnProperty {
// 指定的属性完整名称,不能和name同时使用。
String[] value() default {};
// //配置文件中属性的前缀
String prefix() default "";
// //指定的属性名称
String[] name() default {};
// 指定的属性的属性值要等于该指定值,当value或name为一个时使用
String havingValue() default "";
// 当不匹配时是否允许加载,当为true时就算不匹配也不影响bean的注入或配置类的生效。
boolean matchIfMissing() default false;
}
使用示例:
@Configuration
@ConditionalOnProperty(
prefix = "system.log",
name = {"open"},
havingValue = "true",
matchIfMissing = false
)
public class Myconfig {
@Bean
public TestBean1 testBean1() {
return new TestBean1();
}
}
然后只要在application
添加如下配置,Myconfig
和testBean1
就会注入到容器当中。如果不设置也不会报错,只是注入不到容器里而已。
system:
log:
open: true