在Spring Boot框架中,可以看到不少@ConditionalOnxxx注解,如@ConditionalOnBean、@ConditionalOnClass、@ConditionalOnProperty、@ConditionalOnMissingBean等注解,@Conditional条件装配于@Profile注解类似,但@Conditional条件装配更关注运行时的动态选择注册Spring Bean,而@profile则偏向于“静态激活和配置”Spring Bean,不过在Spring4.0开始,@Profile的实现方式也是基于实现Condition来实现的,文末会提及。
- 观察@ConditionalOnClass、@ConditionalOnBean注解不难发现,均被注解@Conditional修饰,@Conditional注解属性值是OnClassCondition、OnBeanCondition类,其实现Condition类的matches方法。故手写一个Spring Boot@ConditionalOnxxx条件装配,也可以参考这种格式,即定义条件装配注解,用@Conditional修饰,写一个实现Condition类实现其matches匹配方法即可,条件装配的逻辑即写在matches方法里面。
- ConditionalOnClass注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}
- ConditionalOnBean注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
Class<?>[] value() default {};
String[] type() default {};
Class<? extends Annotation>[] annotation() default {};
String[] name() default {};
SearchStrategy search() default SearchStrategy.ALL;
Class<?>[] parameterizedContainer() default {};
}
- 条件装配核心接口Condition接口,接口matches方法即为匹配的意思,返回true的话说明该条件成立,反之不成立。matches方法两个入参,第一个为ConditionContext包含Spring应用上下文相关,第二个参数是AnnotatedTypeMetadata,AnnotationMetadata为其子接口,可以获取了注解相关信息。
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
-
本文代码项目目录结构如图所示,在spring-boot项目的spring-boot-conditional模块:
- 代码总共有4个类和一个yaml配置文件,通过配置文件language的值为Chinese或者English值来条件化装配注册不同的language bean。
- @ConditionalOnSystemProperty注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionalOnSystemProperty {
/**
* 语言选择
*/
String language();
}
- OnSystemPropertyCondition类,实现Condition接口的matches方法,逻辑就是根据yaml文件设定的language,返回true或者false来判断@ConditionalOnSystemProperty注解修饰的方法条件成不成立,进而条件装配匹配要注册的Spring Bean。
public class OnSystemPropertyCondition implements Condition {
@Value("${language}")
private String language;
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取ConditionalOnSystemProperty所有的属性方法值
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());
assert annotationAttributes != null;
//获取ConditionalOnSystemProperty#language()方法值
String annotationLanguage = (String) annotationAttributes.get("language");
//配置文件动态选择bean
if (language.equals(annotationLanguage)) {
return true;
}
return false;
}
}
- ConditionalMessageConfiguration配置类
@Configuration
@Slf4j
@ConditionalOnBean
public class ConditionalMessageConfiguration {
@ConditionalOnSystemProperty(language = "Chinese")
@Bean("language")
public String chineseMessage() {
log.info("初始化language bean,使用中文:你好,世界");
return "你好,世界";
}
@ConditionalOnSystemProperty(language = "English")
@Bean("language")
public String englishMessage() {
log.info("初始化language bean,使用英文:hello,world");
return "hello,world";
}
}
- application.yaml文件
server:
port: 8085
language:
Chinese
-
最后启动项目,观察结果
- 前面提到,Spring4.0开始@Profile的实现方式也是基于实现Condition的方式来实现的,可看@Profile注解
- @Profile注解,被@Conditional注解修饰,实现类是ProfileCondition类。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
- ProfileCondition类,即实现Condition接口的matches方法,通过AnnotatedTypeMetadata获取注解@Profile的方法属性value,然后遍历value数组的值,通过匹配系统启动时环境参数来判断是否匹配true或者false,条件成立则注册相关Spring Bean。
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}