一、前言
在博文《SpringBoot系列十一》:精讲如何使用@Conditional系列注解做条件装配中我们讨论了如何使用@Conditional系列注解做条件装配,假如我想自定义条件装配改怎么做呢?
本文就如何自定义条件装配展开讨论。
二、@Conditional介绍
@Conditional注解是从spring4.0版本才有的,其是一个条件装配注解,可以用在任何类型或者方法上面,以指定的条件形式限制bean的创建;即当所有条件都满足的时候,被@Conditional标注的目标才会被spring容器处理。
- @Conditional本身也是一个父注解,从SpringBoot1.0版本开始派生出了大量的子注解;用于Bean的按需加载。
- @Conditional注解和其所有子注解必须依托于被
@Component衍生注解
标注的类,即Spring要能扫描到@Conditional衍生注解所在的类,才能做进一步判断。- @Conditional衍生注解可以加在类 或 类的方法上;加在类上表示类的所有方法都做条件装配、加在方法上则表示只有当前方法做条件装配。
1、@Conditional源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition} classes that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
@Conditional注解只有一个value参数,类型是:Condition类型的数组;而Condition
是一个接口,其表示一个条件判断,内部的matches()
方法返回true或false;当所有Condition都成立时,@Conditional的条件判断才成立。
1)Condition接口
@FunctionalInterface
public interface Condition {
/**
* 判断条件是否匹配
* @param context 条件判断上下文
* @param metadata 注解元数据
* @return 如果条件匹配返回TRUE,否者返回FALSE
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
Condition是一个函数式接口,其内部只有一个matches()方法,用来判断条件是否成立的,方法有2个入参:
context:条件上下文,ConditionContext接口类型,用来获取容器中的信息
metadata:用来获取被@Conditional标注的对象上的所有注解信息
2)ConditionContext接口
public interface ConditionContext {
/**
* 返回bean定义注册器BeanDefinitionRegistry,通过注册器获取bean定义的各种配置信息
*/
BeanDefinitionRegistry getRegistry();
/**
* 返回ConfigurableListableBeanFactory类型的bean工厂,相当于一个ioc容器对象
*/
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
/**
* 返回当前spring容器的环境配置信息对象
*/
Environment getEnvironment();
/**
* 返回正在使用的资源加载器
*/
ResourceLoader getResourceLoader();
/**
* 返回类加载器
*/
@Nullable
ClassLoader getClassLoader();
}
ConditionContext接口中提供了一些常用的方法,用于获取spring容器中的各种信息。
三、自定义条件装配
先看看@ConditionalOnClass注解是怎么做的?
1、@ConditionalOnClass
@ConditionalOnClass注解被@Conditional(OnClassCondition.class)
注解标注,结合上面@Conditional注解的介绍来看。可以知道@ConditionalOnClass的条件装配判断逻辑依赖于Condition
接口的实现类OnClassCondition
实现。
再看OnClassCondition的类图:
理论上只需要OnClassCondition
重写Condition
接口的matches(ConditionContext, AnnotatedTypeMetadata)
方法即可自定义条件装配的判定逻辑。然而OnClassCondition
并没有,matches()方法的重写由OnClassCondition
的父类SpringBootCondition
来完成。
下面模仿其自定义一个《系统属性名称与属性值匹配的条件注解@ConditionalOnSystemProperty》。
2、自定义条件装配注解@ConditionalOnSystemProperty
注解@ConditionalOnSystemProperty注解的作用是获取到 启动jar程序命令行中 的Program arguments
属性的名称与属性值做匹配进而决定是否装配类。
对于如何在IDEA中设置Program arguments
,参考博文:https://blog.csdn.net/Saintmm/article/details/124603343。
整体代码目录结构如下:
1> 自定义Condition
package com.saint.autoconfigure.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap;
import java.util.Objects;
/**
* 系统属性值与值匹配条件
*
* @author Saint
*/
public class OnSystemPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes =
metadata.getAllAnnotationAttributes(ConditionalOnSystemProperty.class.getName());
String name = (String) attributes.getFirst("name");
String value = (String) attributes.getFirst("value");
String systemPropertyValue = System.getProperty(name);
// 比较系统属性值和方法的值
if (Objects.equals(systemPropertyValue, value)) {
System.out.printf("系统属性【名称: %s】找到匹配值:%s \n", name, value);
return true;
}
return false;
}
}
2> 自定义条件装配注解
package com.saint.autoconfigure.condition;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
/**
* 系统属性名称与属性值匹配条件注解
*
* @author Saint
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionalOnSystemProperty {
/**
* 属性名
*/
String name();
/**
* 属性值
*/
String value();
}
3> 定义 使用条件装配注解的配置类
package com.saint.config;
import com.saint.autoconfigure.condition.ConditionalOnSystemProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 条件配置类
*
* @author Saint
*/
@Configuration
public class MyConditionalConfiguration {
/**
* 当存在key为language,value为Chinese的property属性时,加载当前方法
*
* @return
*/
@ConditionalOnSystemProperty(name = "language", value = "Chinese")
@Bean("message")
public String chinese() {
return "你好,世界!";
}
/**
* 当存在key为language,value为English的property属性时,加载当前方法
*
* @return
*/
@ConditionalOnSystemProperty(name = "language", value = "English")
@Bean("message")
public String english() {
return "Hello, World";
}
}
4> 在启动类中测试
package com.saint;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class SaintSpringbootApplication {
public static void main(String[] args) {
// 设置language属性值,也可以通过--language=English在Program arguments中设置
System.setProperty("language", "English");
ConfigurableApplicationContext context = SpringApplication.run(SaintSpringbootApplication.class, args);
// 从Spring上下文中获取beanName为message的类
String message = context.getBean("message", String.class);
System.out.println("当前message类型为:" + message);
}
}
运行结果输出如下:
从结果可以看出,@ConditionalOnSystemProperty注解条件装配成功生效。