一.@Conditional
注解
这个注解在Spring4中引入,其主要作用就是判断条件是否满足,从而决定是否初始化并向容器注册Bean
1. 定义
@Conditional
注解定义如下,其内部主要就是利用了Condition接口,来判断是否满足条件,从而决定是否需要加载Bean
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
下面是Condtion
接口的定义,这个可以说是最基础的入口了,其他的所有条件注解,归根结底,都是通过实现这个接口进行扩展的
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
这个接口中,有个参数比较有意思ConditionContext
,它持有不少有用的对象,可以用来获取很多系统相关的信息,来丰富条件判断,接口定义如下
public interface ConditionContext {
// 获取Bean定义
BeanDefinitionRegistry getRegistry();
// 获取Bean工程,因此就可以获取容器中的所有bean
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
// environment 持有所有的配置信息
Environment getEnvironment();
// 资源信息
ResourceLoader getResourceLoader();
// 类加载信息
@Nullable
ClassLoader getClassLoader();
}
2. 使用说明
通过一个小例子,简单的说一下如何使用Condition和@Conditional
注解,来实现bean的条件加载
首先我们定义一个随机产生数据的类,其功能就是随机生成一些数据
public class RandDataComponent<T> {
private Supplier<T> rand;
public RandDataComponent(Supplier<T> rand) {
this.rand = rand;
}
public T rand() {
return rand.get();
}
}
我们目前提供两种随机数据生成的bean,但是需要根据配置来选择具体选中的方式,因此我们如下定义Bean
@Configuration
public class ConditionalAutoConfig {
@Bean
@Conditional(RandIntCondition.class)
public RandDataComponent<Integer> randIntComponent() {
return new RandDataComponent<>(() -> {
Random random = new Random();
return random.nextInt(1024);
});
}
@Bean
@Conditional(RandBooleanCondition.class)
public RandDataComponent<Boolean> randBooleanComponent() {
return new RandDataComponent<>(() -> {
Random random = new Random();
return random.nextBoolean();
});
}
}
上面的配置,先不管@Conditional
注解的内容,单看两个Bean的定义,一个是定义int随机数生成;一个是定义boolean随机生成;
但是我们的系统中,只需要一个随机数据生成器即可,我们选择根据配置conditional.rand.type
的值来选择到底用哪个,配置如下
# int 表示选择随机产生int数据; 非int 表示随机产生boolean数据
conditional.rand.type=int
接下来就得看这个条件如何加上了,也就是上面配置类ConditionalAutoConfig
中两个注解的内容了,两个类都是实现Condition
的接口,具体如下
public class RandBooleanCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String type = conditionContext.getEnvironment().getProperty("conditional.rand.type");
return "boolean".equalsIgnoreCase(type);
}
}
public class RandIntCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String type = conditionContext.getEnvironment().getProperty("conditional.rand.type");
return "int".equalsIgnoreCase(type);
}
}
上面的实现也比较清晰,获取配置值,然后判断,并返回true/fase;返回true,则表示这个条件满足,那么这个Bean就可以被加载了;否则这个Bean就不会创建
3. 测试与验证
针对上面的配置与实现,写一个测试类如下
@RestController
@RequestMapping(path = "/conditional")
public class ConditionalRest {
@Autowired
private RandDataComponent randDataComponent;
@GetMapping(path = "/show")
public String show() {
String type = environment.getProperty("conditional.rand.type");
return randDataComponent.rand() + " >>> " + type;
}
}
当配置文件的值为int时,每次访问返回的应该都是正整数,演示如下图
将配置的值改成boolean之后,再次测试如下图
II. 扩展与小结
上面的测试演示了通过配置文件选择注入Bean的情况,如果一个Bean是通过自动扫描加载的,是否可以直接在Bean的类上添加注解来决定是否载入呢?
1. 自动扫描Bean的条件加载
从使用来讲,和前面的没有什么区别,只是将注解放在具体的类上而言,同样给出一个示例,先定义一个bean
@Component
@Conditional(ScanDemoCondition.class)
public class ScanDemoBean {
@Value("${conditional.demo.load}")
private boolean load;
public boolean getLoad() {
return load;
}
}
对应的判断条件如下,当配置文件中conditional.demo.load
为true时,才会加载这个配置,否则不实例化
public class ScanDemoCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return "true".equalsIgnoreCase(conditionContext.getEnvironment().getProperty("conditional.demo.load"));
}
}
测试类和前面差不多,稍微注意下的就是自动注入时,改一下必要条件,避免bean不存在时报错
@Autowired(required = false)
private ScanDemoBean scanDemoBean;
@GetMapping(path = "/scan")
public String showDemo() {
String type = environment.getProperty("conditional.demo.load");
if (scanDemoBean == null) {
return "not exists! >>>" + type;
} else {
return "load : " + scanDemoBean.getLoad() + " >>>" + type;
}
}
当配置为true时,bean应该存在,走上面的else逻辑
当配置为false时,不会加载bean,走if逻辑
2. 小结
通过@Conditional
注解配合Condition
接口,来决定给一个bean是否创建和注册到Spring容器中,从而实现有选择的加载bean
a. 优势
这样做的目的是什么呢?
- 当有多个同名bean时,怎么抉择的问题
- 解决某些bean的创建有其他依赖条件的case
b. 更多注解
上面可以控制bean的创建,但通过上面的流程,会发现有一点繁琐,有没有什么方式可以简化上面的流程呢?
只用一个注解就好,不要自己再来实现Condtion接口,Spring框架提供了一系列相关的注解,如下表
注解 | 说明 |
---|---|
@ConditionalOnSingleCandidate |
当给定类型的bean存在并且指定为Primary的给定类型存在时,返回true |
@ConditionalOnMissingBean |
当给定的类型、类名、注解、昵称在beanFactory中不存在时返回true.各类型间是or的关系 |
@ConditionalOnBean |
与上面相反,要求bean存在 |
@ConditionalOnMissingClass |
当给定的类名在类路径上不存在时返回true,各类型间是and的关系 |
@ConditionalOnClass |
与上面相反,要求类存在 |
@ConditionalOnCloudPlatform |
当所配置的CloudPlatform为激活时返回true |
@ConditionalOnExpression |
spel表达式执行为true |
@ConditionalOnJava |
运行时的java版本号是否包含给定的版本号.如果包含,返回匹配,否则,返回不匹配 |
@ConditionalOnProperty |
要求配置属性匹配条件 |
@ConditionalOnJndi |
给定的jndi的Location 必须存在一个.否则,返回不匹配 |
@ConditionalOnNotWebApplication |
web环境不存在时 |
@ConditionalOnWebApplication |
web环境存在时 |
@ConditionalOnResource |
要求制定的资源存在 |
二.下面是几个常用的注解方式
@ConditionalOnBean
@ConditionalOnMissingBean
@ConditionalOnClass
@ConditionalOnMissingClass
I. Bean的存在与否作为条件
当Bean不存在时,创建一个默认的Bean,在Spring的生态中可以说比较常见了;接下来看下这种方式可以怎么用
1. @ConditionalOnBean
要求bean存在时,才会创建这个bean;如我提供了一个bean名为RedisOperBean
,用于封装redis相关的操作;但是我这个bean需要依赖restTemplate
这个bean,只有当应用引入了redis的相关依赖,并存在RestTemplate
这个bean的时候,我这个bean才会生效
假设bean的定义如下
1 2 3 4 5 6 7 8 |
@Component @ConditionalOnBean(name="redisTemplate") public class RedisOperBean { private final RedisTemplate redisTemplate; public RedisOperBean(RedisTemplate redisTemplate) { // ... } } |
这样的好处就是我提供的这个第三方包,如果被用户A间接依赖(但是A本身不需要操作redis),也不会因为创建RedisOperBean
而抛异常
产生异常的原因是因为找不到RestTemplate的bean,因此无法实例化RedisOperBean,从而抛出异常
a. 注解定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnBean { // bean类型 trueClass<?>[] value() default {}; true// bean类型 trueString[] type() default {}; true// 要求bean上拥有指定的注解 trueClass<? extends Annotation>[] annotation() default {}; true// bean names trueString[] name() default {}; trueSearchStrategy search() default SearchStrategy.ALL; } |
b. 测试用例
构建一个简单的测试用例,先定义一个基础的bean
1 2 |
public class DependedBean { } |
再定义一个依赖只有上面的bean存在时,才会加载的bean
1 2 3 4 5 6 7 8 9 10 11 12 |
public class LoadIfBeanExist { private String name; public LoadIfBeanExist(String name) { this.name = name; } public String getName() { return "load if bean exists: " + name; } } |
接下来就是bean的定义了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Bean public DependedBean dependedBean() { return new DependedBean(); } /** * 只有当DependedBean 存在时,才会创建bean: `LoadIfBeanExist` * * @return */ @Bean @ConditionalOnBean(name = "dependedBean") public LoadIfBeanExist loadIfBeanExist() { return new LoadIfBeanExist("dependedBean"); } |
根据上面的测试用例,LoadIfBeanExist
是会被正常加载的; 具体结果看后面的实例演示
2. ConditionalOnMissingBean
和前面一个作用正好相反的,上面是要求存在bean,而这个是要求不存在
a. 接口定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public @interface ConditionalOnMissingBean { trueClass<?>[] value() default {}; trueString[] type() default {}; true/** * The class type of beans that should be ignored when identifying matching beans. */ trueClass<?>[] ignored() default {}; true/** * The class type names of beans that should be ignored when identifying matching * beans. */ trueString[] ignoredType() default {}; trueClass<? extends Annotation>[] annotation() default {}; trueString[] name() default {}; trueSearchStrategy search() default SearchStrategy.ALL; } |
b. 测试用例
同样定义一个bean不存在时,才创建的bean
1 2 3 4 5 6 7 8 9 10 11 |
public class LoadIfBeanNotExists { public String name; public LoadIfBeanNotExists(String name) { this.name = name; } public String getName() { return "load if bean not exists: " + name; } } |
对应的bean配置如下
1 2 3 4 5 6 7 8 9 10 |
/** * 只有当没有notExistsBean时,才会创建bean: `LoadIfBeanNotExists` * * @return */ @Bean @ConditionalOnMissingBean(name = "notExistsBean") public LoadIfBeanNotExists loadIfBeanNotExists() { return new LoadIfBeanNotExists("notExistsBean"); } |
因为没有notExistsBean,所以上面这个bean也应该被正常注册
3. 实例演示
因为bean的是否存在和class的是否存在有较大的相似性,因此实例演示放在下一小节,一起测试
II. Class的存在与否作为条件
从使用来看,和前面基本上没有太大的区别,无非就是将bean换成了class;这样就可以避免因为Class Not Found
导致的编译异常了
1. @ConditionalOnClass
要求class存在
a. 注解定义
1 2 3 4 5 6 7 8 9 10 |
public @interface ConditionalOnClass { trueClass<?>[] value() default {}; true/** * The classes names that must be present. * @return the class names that must be present. */ trueString[] name() default {}; } |
b. 测试用例
先定义一个class
1 2 |
public class DependedClz { } |
然后依赖class存在的bean
1 2 3 4 5 6 7 8 9 10 11 |
public class LoadIfClzExists { private String name; public LoadIfClzExists(String name) { this.name = name; } public String getName() { return "load if exists clz: " + name; } } |
接下来就是Bean的配置
1 2 3 4 5 6 7 8 9 10 |
/** * 当引用了 {@link DependedClz} 类之后,才会创建bean: `LoadIfClzExists` * * @return */ @Bean @ConditionalOnClass(DependedClz.class) public LoadIfClzExists loadIfClzExists() { return new LoadIfClzExists("dependedClz"); } |
因为类存在,所以测试时,这个bean应该被正常注册
2. @ConditionalOnMissingClass
class不存在时,才会加载bean
a. 注解定义
1 2 3 |
public @interface ConditionalOnMissingClass { trueString[] value() default {}; } |
b. 测试用例
定义一个class缺少时才会创建的bean
1 2 3 4 5 6 7 8 9 10 11 |
public class LoadIfClzNotExists { private String name; public LoadIfClzNotExists(String name) { this.name = name; } public String getName() { return "load if not exists clz: " + name; } } |
bean的配置如下
1 2 3 4 5 6 7 8 9 10 |
/** * 当系统中没有 com.git.hui.boot.conditionbean.example.depends.clz.DependedClz类时,才会创建这个bean * * @return */ @Bean @ConditionalOnMissingClass("com.git.hui.boot.conditionbean.example.depends.clz.DependedClz") public LoadIfClzNotExists loadIfClzNotExists() { return new LoadIfClzNotExists("com.git.hui.boot.conditionbean.example.depends.clz.DependedClz"); } |
因为上面这个类存在,所以这个bean不应该被正常注册
3. 实例演示
起一个rest服务,测试下上面的四个bean是否正常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
@RestController @RequestMapping("depends") public class DependRest { @Autowired private LoadIfBeanExist loadIfBeanExist; @Autowired private LoadIfBeanNotExists loadIfBeanNotExists; @Autowired private LoadIfClzExists loadIfClzExists; @Autowired(required = false) private LoadIfClzNotExists loadIfClzNotExists; @GetMapping(path = "show") public String show() { Map<String, String> result = new HashMap<>(4); // 存在 result.put("loadIfBeanExist", loadIfBeanExist == null ? "null ==> false!" : loadIfBeanExist.getName()); // 存在 result.put("loadIfBeanNotExists", loadIfBeanNotExists == null ? "null ==> false!" : loadIfBeanNotExists.getName()); // 存在 result.put("loadIfClzExists", loadIfClzExists == null ? "null ==> false!" : loadIfClzExists.getName()); // 不存在 result.put("loadIfClzNotExists", loadIfClzNotExists == null ? "null ==> true!" : loadIfClzNotExists.getName()); return JSONObject.toJSONString(result); } } |
根据前面的分析,返回的结果应该是三个存在,一个不存在;下图执行和我们预期一致