一.前言

本篇文章主要来讲讲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容器


boot spring 继承docker spring boot condition_spring-boot

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

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


boot spring 继承docker spring boot condition_bc_02

当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是自动配置,多环境,配置动态化的利器。