SpringBoot的@Conditional条件注解详解

 更新时间:2023年12月29日 10:29:38   作者:double_lifly  
这篇文章主要介绍了SpringBoot的@Conditional条件注解详解,打开每个自动配置类,都会看到@Conditional或其衍生的条件注解,本节我们来认识下@Conditional注解,需要的朋友可以参考下

@Conditional注解

打开每个自动配置类,都会看到@Conditional或其衍生的条件注解,本节我们来认识下@Conditional注解。

认识条件注解

@Conditional注解是由Spring4.0版本引入的新特性,可根据是否满足指定的条件来决定是否进行Bean的实例化装配,比如设定类路径下包含某个jar包的时候才会对注解的类进行实例化操作。总之,是根据一些特定条件来控制Bean实例化行为,@Conditional注解代码如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
	/**
	 * All {@link Condition Conditions} that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();
}

@Conditional注解唯一的元素属性是接口Condition的数组,只有在数组中指定的所有Condition的matches方法都返回true的情况下,被注解的类才会被加载。上一篇文章讲到的OnClassCondition类就是Condition的子类之一,相关代码如下:

@FunctionalInterface
public interface Condition {
	//决定条件是否匹配
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

matches方法的第一个参数为ConditionContext,可以通过接口提供的方法来获取Spring应用的上下文信息,ConditionContext接口定义如下:

public interface ConditionContext {
	//返回BeanDefinitionRegistry注册表,可以检查Bean的定义
	BeanDefinitionRegistry getRegistry();
	//ConfigurableListableBeanFactory ,可以检查Bean是否已经存在,进一步检查Bean属性
	@Nullable
	ConfigurableListableBeanFactory getBeanFactory();
	//获取Envirment,获取当前环境变量,监测当前环境变量是否存在
	Environment getEnvironment();
	//ResourceLoader ,用于读取或检查所加载的资源
	ResourceLoader getResourceLoader();
	//返回ClassLoader ,用于检查类是否存在
	@Nullable
	ClassLoader getClassLoader();
}

matches方法的第二个参数为AnnotatedTypeMetadata ,该接口提供了访问特定类或方法的注解功能,并且不需要加载类,可以用来检查带有@Bean注解的方法上是否还有其他注解,AnnotatedTypeMetadata 接口定义如下:

public interface AnnotatedTypeMetadata {
    MergedAnnotations getAnnotations();
    default boolean isAnnotated(String annotationName) {
        return this.getAnnotations().isPresent(annotationName);
    }
    @Nullable
    default Map<String, Object> getAnnotationAttributes(String annotationName) {
        return this.getAnnotationAttributes(annotationName, false);
    }
    @Nullable
    default Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
        MergedAnnotation<Annotation> annotation = this.getAnnotations().get(annotationName, (Predicate)null, MergedAnnotationSelectors.firstDirectlyDeclared());
        return !annotation.isPresent() ? null : annotation.asAnnotationAttributes(Adapt.values(classValuesAsString, true));
    }
    @Nullable
    default MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName) {
        return this.getAllAnnotationAttributes(annotationName, false);
    }
    @Nullable
    default MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) {
        MergedAnnotation.Adapt[] adaptations = Adapt.values(classValuesAsString, true);
        return (MultiValueMap)this.getAnnotations().stream(annotationName).filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes)).map(MergedAnnotation::withNonMergedAttributes).collect(MergedAnnotationCollectors.toMultiValueMap((map) -> {
            return map.isEmpty() ? null : map;
        }, adaptations));
    }
}

isAnnotated方法能够提供判断带有@Bean注解的方法上是否还有其他注解的功能。其他方法提供不同形式的获取@Bean注解的方法上其他注解的属性信息。

条件注解的衍生注解

在Spring Boot的autoconfigure项目中提供了各类基于@Conditional注解的衍生注解,它们适用于不同的场景并提供了不同的功能。以下相关注解均位于spring-boot-auroconfigure项目的org.springframework.boot.autoconfigure.condition包下。

在这里插入图片描述

  • @ConditionalOnBean:在容器中有指定Bean的条件下。
  • @ConditionalOnClass:在classPath类路径下有指定类的条件下。
  • @ConditionalOnCloudPlatform:当指定的平台处于active状态时。
  • @ConditionalOnExpression:基于SpEL表达式的条件判断。
  • @ConditionalOnJava:基于JVM作为判断条件。
  • @ConditionalOnJndi:在JNDI存在的条件下查找指定的位置。
  • @ConditionalOnMissingBean:当容器中没有指定Bean的条件时。
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件时。
  • @ConditionalOnNotWebApplication:在项目不是一个Web项目的条件下。
  • @ConditionalOnProperty:在指定的属性有指定的值。
  • @ConditionalOnResource: 类路径是否有指定的值。
  • @ConditionalOnSingleCandidate: 在指定的Bean在容器中只有一个或者多个但是指定了首选的Bean时。
  • @ConditionalOnWebApplication: 在项目是一个Web项目的条件下。

如果仔细观察这些注解的源码,会发现他们其实都组合了@Conditional注解,不同之处时他们中指定的条件(Condition)不同。下面以@ConditionalOnWebApplication为例对衍生注解进行简单的分析。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {
	//所需的web类型
	Type type() default Type.ANY;
	//可选应用枚举类
	enum Type {
		//任何类型
		ANY,
		//基于servlet的web应用
		SERVLET,
		//基于reactive的web应用
		REACTIVE
	}
}

@ConditionalOnWebApplication注解的源码中组合了@Conditional注解,并且指定了对应的Condition为OnWebApplicationCondition。OnWebApplicationCondition类的结构与前面讲到的OnClassCondition一样,都继承自SpringBootCondition并实现了AutoConfigurationImportFilter接口。下图讲述了以OnWebApplicationCondition为例衍生注解的关系结构,重点讲述了Condition的功能和用法。

在这里插入图片描述

上面学习了Condition接口的源码,抽象类SpringBootCondition是如何实现该方法的呢?相关源码如下:

public abstract class SpringBootCondition implements Condition {
	private final Log logger = LogFactory.getLog(getClass());
	@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			logOutcome(classOrMethodName, outcome);
			recordEvaluation(context, classOrMethodName, outcome);
			return outcome.isMatch();
		}
		catch (NoClassDefFoundError ex) {
			throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
					+ ex.getMessage() + " not found. Make sure your own configuration does not rely on "
					+ "that class. This can also happen if you are "
					+ "@ComponentScanning a springframework package (e.g. if you "
					+ "put a @ComponentScan in the default package by mistake)", ex);
		}
		catch (RuntimeException ex) {
			throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
		}
	}
	......
	/**
	 * Determine the outcome of the match along with suitable log output.
	 * @param context the condition context
	 * @param metadata the annotation metadata
	 * @return the condition outcome
	 */
	public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
