• Spring Boot的Conditional扩展


    一.前言

    本篇文章主要来讲讲Conditional的作用,形式和实现原理,只有透彻的理解了Conditional系列,才能更好的学习spring boot的自动配置,因为它是自动配置能够实现的一大利器!主要从以下几个方面介绍Conditional

    • Conditional是什么及作用
    • Conditional的原理
    • Conditional系列
    • Conditional系列在Spring Boot中的应用

    二.Conditional是什么及作用

    Conditional是spring框架中spring-context模块提供的注解,是spring容器创建Bean的条件,如果条件满足,则创建Bean,否则不创建。

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.METHOD})
    public @interface Conditional {
    	Class<? extends Condition>[] value();
    }
    

    当注解中的value Condition条件全部匹配时,则创建Bean,否则不创建。

    public interface Condition {
    	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
    }
    

    Condition是具体的条件,Conditional是对Condition的使用方式,反之,Condition是Conditional的作用方式。

    如果匹配,matches将返回true。ConditionContext参数是进行条件匹配逻辑的上下文,可以用于访问容器,注册器,环境等。AnnotatedTypeMetadata是Conditional注解作用的类型metadata。利用这两者,可以自定义自己的条件匹配。

    可以看个实例,如ProfileCondition,

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

    当环境的profile与注解指定的profile匹配时返回true,这样可以根据不同环境配置不同的Bean,或者指定某些Bean只在特定环境中才生效。


    三.Conditional的原理

    Conditional注解的处理器是ConditionEvaluator,其内部会解析Conditional注解,获取注解value配置的所有Condition,然后执行Condition的match方法:

    public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
    	// 如果未被Conditional注释,则返回
    	if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
    		return false;
    	}
    	if (phase == null) {
    		// 如果是注解metadata,且是配置类,则执行配置阶段的Condition
    		if (metadata instanceof AnnotationMetadata &&
    				ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
    			return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
    		}
    		// 否则解析注册Bean阶段的Condition
    		return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
    	}
    	// 获取Conditional注解的value,所有的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);
    		}
    	}
    	// 执行所有的Condition,只要其中一个不满足,则返回true,表示应该跳过
    	AnnotationAwareOrderComparator.sort(conditions);
    	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;
    			}
    		}
    	}
    	return false;
    }
    

    ConditionEvaluator负责处理Conditional注解,然ConditionEvaluator的调用是在各大BeanDefinitionReader或者Configuration Class解析器中:

    • ConfigurationClassParser,它负责解析被Configuration注解的Class定义,在解析之初,会调用ConditionEvaluator,执行Condition match。如果not match,则该配置类将被略过,不被处理
    • ConfigurationClassBeanDefinitionReader,它负责读取并处理被ConfigurationClassParser解析后生成的完整的Configuration Class(如import,bean方法等),在处理Bean方法、import时都会调用ConditionEvaluator,如果not match,则返回不执行Bean方法或者不处理import
    • AnnotatedBeanDefinitionReader,它负责处理被注解的Bean定义(如Configuration/Component),在处理之初,如果被注解的Bean被Conditional修饰,就是调用ConditionEvaluator进行处理,如果not match,则这个被注解的Bean定义也不进行处理

    Conditional注解的处理,是综合ConditionEvaluator和以上的BeanDefinition Reader和Parser,在解析配置之处时执行Condition而起到作用。


    四. Conditional的形式

    前面提到Conditional是spring-context模块中定义的注解,但是Conditional需要和Condition配合使用才能应用在各种场景,这样需要应用开发者编写相应的Condition,就无法做到开箱即用,应用起来比较麻烦。

    因此,spring boot对Conditional做了很多扩展,以适应各种应用场景。这样就形成了Conditional系列,这里只罗列一些常用的

    • ConditionalOnBean,只匹配特定的bean在beanfactory时。常用于相关联的bean之间的依赖注册
    • ConditionalOnClass,只匹配特定的bean在类路径上。常用于某个某块被引用时,注册bean,如spring boot自动配置
    • ConditionalOnMissingBean,只匹配特定的bean不在beanfactory时。常用于防止重复注册
    • ConditionalOnMissingClass,只匹配特定的bean不在类路上。常用于自动配置,某个bean不存在,然后才注册另外的bean
    • ConditionalOnWebApplication,只匹配是web applicationContext时。常用于如果是web环境,注册某些和web环境相关联的bean
    • ConditionalOnResource,只匹配某些特定资源存在时
    • ConditionalOnProperty,只匹配某个特定属性存在environment时,默认情况需要存在且为true时。常用于开关注册某些bean,或者策略模式
    • ConditionalOnNotWebApplication,只匹配非web applicationContext时

    五.Conditional系列在Spring Boot中的应用

    1.spring boot中DispatcherServlet注册至Web容器

    如上图中,当DispatcherServlet在上下文中,则注册ServletRegistrationBean。该Bean用于将DispatcherServlet注册进web容器。ConditionalOnBean常用于依赖Bean之间的注册。

    2.spring boot中数据源的自动配置

    当DataSource和EmbeddedDatabaseType同时在类路径上时,就自动配置Datasource。所以只需要引入spring-boot-starter-jdbc,就可以完成数据源的自动配置。因为Datasource是jdk中的类,必然在类路径上。spring-boot-starter-jdbc中会传递依赖spring-jdbc模块,从而将EmbeddedDatabaseType引入类路径中,即完成了datasource auto configuration。


    六.总结

    Conditional是spring中用于在注册bean时进行的条件选择,以抉择是否注册该Bean。真正大放光芒之际仍在spring boot的自动配置中得到大量的应用。Conditional是自动配置,多环境,配置动态化的利器。

  • 相关阅读:
    CNN的学习记录
    softmax和softmax loss的学习记录
    Vue2.0 生命周期
    Vue methods 方法
    Vue2.0 全局操作 Vue.set
    Vue2.0 自定义指令 vuedirective
    Vue2.0 构造器的延伸 Vue.extend
    vue computed
    vuecli 脚手架分析
    h5表单介绍与案例
  • 原文地址:https://www.cnblogs.com/lxyit/p/12511735.html
Copyright © 2020-2023  润新知