Spring框架中条件注解@Conditional的用法和示例

1、@Conditional用法示例

  @Conditional注解是用来判断是否满足指定的条件来决定是否进行Bean的实例化及装配,下面通过示例演示@ConditionalOnMissingClass、@ConditionalOnMissingBean、@ConditionalOnClass和@ConditionalOnBean的用法。

1、定义基础类,根据这些类或实例判断指定的类是否被加载

@Component
public class BeanOne {
    public void run(){
        System.out.println("BeanOne.run()方法。");
    }
}
@Component
public class BeanTwo {
    public void run(){
        System.out.println("BeanTwo.run()方法。");
    }
}
public class BeanThree {
    public void run(){
        System.out.println("BeanThree.run()方法。");
    }
}

这三个类,前两个通过@Component注解,会被加载到Spring IOC容器中,第三个类没有被注解,所以在Spring IOC容器中不存在该类的实例。

2、配置测试类

注解@ConditionalOnBean:

主要使用了注解@ConditionalOnBean,主要是根据Spring IOC容器中是否有该类的实例对象来进行判断。根据前面的配置,可以知道BeanOne、BeanTwo两个类的实例会被加载到Spring IOC容器中,而BeanThree类的实例不会被加载到容器中,所以第一种情况下,该类不会被加载,第二种情况该类会被加载。

@Configuration
//第一种情况:不会被加载,BeanThree不在IOC容器中
@ConditionalOnBean({BeanOne.class, BeanThree.class})
//第二种情况:会被加载,都在容器内
//@ConditionalOnBean({BeanOne.class, BeanTwo.class})
public class TestOnBean {

}

注解@ConditionalOnClass:

主要使用了注解@ConditionalOnClass,主要是根据类路径下是否有该类来进行判断。根据前面的配置,可以知道BeanOne、BeanThree两个类的都存在(BeanThree在类路径存在,但是不在Spring IOC容器中),而BeanFour类在类路径下不存在,所以第一种情况下,该类会被加载,第二种情况该类不会被加载。

@Configuration
//第一中情况:存在这两个类,所以会被加载
//@ConditionalOnClass({BeanOne.class, BeanThree.class})
//第二种情况:不会被加载,因为不存在BeanFour
@ConditionalOnClass(name={"com.qriver.spring.conditional.bean.base.BeanOne", "com.qriver.spring.conditional.bean.base.BeanFour"})
public class TestOnClass {

}

注解@ConditionalOnMissingBean:

主要使用了注解@ConditionalOnMissingBean,主要是根据Spring IOC容器中是否有该类的实例对象来进行判断,和注解@ConditionalOnBean正好相反。根据前面的配置,可以知道BeanOne类的实例会被加载到Spring IOC容器中,而BeanThree类的实例不会被加载到容器中,所以第一种情况下,该类会被加载,第二种情况该类不会被加载。

@Configuration
//第一种情况:BeanThree不在IOC容器中,所以会被加载
//@ConditionalOnMissingBean(BeanThree.class)
//第二种情况:BeanOne在IOC容器中,所以不会被加载
@ConditionalOnMissingBean(BeanOne.class)
public class TestOnMissingBean {

}

注解@ConditionalOnMissingClass:

主要使用了注解@ConditionalOnMissingClass,主要是根据类路径下是否有该类来进行判断,正好跟注解@ConditionalOnClass相反。根据前面的配置,可以知道BeanOne、BeanThree两个类的都存在(BeanThree在类路径存在,但是不在Spring IOC容器中),而BeanFour类在类路径下不存在,所以第一种情况下,该类不会被加载,第二种情况该类会被加载。

@Configuration
//第一中情况:存在这两个类,所以会被加载
//@ConditionalOnClass({BeanOne.class, BeanThree.class})
//第二种情况:不会被加载,因为不存在BeanFour
@ConditionalOnClass(name={"com.qriver.spring.conditional.bean.base.BeanFour"})
public class TestOnClass {

}

3、测试运行类

@Configuration
@ComponentScan("com.qriver.spring.conditional")
public class MainConfig {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        String[] beanNames = context.getBeanDefinitionNames();
        for(int i=0;i<beanNames.length;i++){
            System.out.println("bean名称:"+beanNames[i]);
        }

    }
}

运行上述代码,会根据注解条件,打印相应的被加载到Spring IOC容器中的类。这里不在展示打印结果了。

2、@Conditional注解

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

@Conditional注解的源码如下:

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

  @Conditional注解及其衍生的注解结构如下图所示,其中大部分衍生的注解都是在在Spring Boot的autoconfigure项目中定义的,即在包org.springframework.boot.autoconfigure.condition下定义,大都是为了适应Spring Boot自动装配过程中,各个组件模块在不同应用 场景下的实例加载。