......
}

在抽象类SpringBootCondition中实现类matches方法,而该方法中最核心的部分是通过调用新定义的抽象方法getMatchOutcome并交由子类来实现,在matches方法中根据子类返回的结果判断是否匹配。

下面来看下OnWebApplicationCondition的源代码。

@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends FilteringSpringBootCondition {
	......
	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		boolean required = metadata.isAnnotated(ConditionalOnWebApplication.class.getName());
		ConditionOutcome outcome = isWebApplication(context, metadata, required);
		if (required && !outcome.isMatch()) {
			return ConditionOutcome.noMatch(outcome.getConditionMessage());
		}
		if (!required && outcome.isMatch()) {
			return ConditionOutcome.noMatch(outcome.getConditionMessage());
		}
		return ConditionOutcome.match(outcome.getConditionMessage());
	}
......
}

可以看出,是否匹配是由两个条件决定的:被注解的类或方法是否包含ConditionalOnWebApplication注解,是否为web应用。

  • 如果包含ConditionalOnWebApplication注解,并且不是Web应用,那么返回不匹配。
  • 如果不包含ConditionalOnWebApplication注解,并且时Web应用,那么返回不匹配。
  • 其他情况返回匹配。下面以SERVLET Web应用为例,看相关源码如何判断是否为web应用的。
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends FilteringSpringBootCondition {
	private static final String SERVLET_WEB_APPLICATION_CLASS = "org.springframework.web.context.support.GenericWebApplicationContext";
	......
	//推断web应用是否匹配
	private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata,
			boolean required) {
		switch (deduceType(metadata)) {
		case SERVLET:
			return isServletWebApplication(context);
		case REACTIVE:
			return isReactiveWebApplication(context);
		default:
			return isAnyWebApplication(context, required);
		}
	}
	......
	private ConditionOutcome isServletWebApplication(ConditionContext context) {
		ConditionMessage.Builder message = ConditionMessage.forCondition("");
		//判断常量定义是否存在
		if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, context.getClassLoader())) {
			return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
		}
		//判断BeanFactory是否存在
		if (context.getBeanFactory() != null) {
			String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
			if (ObjectUtils.containsElement(scopes, "session")) {
				return ConditionOutcome.match(message.foundExactly("'session' scope"));
			}
		}
		//判断Enviroment的类型是否为ConfigurableWebEnvironment类型
		if (context.getEnvironment() instanceof ConfigurableWebEnvironment) {
			return ConditionOutcome.match(message.foundExactly("ConfigurableWebEnvironment"));
		}
		//判断ResourceLoader的类型是否为WebApplicationContext
		if (context.getResourceLoader() instanceof WebApplicationContext) {
			return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
		}
		return ConditionOutcome.noMatch(message.because("not a servlet web application"));
	}
	......
	//从AnnotateTypeMeatdata中获取type值
	private Type deduceType(AnnotatedTypeMetadata metadata) {
		Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnWebApplication.class.getName());
		if (attributes != null) {
			return (Type) attributes.get("type");
		}
		return Type.ANY;
	}
}

