Spring源码学习笔记(5)——@Conditonal注解

时间:2022-07-24
本文章向大家介绍Spring源码学习笔记(5)——@Conditonal注解,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Spring源码学习笔记(5)——@Conditonal注解

一. @Conditonal注解基本使用

  1. 简介 @Conditonal是Spring中常用的一个注解,标记了该注解后,只有在满足@Conditonal中指定的所有条件后,才可以向容器中注入组件。
  2. 接口信息 @Conditonal只有一个属性,即一个Condition接口的数组,表示该@Conditonal注解需要满足的所有条件,只有当所有的Condition的匹配时,才向IoC容器中注册组件。源码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {

	//只有当所有的Condition的匹配时,才向IoC容器中注册组件
	Class<? extends Condition>[] value();

}
  1. Condition接口只有一个方法,判断是否满足当前条件,源码如下:
public interface Condition {

	/**
	 * 判断是否满足当前条件
	 * @param context 当前条件所处的上下文环境
	 * @param metadata @Conditional注解所描述的类型的元信息
	 * @return 返回true时,表示满足条件,组件可以被注册
	 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}
  1. 示例程序 下面简单演示@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 操作系统";
    }
}
  1. 下面,定义基于操作系统的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));
    }
}
  1. 接下来,使用@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();
    }
}
  1. 最后打印所有注入到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()是一个模板方法,交给子类去实现。