在这里插入图片描述

3、Condition 条件判断

  @Conditional注解唯一的元素属性是接口Condition的数组,只有在数组中指定的所有Condition的matches方法都返回true的情况下,被注解的类才会被加载。在条件注解中,注解@Conditional及其衍生类是定义了注解的用法,而Condition 及其子类实现了该用法的具体逻辑判断,两者配合实现了条件注解的功能。

Condition的源码如下:

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

  其中,matches方法的第一个参数为ConditionContext,可通过该接口提供的方法来获得Spring应用的上下文信息。第二个参数为AnnotatedTypeMetadata,该接口提供了访问特定类或方法的注解功能,可以用来检查带有@Bean注解的方法上是否还有其他注解。

  Condition接口的层级结构, 如下所示:

在这里插入图片描述
  其中抽象类SpringBootCondition是Condition接口的子类,主要是Spring Boot为自动装配各个组件定义的条件注解匹配判断条件的基类。

4、注解@ConditionalOnWebApplication源码分析

  注解@ConditionalOnWebApplication主要用来判断当前环境是否是Web环境,其中Web环境又细分了ANY、SERVLET、REACTIVE三类。该注解主要是通过组合上述提到的@Conditional注解而实现的,其中对应的Condition实现类是OnWebApplicationCondition。源码如下所示:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {

	Type type() default Type.ANY;
	enum Type {
		ANY,
		SERVLET
		REACTIVE
	}
}

  通过源码可以发现,注解@ConditionalOnWebApplication再细分Web环境类型时,是由定义的枚举类型Type标识的。其中,让注解真正能够实现判断是否是Web环境,是什么类型的Web环境,是由OnWebApplicationCondition实现类来进行的。下面我们将详细分析OnWebApplicationCondition类,首先我们需要了解该类的继承关系,如下所示:
在这里插入图片描述

  通过OnWebApplicationCondition类的继承关系图,我们知道该类实现FilteringSpringBootCondition抽象类,而FilteringSpringBootCondition抽象类又继承了SpringBootCondition类,并实现了AutoConfigurationImportFilter接口,而SpringBootCondition抽象类在前面也提到了,是Spring Boot为自动装配各个组件定义的条件注解判断的基类。

SpringBootCondition抽象类

  SpringBootCondition抽象类实现了Condition接口,实现了接口中的matches(ConditionContext context, AnnotatedTypeMetadata metadata)方法,在该方法中,判断是否符合条件的核心逻辑是通过抽象方法getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata)实现,该方法交由子类具体实现。在实现的matches()方法中,主要是完成了抽象方法getMatchOutcome()的调用,然后就是相关日志信息的记录。

//SpringBootCondition.java类

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

AutoConfigurationImportFilter接口

@FunctionalInterface
public interface AutoConfigurationImportFilter {
	boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);
}

  该接口主要是判断autoConfigurationClasses对应的类,是否符合可以被加载的条件。在Spring Boot自动加载各个组件的过程中,用于快速排除不符合条件的类。这里暂时只分析该接口实现的功能,等梳理Spring Boot加载过程的时候,再详细了解该接口再Spring Boot框架中的应用。

FilteringSpringBootCondition抽象类

  FilteringSpringBootCondition抽象类继承了前面提到的SpringBootCondition类,并实现了AutoConfigurationImportFilter接口。FilteringSpringBootCondition抽象类主要实现了AutoConfigurationImportFilter接口的match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata)方法,其中判断是否符合加载条件的逻辑,又通过定义的抽象方法getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata)交由子类进行了实现。同时还定义了一个内部类ClassNameFilter,用来判断指定类(字符串形式)是否存在该类加载器中。

AutoConfigurationImportFilter接口的match()方法在FilteringSpringBootCondition中的实现,代码如下:

@Override
	public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
		//获取ConditionEvaluationReport实例,用来记录条件判断的详细信息
		ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
		//判断autoConfigurationClasses数组中的类,是否符合加载条件,通过抽象方法getOutcomes()实现,该抽象方法交由子类进行实现
		ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
		boolean[] match = new boolean[outcomes.length];
		//把ConditionOutcome[]数组转成了boolean[],记录autoConfigurationClasses数组中对应的类是否符合加载条件
		for (int i = 0; i < outcomes.length; i++) {
			match[i] = (outcomes[i] == null || outcomes[i].isMatch());
			if (!match[i] && outcomes[i] != null) {
				//记录不配的具体日志
				logOutcome(autoConfigurationClasses[i], outcomes[i]);
				if (report != null) {
					report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
				}
			}
		}
		return match;
	}

内部类实现的用来判断指定类(字符串形式)是否存在该类加载器中的判断,由下面方法实现,共

protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter,
	ClassLoader classLoader) {
	if (CollectionUtils.isEmpty(classNames)) {
		return Collections.emptyList();
	}
	List<String> matches = new ArrayList<>(classNames.size());
	for (String candidate : classNames) {
		//调用内部类,判断candidate是否存在于当前的类加载器中
		if (classNameFilter.matches(candidate, classLoader)) {
			matches.add(candidate);
		}
	}
	return matches;
}

判断类是否存在的方法,通过调用resolve()方法,尝试使用类加载器根据className去加载该类,如果抛出ClassNotFoundException异常,说明该类不存在,否则认为该类存在。

static boolean isPresent(String className, ClassLoader classLoader) {
	if (classLoader == null) {
		classLoader = ClassUtils.getDefaultClassLoader();
	}
	try {
		resolve(className, classLoader);
		return true;
	}
	catch (Throwable ex) {
		return false;
	}
}

protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
	if (classLoader != null) {
		return classLoader.loadClass(className);
	}
	return Class.forName(className);
}

OnWebApplicationCondition类

  该类继承FilteringSpringBootCondition抽象类,主要用来判断项目是否是Web环境。通过前面的分析,我们可以知道,OnWebApplicationCondition类间接的继承了SpringBootCondition抽象类,所以需要实现SpringBootCondition抽象类中定义的getMatchOutcome()方法。同时,OnWebApplicationCondition类继承的FilteringSpringBootCondition抽象类,也有一个抽象方法getOutcomes()方法需要实现。

  首先,我们来分析OnWebApplicationCondition类是如何FilteringSpringBootCondition抽象类中的getOutcomes()方法的。

@Override
protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
		AutoConfigurationMetadata autoConfigurationMetadata) {
	//定义保存是否匹配结果的变量
	ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
	//循环,遍历每一个autoConfigurationClass是否匹配,并把匹配结果存到上面定义的变量中,具体的判断交由getOutcome()方法实现
	for (int i = 0; i < outcomes.length; i++) {
		String autoConfigurationClass = autoConfigurationClasses[i];
		if (autoConfigurationClass != null) {
			outcomes[i] = getOutcome(
					autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication"));
		}
	}
	return outcomes;
}

  通过上面的源码,我们可以知道,首先由工具类AutoConfigurationMetadataLoader定义的内部类PropertiesAutoConfigurationMetadata(AutoConfigurationMetadata的实现类)来获取autoConfigurationClass."ConditionalOnWebApplication"对应的type(对应的type是在Spring Boot初始化时加载的元数据配置)。

  这里提到的type是在@EnableAutoConfiguration注解上通过@Import注解导入的AutoConfigurationImportSelector实现类(ImportSelector实现类)的selectImports()方法中进行加载的。加载元数据的配置用到了AutoConfigurationMetadataLoader类提供的loadMetaData方法,该方法会默认加载类路径下META-INF/spring-autoconfigure-metadata.properties内的配置。spring-autoconfigure-metadata.properties文件内的配置格式:自动配置类的全限定名.注解名称=值

代码如下:

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
	……
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
	……
	}

通过上述在初始化时,加载的元数据,就是为了这里过滤自动配置使用。通过调用新定义的getOutcome(type)方法来进行是否匹配的判断,具体实现如下:

private ConditionOutcome getOutcome(String type) {
	if (type == null) {
		return null;
	}
	//消息构建类
	ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class);
	//判断SERVLET类型的Web应用
	if (ConditionalOnWebApplication.Type.SERVLET.name().equals(type)) {
		//判断org.springframework.web.context.support.GenericWebApplicationContext类是否存在类加载器中,如果不存在说明不是SERVLET类型的Web应用
		if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
			return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
		}
	}
	//判断REACTIVE类型的Web应用
	if (ConditionalOnWebApplication.Type.REACTIVE.name().equals(type)) {
		//判断org.springframework.web.reactive.HandlerResult类是否存在类加载器中,如果不存在说明不是REACTIVE类型的Web应用
		if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
			return ConditionOutcome.noMatch(message.didNotFind("reactive web application classes").atAll());
		}
	}
	//如果org.springframework.web.context.support.GenericWebApplicationContext类和org.springframework.web.reactive.HandlerResult类都不在类加载器中,说明既不是SERVLET类型的Web应用也不是REACTIVE类型的Web应用
	if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())
			&& !ClassUtils.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
		return ConditionOutcome.noMatch(message.didNotFind("reactive or servlet web application classes").atAll());
	}
	return null;
}

然后,我们来分析OnWebApplicationCondition类如何实现SpringBootCondition抽象类中定义的getMatchOutcome()方法。具体实现如下:

@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
	//首先判断ConditionalOnWebApplication直接是否存在
	boolean required = metadata.isAnnotated(ConditionalOnWebApplication.class.getName());
	//判断是否是Web应用
	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());
}

根据上面的代码,我们可以知道,判断是否是Web应用的逻辑是由isWebApplication()方法实现的,首先由deduceType()方法判断具体的Web类型,然后再交给具体的方法进行判断,具体实现如下:

private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata,
	boolean required) {
	//判断Web类型
	switch (deduceType(metadata)) {
	case SERVLET:
		return isServletWebApplication(context);
	case REACTIVE:
		return isReactiveWebApplication(context);
	default:
		return isAnyWebApplication(context, required);
	}
}

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

三种Web类型的判断的逻辑,基本上是一样的,这里我们以SERVLET类型的Web应用的为例,来分析具体的实现,代码如下:

private ConditionOutcome isServletWebApplication(ConditionContext context) {
	//日志消息构建实例
	ConditionMessage.Builder message = ConditionMessage.forCondition("");
	//存在对应的GenericWebApplicationContext类,所以不是SERVLET类型Web应用
	if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, context.getClassLoader())) {
		return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
	}
	if (context.getBeanFactory() != null) {
		//存在session的Scope则是SERVLET类型Web应用(后续需要详细了解该条件的判断依据(待处理))
		String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
		if (ObjectUtils.containsElement(scopes, "session")) {
			return ConditionOutcome.match(message.foundExactly("'session' scope"));
		}
	}
	//如果是ConfigurableWebEnvironment的上下文,判断是SERVLET类型Web应用
	if (context.getEnvironment() instanceof ConfigurableWebEnvironment) {
		return ConditionOutcome.match(message.foundExactly("ConfigurableWebEnvironment"));
	}
	//如果是资源加载器是WebApplicationContext类型,判断是SERVLET类型Web应用
	if (context.getResourceLoader() instanceof WebApplicationContext) {
		return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
	}
	//其他情况,则都不是SERVLET类型Web应用
	return ConditionOutcome.noMatch(message.because("not a servlet web application"));
}
5、注解@ConditionalOnWebApplication的应用

  注解@ConditionalOnWebApplication在Spring Boot环境初始化的过程中,用来判断当前项目是否是Web应用,具体是什么类型的Web应用,会根据不同的结果进行相应的初始化过程,后续在分析Spring Boot初始化过程中在分析注解@ConditionalOnWebApplication在Spring Boot框架中的应用。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Boot,@Conditional注解和@AutoConfigureAfter注解是非常常用的注解,下面我来给你详细解析一下这两个注解。 ## @Conditional注解 @Conditional注解Spring Boot非常重要的一个注解,在Spring Boot,很多自动配置都是通过@Conditional注解来实现的。 @Conditional注解可以根据满足某些条件来决定是否创建一个bean。比如,我们可以根据某个类是否存在来决定是否创建一个bean,具体示例如下: ```java @Configuration @Conditional(ExistClassCondition.class) public class MyConfiguration { @Bean public MyBean myBean() { return new MyBean(); } } public class ExistClassCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { try { Class.forName("com.example.MyClass"); return true; } catch (ClassNotFoundException e) { return false; } } } ``` 上面的代码,我们定义了一个MyConfiguration类,并且在该类上加了@Conditional注解,该注解的参数是一个Condition的实现类ExistClassCondition。ExistClassCondition类的matches方法返回true的条件是com.example.MyClass类存在。 这样,当com.example.MyClass类存在的时候,MyBean这个bean才会被创建。否则,MyBean这个bean不会被创建。 ## @AutoConfigureAfter注解 @AutoConfigureAfter注解也是Spring Boot比较常用的注解之一,它可以用来控制自动配置的顺序。 比如,我们可以通过@AutoConfigureAfter注解来控制某个自动配置类在另一个自动配置类之后加载,具体示例如下: ```java @Configuration @AutoConfigureAfter(MyAutoConfiguration.class) public class MyAnotherAutoConfiguration { // ... } ``` 上面的代码,我们定义了一个MyAnotherAutoConfiguration类,并且在该类上加了@AutoConfigureAfter注解,该注解的参数是MyAutoConfiguration.class。这样,在Spring Boot启动时,MyAutoConfiguration这个自动配置类会先于MyAnotherAutoConfiguration这个自动配置类被加载。 总结:@Conditional注解和@AutoConfigureAfter注解都是Spring Boot非常实用的注解。通过@Conditional注解可以实现根据满足某些条件来决定是否创建一个bean,通过@AutoConfigureAfter注解可以控制自动配置类的加载顺序,这些都是我们在实际开发非常常用的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

姠惢荇者

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

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

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

打赏作者

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

抵扣说明:

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

余额充值