springboot对条件接口Condition的扩展和使用

写在前面

对于Condition的基础使用,可以参考这里
到 2.3 SpringBootCondition 的进击

1:springboot的基础实现类

org.springframework.boot.autoconfigure.condition.SpringBootCondition是spring boot扩展spring的org.springframework.context.annotation.Condition的抽象基础类,类签名如下:

public abstract class SpringBootCondition implements Condition {
    
    }

我们知道,具体实现类最终也需要使用在具体的注解上,也就是Condition实现类+注解是成对出现的,需要注意的是具体的实现类并非直接集成org.springframework.boot.autoconfigure.condition.SpringBootCondition,而是继承自org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition,springboot中常用的如下:

容器中有指定bean的时候为true:
	注解:ConditionalOnBean
	条件类:OnBeanCondition
容器中没有指定bean的时候为true:
	注解:ConditionalOnMissingBean
	条件类:OnBeanCondition
容器中指定的bean只有一个时为true:
	注解:ConditionalOnSingleCandidate
	条件类:OnBeanCondition
当类路径下有指定类的时候为true:
	注解:ConditionalOnClass
	条件类:OnClassCondition
当类路径下没有指定类的时候为true:
	注解:ConditionalOnMissingClass
	条件类:OnClassCondition
当指定的属性有指定的值的时候为true:
	注解:ConditionalOnProperty
	条件类:OnPropertyCondition
类路径有指定的值的时候为true:
	注解:ConditionalOnResource
	条件类:OnResourceCondition
基于springEL表达式判断是否为true:
	注解:ConditionalOnExpression
	条件类:OnExpressionCondition
基于Java版本进行判断,满足版本要求为true:
	注解:ConditionalOnJava
	条件类:OnJavaCondition
当项目不是web项目的条件下为true:
	注解:ConditionalOnNotWebApplication
	条件类:OnWebApplicationCondition
当项目时web项目的条件下为true:
	注解:ConditionalOnWebApplication
	条件类:OnWebApplicationCondition

可以看到对于注解的格式是ConditionalOnXXX,而条件类的格式是OnXXXCondition,我们在扩展自己的注解和条件类的时候也可以参考这个规范格式

2:@Conditional注解

@Conditional注解源码如下:

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

从源码中我们可以得到如下几个信息:

1:注解可以使用在类上,方法上
2:注解会在运行时起作用
3:注解需要一个类型为Condition的Class的数组

一般的,我们是将该注解和@Configuration注解一起配合使用,其实只要是标注为一个spring bean的注解都可以配合该注解一起使用,如下定义条件类:

public class OnCreateHelloControllerCondition implements Condition {
    
    

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    
    	// return true;
        return false;
    }
}

然后定义一个controller:

@Controller
@Conditional(OnCreateHelloControllerCondition.class)
public class HelloController {
    
    

    @Resource
    private AService aService;

    @ResponseBody
    @RequestMapping("/hello")
    public String hello() {
    
    
        aService.sayHi();
        return "Hello World!";
    }
}

注意其中的@Conditional(OnCreateHelloControllerCondition.class),因为我们定义OnCreateHelloControllerCondition#matches方法返回的是false,所以不会加载HelloController,如下代码测试:

@SpringBootApplication
public class HelloWorldMainApplication {
    
    

    public static void main(String[] args) throws URISyntaxException, IOException {
    
    
        ConfigurableApplicationContext run = SpringApplication.run(HelloWorldMainApplication.class, args);
        HelloController bean = run.getBean(HelloController.class);
        System.out.println(bean);
    }
}

运行:
在这里插入图片描述
修改OnCreateHelloControllerCondition #matches返回true,测试:

2021-02-04 16:57:06.491  INFO 71644 --- [           main] dongshi.daddy.HelloWorldMainApplication  : Started HelloWorldMainApplication in 1.02 seconds (JVM running for 1.414)
dongshi.daddy.controller.HelloController@6bd51ed8

3:SpringBootCondition抽象类

该抽象类是springboot扩展Condition的抽象鸡肋,springboot中的条件类都是继承自该类,该类源码如下:

public abstract class SpringBootCondition implements Condition {
    
    

	private final Log logger = LogFactory.getLog(getClass());