首先在isWebApplication方法中进行Web应用类型的推断。这里使用AnnotatedTypeMetadata的getAnnotationAttributes方法获取所有关于@ConditionalOnWebApplication的注解属性。返回值为null说明未配置任何属性,默认为Type.ANY,如果配置属性,会获取type属性对应的值。 如果返回值为Type.SERVLET,调用isServletWebApplication方法来进行判断。该方法的判断有以下条件:

  • GenericWebApplicationContext类是否在类路径下
  • 容器内是否存在注册名为session的scope
  • 容器的Environment是否为ConfigurableWebEnvironment
  • 容器的ResourceLoader是否为WebApplicationContext
    在完成以上判断以后,得出的最终结果封装为ConditionOutcome对象返回,并在抽象类SpringBootCondition的matches方法中完成判断,返回最终结果。

到此这篇关于SpringBoot的@Conditional条件注解详解的文章就介绍到这了,更多相关@Conditional条件注解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中抽象类和接口的区别_动力节点Java学院整理

    Java中抽象类和接口的区别_动力节点Java学院整理

    java抽象类和接口最本质的区别是接口里不能实现方法--接口中的方法全是抽象方法。抽象类中可实现方法--抽象类中的方法可以不是抽象方法,下文给大家简单介绍下,需要的的朋友参考下
    2017-04-04
  • Spring @Autowired注解超详细示例

    Spring @Autowired注解超详细示例

    @Autowired注解可以用在类属性,构造函数,setter方法和函数参数上,该注解可以准确地控制bean在何处如何自动装配的过程。在默认情况下,该注解是类型驱动的注入
    2022-08-08
  • Java排序算法之桶排序详解

    Java排序算法之桶排序详解

    这篇文章主要介绍了Java排序算法之桶排序详解,桶排序是将数组中的元素放到一个一个的桶中,每个桶(bucket)代表一个区间,里面可以承载一个或者多个元素,然后将桶内的元素进行排序,再按顺序遍历桶,输出桶内元素,需要的朋友可以参考下
    2023-10-10
  • spring boot和spring cloud之间的版本关系

    spring boot和spring cloud之间的版本关系

    这篇文章主要介绍了spring boot和spring cloud之间的版本关系,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • 详解MyBatis工作原理

    详解MyBatis工作原理

    近来想写一个mybatis的分页插件,但是在写插件之前肯定要了解一下mybatis具体的工作原理吧,本文就详细总结了MyBatis工作原理,,需要的朋友可以参考下
    2021-05-05
  • 简单谈谈java自定义注解

    简单谈谈java自定义注解

    下面小编就为大家带来一篇简单谈谈java自定义注解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • Java Builder模式实现原理及优缺点解析

    Java Builder模式实现原理及优缺点解析

    这篇文章主要介绍了Java Builder模式实现原理及优缺点解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • Java 实现贪吃蛇游戏的示例

    Java 实现贪吃蛇游戏的示例

    这篇文章主要介绍了Java 如何实现贪吃蛇游戏,帮助大家更好的理解和学习使用Java,感兴趣的朋友可以了解下
    2021-03-03
  • 基于visualvm监控类实现过程详解

    基于visualvm监控类实现过程详解

    这篇文章主要介绍了基于visualvm监控类实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • MyBatis常用标签以及使用技巧总结

    MyBatis常用标签以及使用技巧总结

    在我们的学习过程中,我们经常使用到mybatis,这篇文章主要给大家介绍了关于MyBatis常用标签以及使用技巧的相关资料,需要的朋友可以参考下
    2021-05-05

最新评论