前言
上一篇文章介紹了SpringBoot的Endpoint食绿,這里再介紹下@Conditional。
SpringBoot的AutoConfig內(nèi)部大量使用了@Conditional钞速,會(huì)根據(jù)運(yùn)行環(huán)境來動(dòng)態(tài)注入Bean。這里介紹一些@Conditional的使用和原理,并自定義@Conditional來自定義功能叫乌。
Conditional
@Conditional是SpringFramework的功能踩衩,SpringBoot在它的基礎(chǔ)上定義了@ConditionalOnClass嚼鹉,@ConditionalOnProperty的一系列的注解來實(shí)現(xiàn)更豐富的內(nèi)容。
觀察@ConditionalOnClass會(huì)發(fā)現(xiàn)它注解了@Conditional(OnClassCondition.class)驱富。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}
OnClassCondition則繼承了SpringBootCondition锚赤,實(shí)現(xiàn)了Condition接口。
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
查看SpringFramework的源碼會(huì)發(fā)現(xiàn)加載使用這些注解的入口在ConfigurationClassPostProcessor中褐鸥,這個(gè)實(shí)現(xiàn)了BeanFactoryPostProcessor接口线脚,前面介紹過,會(huì)嵌入到Spring的加載過程叫榕。
這個(gè)類主要是從ApplicationContext中取出Configuration注解的類并解析其中的注解浑侥,包括 @Conditional,@Import和 @Bean等晰绎。
解析 @Conditional 邏輯在ConfigurationClassParser類中寓落,這里面用到了 ConditionEvaluator 這個(gè)類。
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
......
}
ConditionEvaluator中的shouldSkip方法則使用了 @Conditional中設(shè)置的Condition類寒匙。
public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
List<Condition> conditions = new ArrayList<Condition>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if (requiredPhase == null || requiredPhase == phase) {
if (!condition.matches(this.context, metadata)) {
return true;
}
}
}
return false;
}
private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(Conditional.class.getName(), true);
Object values = (attributes != null ? attributes.get("value") : null);
return (List<String[]>) (values != null ? values : Collections.emptyList());
}
自定義Conditional
所以自定義Conditional就是通過自定義注解和Condition的實(shí)現(xiàn)類零如。完整的代碼在Github上了
- 定義@ConditionalOnMyProperties
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnMyPropertiesCondition.class)
public @interface ConditionalOnMyProperties {
String name();
}
- 定義OnMyPropertiesCondition躏将,這里繼承了SpringBootCondition重用了部分功能,然后再getMatchOutcome實(shí)現(xiàn)了自定義的功能考蕾。
public class OnMyPropertiesCondition extends SpringBootCondition {
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Object propertiesName = metadata.getAnnotationAttributes(ConditionalOnMyProperties.class.getName()).get("name");
if (propertiesName != null) {
String value = context.getEnvironment().getProperty(propertiesName.toString());
if (value != null) {
return new ConditionOutcome(true, "get properties");
}
}
return new ConditionOutcome(false, "none get properties");
}
}
- ConditionalOnMyProperties使用類祸憋,還要加上Configuration注解才能生效。
@Configuration
@ConditionalOnMyProperties(name = "message")
public static class ConditionClass {
@Bean
public HelloWorld helloWorld() {
return new HelloWorld();
}
}
private static class HelloWorld {
public void print() {
System.out.println("hello world");
}
}
- 入口類肖卧,這里運(yùn)行兩次SpringApplication蚯窥,傳入的參數(shù)不同,第一次取Bean會(huì)拋出Bean不存在的異常塞帐,第二次就會(huì)正常輸出拦赠。
@Configuration
@EnableAutoConfiguration
public class CustomizeConditional {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(CustomizeConditional.class);
springApplication.setWebEnvironment(false);
ConfigurableApplicationContext noneMessageConfigurableApplicationContext = springApplication.run("--logging.level.root=ERROR","--endpoints.enabled=false");
try {
noneMessageConfigurableApplicationContext.getBean(HelloWorld.class).print();
} catch (Exception e) {
e.printStackTrace();
}
ConfigurableApplicationContext configurableApplicationContext = springApplication.run("--message=haha", "--logging.level.root=ERROR");
configurableApplicationContext.getBean(HelloWorld.class).print();
}
}
結(jié)語
ConfigurationClassPostProcessor實(shí)現(xiàn)了很多的擴(kuò)展功能,很多額外的注解包括@Bean葵姥,@Import等都是由這個(gè)類解析的荷鼠。