	@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    
		// <1>
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
    
    
			// <2>
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			// <3>
			logOutcome(classOrMethodName, outcome);
			// <4>
			recordEvaluation(context, classOrMethodName, outcome);
			// <5>
			return outcome.isMatch();
		}
		catch (NoClassDefFoundError ex) {
    
    
			...
		}
		catch (RuntimeException ex) {
    
    
			...
		}
	}
}

<1>处代码是获取使用了条件注解的类或者是方法的全限定名称,使用条件变量"org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration".equals(classOrMethodName)debug,查看springmvc自动配置类WebMvcAutoConfiguration情况时的值:
在这里插入图片描述
<2>处调用方法是一个抽象方法,强制子类实现,源码如下:

public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);

具体子类根据各自具体情况实现条件类返回ConditionOutcome对象。<3>处代码打印日志,辅助开发人员确定哪些类被自动加载。<4>记录相关信息,不做深究,<5>处返回最终是否匹配的结果。该抽象类的主要作用就是打印最终匹配的结果信息,主要的类是ConditionOutcomeConditionMessage。二者的源码不是特别复杂如下:

org.springframework.boot.autoconfigure.condition.ConditionOutcome
public class ConditionOutcome {
    
    
	// 条件是否匹配的结果
	private final boolean match;
	// 条件的匹配结果的信息对象
	private final ConditionMessage message;
}
org.springframework.boot.autoconfigure.condition.ConditionMessage
public final class ConditionMessage {
    
    
	// 匹配结果的消息
	private String message;
}

下面我们来测试下日志输出的信息。

3.1:测试匹配结果日志

为了能够看到日志,我们先在application.properties配置文件中修改org.springframework包日志的隔离级别为trace
logging.level.org.springframework=trace。为了测试更加有针对性,我们使用条件变量this.getClass().equals(OnWebApplicationCondition.class) && "org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration".equals(classOrMethodName)来测试WebMvcAutoConfiguration类的日志输出:
在这里插入图片描述
另外对于多条件匹配的情况,SpringBootCondition也提供了anyMatches方法,用来从多个条件中匹配任意的一个。

4:@ConditionalOnProperty

在前面已经提到,该注解的作用的当指定的属性有指定的值的时候为true,即,只有配置文件中有指定的属性的时候,配置才会生效,我们先来看下源码:

@Retention(RetentionPolicy.RUNTIME)
@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({
    
    OnPropertyCondition.class})
public @interface ConditionalOnProperty {
    
    
    String[] value() default {
    
    };

    String prefix() default "";

    String[] name() default {
    
    };

    String havingValue() default "";

    boolean matchIfMissing() default false;
}

其中prefix用来社设置属性的前缀,name用来设置属性的名称数组,havingValue代表prefix+name组合的属性要有指定的值,最后valuename二选其一使用即可,不可同时使用,例如如下的配置:

@ConditionalOnProperty(prefix="people",name = "hobbies", havingValue = "basketball")

就代表people.hobbies配置项的值中要有basketball。下面我们通过实例来看下。

4.1:定义配置类

@Configuration
@ConditionalOnProperty(prefix = "people", name = "hobbies", havingValue = "basketball")
public class TestConditionalOnPropertyConfiguration {
    
    
}

4.2:定义启动类

@SpringBootApplication
public class HelloWorldMainApplication {
    
    

    public static void main(String[] args) throws URISyntaxException, IOException {
    
    
        ConfigurableApplicationContext run = SpringApplication.run(HelloWorldMainApplication.class, args);
        TestConditionalOnPropertyConfiguration bean = run.getBean(TestConditionalOnPropertyConfiguration.class);
        System.out.println(bean);
    }
}

不配置指定属性,测试:

2021-02-05 11:54:35.068  INFO 52932 --- [           main] dongshi.daddy.HelloWorldMainApplication  : Started HelloWorldMainApplication in 1.219 seconds (JVM running for 1.794)
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'dongshi.daddy.myconfiguration.TestConditionalOnPropertyConfiguration' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1123)
	at dongshi.daddy.HelloWorldMainApplication.main(HelloWorldMainApplication.java:16)

配置:

people.hobbies=basketball

启动:

2021-02-05 11:57:35.755  INFO 71760 --- [           main] dongshi.daddy.HelloWorldMainApplication  : Started HelloWorldMainApplication in 1.172 seconds (JVM running for 1.748)
dongshi.daddy.myconfiguration.TestConditionalOnPropertyConfiguration$$EnhancerBySpringCGLIB$$c1791150@5b9396d3

修改为不是havingValue指定的值people.hobbies=football,测试:

2021-02-05 11:58:44.722  INFO 68468 --- [           main] dongshi.daddy.HelloWorldMainApplication  : Started HelloWorldMainApplication in 1.163 seconds (JVM running for 1.661)
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'dongshi.daddy.myconfiguration.TestConditionalOnPropertyConfiguration' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1123)
	at dongshi.daddy.HelloWorldMainApplication.main(HelloWorldMainApplication.java:16)

4.3:源码分析

从注解ConditionalOnProperty的源码可以看出,其使用的条件类是OnPropertyCondition,该类主要源码如下:

class OnPropertyCondition extends SpringBootCondition {
    
    
	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    
		...
	}
}

继承了SpringBootCondition,因此首先会调用org.springframework.boot.autoconfigure.condition.SpringBootCondition#matches()该方法源码如下:

@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    
	String classOrMethodName = getClassOrMethodName(metadata);
	try {
    
    
		//<1> 该处相当于this.getMatchOutcome,因此会调用具体实现类方法
		ConditionOutcome outcome = getMatchOutcome(context, metadata);
		...
	}
	catch (NoClassDefFoundError ex) {
    
    
		...
	}
}

通过<1>处代码会调用org.springframework.boot.autoconfigure.condition.OnPropertyCondition#getMatchOutcome,那么我们就从这里开始分析下源码,为了方便调试,我们在方法的第一行增加条件变量allAnnotationAttributes.get(0).getString("havingValue").equals("basketball"),如下:
在这里插入图片描述
方法源码如下:

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    
	// <1>
	List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
			metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
	// <2>
	List<ConditionMessage> noMatch = new ArrayList<>();
	List<ConditionMessage> match = new ArrayList<>();
	// <3>
	for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
    
    
		// <4>
		ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
		(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
	}
	// <5>
	if (!noMatch.isEmpty()) {
    
    
		return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
	}
	// <6>
	return ConditionOutcome.match(ConditionMessage.of(match));
}

<1>处代码获取在@ConditionalOnProperty注解定义的属性,这里可以认为只有一个,如下:
在这里插入图片描述
<2>处是存储匹配和不匹配的ConditionMessage消息集合。这里只会有一个有一条数据。<3>处循环处理所有的注解属性。<4>处执行匹配,源码如下:

private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) {
    
    
	// Spec是一个封装属性信息的对象,后续匹配就通过该类完成
	Spec spec = new Spec(annotationAttributes);
	List<String> missingProperties = new ArrayList<>();
	List<String> nonMatchingProperties = new ArrayList<>();
	// <determineOutcome_1> 执行具体的匹配,将缺失的属性名称和不匹配的属性名称设置
	// 到对应的集合中,这两种情况都会导致最终不匹配
	spec.collectProperties(resolver, missingProperties, nonMatchingProperties);
	// 如果是缺失属性集合不为空则说明不匹配
	if (!missingProperties.isEmpty()) {
    
    
		return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
				.didNotFind("property", "properties").items(Style.QUOTE, missingProperties));
	}
	// 如果是不匹配集合不为空,则说明不匹配
	if (!nonMatchingProperties.isEmpty()) {
    
    
		return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
				.found("different value in property", "different value in properties")
				.items(Style.QUOTE, nonMatchingProperties));
	}
	// 到这里了则说明匹配成功
	return ConditionOutcome
			.match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched"));
}

<determineOutcome_1>处源码如下:

private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) {
    
    
	for (String name : this.names) {
    
    
		String key = this.prefix + name;
		if (resolver.containsProperty(key)) {
    
    
			if (!isMatch(resolver.getProperty(key), this.havingValue)) {
    
    
				nonMatching.add(name);
			}
		}
		else {
    
    
			if (!this.matchIfMissing) {
    
    
				missing.add(name);
			}
		}
	}
}

debug如下:
在这里插入图片描述
我们再来看一个当不匹配的debug结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/wang0907/article/details/113571713