在SpringBoot的自動裝配中,有很多的@Condition*註解用於我們按照不同的需求來裝配Bean,這裏我們示例一下如何自定義自己的Condition
其實我們可以參照已經提供的一些註解來實現即可
一、需求
配置文件中某個key對應的值有多個部分組成,之間使用英文逗號分隔(且成爲各個組件),要求當配置文件中配置了某個組件的時候裝配對應的Bean。
如:
hello.animals=cat,dog
當存在cat的時候,裝配CatHelloService;當存在dog的時候,裝配DogService,當同時存在cat,dog的時候,裝配AnimalHelloService。
二、自定義Conditon註解
這個參照一下@ConditionOnProperty註解,定義如下:
package com.example.demo.selfcondition;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnHavingValueCondition.class)
public @interface ConditionOnHavingValue {
/**
* 屬性名
* @return
*/
String name();
/**
* 需要包含的值
* @return
*/
String[] havingValue();
}
可以看到,@Conditional(OnHavingValueCondition.class)裏面指定了一個Class,這個類就是用來進行匹配的。
三、創建匹配規則類
老規矩,匹配規則的編寫,建議找存在的類去參考着寫,我這裏參考了OnPropertyCondition(用來解析@ConditionOnProperty的)
package com.example.demo.selfcondition;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap;
import java.util.*;
public class OnHavingValueCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(ConditionOnHavingValue.class.getName()));
boolean result = false;
PropertyResolver propertyResolver = context.getEnvironment();
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
String name = (String) annotationAttributes.get("name");
String[] havingValues = (String[]) annotationAttributes.get("havingValue");
if (!propertyResolver.containsProperty(name)) {
break;
}
String values = propertyResolver.getProperty(name);
if (values == null || values.trim().length() == 0) {
break;
}
List<String> realValues = Arrays.asList(values.split(","));
result = Arrays.stream(havingValues).allMatch(realValues::contains);
}
if (result)
return new ConditionOutcome(true, "get properties");
return new ConditionOutcome(false, "get properties");
}
private List<AnnotationAttributes> annotationAttributesFromMultiValueMap(
MultiValueMap<String, Object> multiValueMap) {
List<Map<String, Object>> maps = new ArrayList<>();
multiValueMap.forEach((key, value) -> {
for (int i = 0; i < value.size(); i++) {
Map<String, Object> map;
if (i < maps.size()) {
map = maps.get(i);
} else {
map = new HashMap<>();
maps.add(map);
}
map.put(key, value.get(i));
}
});
List<AnnotationAttributes> annotationAttributes = new ArrayList<>(maps.size());
for (Map<String, Object> map : maps) {
annotationAttributes.add(AnnotationAttributes.fromMap(map));
}
return annotationAttributes;
}
}
其實需要注意的就是,這個類應該要繼承SpringBootCondition或者是實現Condition接口
四、使用
-
HelloService接口
package com.example.demo.selfcondition; public interface HelloService { String hello(); }
-
AnimalHelloService
@Component @ConditionOnHavingValue(name = "hello.animals", havingValue = {"dog", "cat"}) public class AnimalHelloService implements HelloService { @Override public String hello() { return "hello, we are animals"; } }
-
DogHelloService
package com.example.demo.selfcondition; import org.springframework.stereotype.Component; @Component @ConditionOnHavingValue(name = "hello.animals", havingValue = "dog") public class DogHelloService implements HelloService { @Override public String hello() { return "hello, I am a dog"; } }
-
CatHelloService
package com.example.demo.selfcondition; import org.springframework.stereotype.Component; @Component @ConditionOnHavingValue(name = "hello.animals", havingValue = "cat") public class CatHelloService implements HelloService { @Override public String hello() { return "hello,I am a cat"; } }
-
注入HelloService
package com.example.demo.selfcondition; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.stream.Collectors; @RestController public class ConditionController { @Autowired List<HelloService> helloServiceList; @GetMapping("/hello") public String hello() { return helloServiceList.stream().map(HelloService::hello).collect(Collectors.joining("<br/>")); } }
-
配置文件
hello.animals=cat,dog
-
請求結果
hello, we are animals hello,I am a cat hello, I am a dog
五、其他
其實,上面我寫的這個註解,等價於下面這種方式
@ConditionalOnExpression("#{T(java.util.Arrays).asList('${hello.animals}').containsAll(T(java.util.Arrays).asList('cat','dog'))}")
@ConditionalOnExpression是SpringBoot提供的基於SpringEL表達式進行裝配的註解,然而,寫EL表達式比較蛋疼,所以我這裏寫了這麼一個註解.
如果對SpringEL表達式比較感興趣的話,可以參看官網地址:
SpringEL表達式