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();
    
    }
    

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

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

    下面简单演示@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()是一个模板方法,交给子类去实现。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张申傲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值