SpringBoot - 自定義Condition

在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接口

四、使用

  1. HelloService接口

    package com.example.demo.selfcondition;
    
    public interface HelloService {
        String hello();
    }
    
    
  2. AnimalHelloService

    @Component
    @ConditionOnHavingValue(name = "hello.animals", havingValue = {"dog", "cat"})
    public class AnimalHelloService implements HelloService {
        @Override
        public String hello() {
            return "hello, we are animals";
        }
    }
    
  3. 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";
        }
    }
    
    
  4. 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";
        }
    }
    
  5. 注入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/>"));
        }
    }
    
    
  6. 配置文件
    hello.animals=cat,dog

  7. 請求結果

    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表達式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章