条件化的Bean——@Conditional注解
当我们希望某个Bean满足了一定的条件才会被创建的时候,我们就可以使用@Conditional注解(Spring 4引入)来实现这种功能。该注解可以用到带有@Bean注解的方法上,如果给定的条件计算结果为true,就会创建这个bean,否则的话,这个bean会被忽略。
常见条件注解
@ConditionalOnBean
(仅仅在当前上下文中存在某个对象时,才会实例化一个Bean)@ConditionalOnClass
(某个class位于类路径上,才会实例化一个Bean)@ConditionalOnExpression
(当表达式为true的时候,才会实例化一个Bean)@ConditionalOnMissingBean
(仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean)@ConditionalOnMissingClass
(某个class类路径上不存在的时候,才会实例化一个Bean)@ConditionalOnNotWebApplication
(不是web应用)
自定义条件
假设有一个名为MagicBean的类,我们希望只有设置了magic环境属性的时候,Spring才会实例化这个类。
条件化地配置bean
@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean() {
return new MagicBean();
}
检查是否存在magic属性
package com.zheng.spring;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class MagicExistsCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment env = conditionContext.getEnvironment();
return env.containsProperty("magic"); // 检查magic属性
}
}
matches()方法通过给定的ConditionContext对象进而得到Environment对象,并使这个对象检查环境中是否存在名为magic的环境属性。
其中ConditionContext
是一个接口
public interface ConditionContext {
BeanDefinitionRegistry getRegistry();
ConfigurableListableBeanFactory getBeanFactory();
Environment getEnvironment();
ResourceLoader getResourceLoader();
ClassLoader getClassLoader();
}
- 借助
getRegistry()
返回的BeanDefinitionRegistry
检查bean定义; - 借助
getBeanFactory()
返回的ConfigurableListableBeanFactory
检查bean是否存在,甚至探查bean的属性; - 借助
getEnvironment()
返回的Environment检查环境变量是否存在以及它的值是什么; - 读取并探查
getResourceLoader()
返回的ResourceLoader
所加载的资源; - 借助
getClassLoader()
返回的ClassLoader加载并检查类是否存在。
AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上还有什么其他的注解。像ConditionContext一样,AnnotatedTypeMetadata也是一个接口。
public interface AnnotatedTypeMetadata {
boolean isAnnotated(String var1);
Map<String, Object> getAnnotationAttributes(String var1);
Map<String, Object> getAnnotationAttributes(String var1, boolean var2);
MultiValueMap<String, Object> getAllAnnotationAttributes(String var1);
MultiValueMap<String, Object> getAllAnnotationAttributes(String var1, boolean var2);
}
借助isAnnotated()
方法,我们能够判断带有@Bean注解的方法是不是还有其他特定的注解。借助其他的那些方法,我们能够检查@Bean注解的方法上其他注解的属性。
@Profile注解与条件化的关系
从Spring 4开始,@Profile注解基于@Conditional和Condition实现。
下面是@Profile实现的源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
其中ProfileCondition检查某个bean profile是否可用,源码如下
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
}
return true;
}
}
ProfileCondition通过AnnotatedTypeMetadata得到了用于@Profile注解的所有属性。借助该信息,它会明确地检查value属性,该属性包含了bean的profile名称。然后,它根据通过ConditionContext得到的Environment来检查[借助acceptsProfiles()方法]该profile是否处于激活状态。如果是则选择这个profile bean进行创建。