Spring源码学习笔记(5)——@Conditonal注解
一. @Conditonal注解基本使用
-
简介
@Conditonal是Spring中常用的一个注解,标记了该注解后,只有在满足@Conditonal中指定的所有条件后,才可以向容器中注入组件。
-
接口信息
@Conditonal只有一个属性,即一个Condition接口的数组,表示该@Conditonal注解需要满足的所有条件,只有当所有的Condition的匹配时,才向IoC容器中注册组件。源码如下:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Conditional { //只有当所有的Condition的匹配时,才向IoC容器中注册组件 Class<? extends Condition>[] value(); }
Condition接口只有一个方法,判断是否满足当前条件,源码如下:
public interface Condition { /** * 判断是否满足当前条件 * @param context 当前条件所处的上下文环境 * @param metadata @Conditional注解所描述的类型的元信息 * @return 返回true时,表示满足条件,组件可以被注册 */ boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
-
示例程序
下面简单演示@Conditional注解的使用,根据当前操作系统的类型,注册不同的Service。
首先,定义Service接口和不同操作系统的实现:
/** * @Auther: ZhangShenao * @Date: 2018/9/25 13:39 * @Description:操作系统Service接口 */ public interface OSService { String showOSInfo(); }
/** * @Auther: ZhangShenao * @Date: 2018/9/25 13:40 * @Description:Windows操作系统实现 */ public class WindowsOSServiceImpl implements OSService{ @Override public String showOSInfo() { return "Windows 操作系统"; } }
/** * @Auther: ZhangShenao * @Date: 2018/9/25 13:41 * @Description:Mac操作系统实现 */ public class MacOSServiceImpl implements OSService{ @Override public String showOSInfo() { return "Mac 操作系统"; } }
下面,定义基于操作系统的Condition类,根据当前环境中的操作系统名称进行匹配:
/** * @Auther: ZhangShenao * @Date: 2018/9/25 13:44 * @Description:Windows操作系统Condition */ public class WindowsCondition implements Condition{ private static final String WINDOWS_OS_NAME = "windows"; @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String osName = environment.getProperty("os.name"); return (!StringUtils.isEmpty(osName) && osName.contains(WINDOWS_OS_NAME)); } }
/** * @Auther: ZhangShenao * @Date: 2018/9/25 13:48 * @Description:Mac操作系统Condition */ public class MacCondition implements Condition{ private static final String MAC_OS_NAME = "Mac"; @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String osName = environment.getProperty("os.name"); return (!StringUtils.isEmpty(osName) && osName.contains(MAC_OS_NAME)); } }
接下来,使用@Conditional注解注册Bean,并指定自定义的Condition
/** * @Auther: ZhangShenao * @Date: 2018/9/21 10:15 * @Description:Spring配置类 */ @Configuration @ComponentScan public class MainConfig { @Bean @Conditional(MacCondition.class) public OSService macOSService(){ return new MacOSServiceImpl(); } @Bean @Conditional(WindowsCondition.class) public OSService windowsOSService(){ return new WindowsOSServiceImpl(); } }
最后打印所有注入到IoC容器中的OSServiceBean,可以看到只有MacOSServiceImpl实例被注入进来了。(本例使用Mac系统演示)
/** * @Auther: ZhangShenao * @Date: 2018/9/21 10:17 * @Description: */ public class AnnotationMain { public static void main(String[] args) { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class); Map<String, OSService> osServices = applicationContext.getBeansOfType(OSService.class); osServices.forEach((s, osService) -> System.err.println(osService)); } }
二. 源码分析
在Condition接口的matches()方法加断点,可以追溯到ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsForBeanMethod方法中,该方法会根据配置类的标记了@Bean注解的方法,向容器中注入Bean,方法定义如下:
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();
// Do we need to mark the bean as skipped by its condition?
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
configClass.skippedBeanMethods.add(methodName);
return;
}
if (configClass.skippedBeanMethods.contains(methodName)) {
return;
}
// Consider name and any aliases
AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
List<String> names = new ArrayList<String>(Arrays.asList(bean.getStringArray("name")));
String beanName = (names.size() > 0 ? names.remove(0) : methodName);
// Register aliases even when overridden
for (String alias : names) {
this.registry.registerAlias(beanName, alias);
}
// Has this effectively been overridden before (e.g. via XML)?
if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
return;
}
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
beanDef.setResource(configClass.getResource());
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
if (metadata.isStatic()) {
// static @Bean method
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
beanDef.setFactoryMethodName(methodName);
}
else {
// instance @Bean method
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setUniqueFactoryMethodName(methodName);
}
beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);
AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);
Autowire autowire = bean.getEnum("autowire");
if (autowire.isAutowire()) {
beanDef.setAutowireMode(autowire.value());
}
String initMethodName = bean.getString("initMethod");
if (StringUtils.hasText(initMethodName)) {
beanDef.setInitMethodName(initMethodName);
}
String destroyMethodName = bean.getString("destroyMethod");
if (destroyMethodName != null) {
beanDef.setDestroyMethodName(destroyMethodName);
}
// Consider scoping
ScopedProxyMode proxyMode = ScopedProxyMode.NO;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
if (attributes != null) {
beanDef.setScope(attributes.getAliasedString("value", Scope.class, configClass.getResource()));
proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = ScopedProxyMode.NO;
}
}
// Replace the original bean definition with the target one, if necessary
BeanDefinition beanDefToRegister = beanDef;
if (proxyMode != ScopedProxyMode.NO) {
BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
new BeanDefinitionHolder(beanDef, beanName), this.registry, proxyMode == ScopedProxyMode.TARGET_CLASS);
beanDefToRegister = new ConfigurationClassBeanDefinition(
(RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata);
}
if (logger.isDebugEnabled()) {
logger.debug(String.format("Registering bean definition for @Bean method %s.%s()",
configClass.getMetadata().getClassName(), beanName));
}
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
该方法会扫描所有@Configuration配置类的标记了@Bean的注解的方法,将方法封装成一个BeanMethod,向容器中注入。在处理前,会调用ConditionEvaluator的shouldSkip()方法判断当前Bean是否要跳过注册。ConditionEvaluator对象是在ConfigurationClassBeanDefinitionReader构造器中实例化的,其主要作用就是处理@Conditional注解的相关逻辑。
在shouldSkip()方法中,会@Bean所在方法或者类上的@Conditional注解,并获取@Conditional注解的所有Condition条件对象,依次调用matcher()方法。只要有一个Condition匹配不成功,就跳过该Bean的注册。具体逻辑如下:
public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
//phase为ConfigurationPhase.REGISTER_BEAN注册Bean阶段
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
//获取该@Bean方法上所有的Condition对象,包括方法上定义的和类上定义的
List<Condition> conditions = new ArrayList<Condition>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
AnnotationAwareOrderComparator.sort(conditions);
//依次调用所有Condition对象的matches()方法,只要有一个匹配失败,就跳过该Bean的注册
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if (requiredPhase == null || requiredPhase == phase) {
if (!condition.matches(this.context, metadata)) {
return true;
}
}
}
三. 在SpringBoot中的应用
@Conditional注解在SpringBoot框架中得到了广泛的使用,SpringBoot定义了大量的基于@Conditional注解的衍生注解,并通过这些注解控制Bean的注册。下面以常用的@ConditionalOnBean注解为例,进行简单的介绍。
@ConditionalOnBean是SpringBoot定义的一个常用的条件注解,含义是只有当IoC容器中已经存在指定Class的实例时,才满足条件。源码如下:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
/**
* The class type of bean that should be checked. The condition matches when all of
* the classes specified are contained in the {@link ApplicationContext}.
* @return the class types of beans to check
*/
Class<?>[] value() default {};
/**
* The class type names of bean that should be checked. The condition matches when all
* of the classes specified are contained in the {@link ApplicationContext}.
* @return the class type names of beans to check
*/
String[] type() default {};
/**
* The annotation type decorating a bean that should be checked. The condition matches
* when all of the annotations specified are defined on beans in the
* {@link ApplicationContext}.
* @return the class-level annotation types to check
*/
Class<? extends Annotation>[] annotation() default {};
/**
* The names of beans to check. The condition matches when all of the bean names
* specified are contained in the {@link ApplicationContext}.
* @return the name of beans to check
*/
String[] name() default {};
/**
* Strategy to decide if the application context hierarchy (parent contexts) should be
* considered.
* @return the search strategy
*/
SearchStrategy search() default SearchStrategy.ALL;
}
可以看到,@ConditionalOnBean注解使用了@Conditional,并指定了条件OnBeanCondition。
OnBeanCondition继承自SpringBootCondition,SpringBootCondition是SpringBoot定义的Condition的父类,其核心逻辑是通过getMatchOutcome()方法拿到匹配条件,并进行判断。getMatchOutcome()是一个模板方法,交给子类去实现。