一.前言
本篇文章主要来讲讲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是自动配置,多环境,配置动态化的利器。