如何自动装配多个接口实现并使用 Map 和 Enum 作为键

How to Autowired multiple interface implementations and use Map and Enum as a key

例如,有一个任务,在不违反Open/Closed原则的情况下,安全地添加以不同方式发送消息的新实现。输入带有一个参数,其中包含用于发送消息的“传输”类型或消息将到达的设备。

作为输入参数,需要使用Enum,但系统设计为当参数为字符串时不使用switch,而是立即按照规定的条件调用需要的服务。

这是我的实现。

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class NotificationMasterTest extends MultiImplementationInterfacesAndEnumApplicationTests {

    @Autowired
    private NotificationMaster notification;

    @Test
    void send() {

        String phone = "phone";

        TypeCommunication typeCommunication =
                TypeCommunication.valueOf("phone");//Runtime Exception

        notification.send(TypeCommunication.PHONE);
    }
}
public enum TypeCommunication {

    PHONE("phone"),
    EMAIL("email"),
    KAFKA("kafka");

    private String value;

    TypeCommunication(String value) {
        this.value = value;
    }


    public String getType() {
        return this.value;
    }
}
public interface Notification {

    void sendNotice();

    TypeCommunication getType();
}
@Service
public class NotificationPhoneImpl implements Notification {

    private final TypeCommunication typeCommunication = TypeCommunication.PHONE;

    public NotificationPhoneImpl() {
    }

    @Override
    public void sendNotice() {
        System.out.println("Send through --- phone---");
    }

    @Override
    public TypeCommunication getType() {
        return this.typeCommunication;
    }
}
@Service
public class NotificationEmailImpl implements Notification {

    private final TypeCommunication typeCommunication = TypeCommunication.EMAIL;

    @Override
    public void sendNotice() {
        System.out.println("Send through --- email ---");
    }

    @Override
    public TypeCommunication getType() {
        return this.typeCommunication;
    }
}
package com.example.multi.implementation.interfaces.services;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Component
public class NotificationMaster {

    private  Map<TypeCommunication, Notification> notificationService = new HashMap<>();

    private Set<Notification> notification;

    public NotificationMaster(Set<Notification> notification) {
        this.notification = notification;
    }

    @PostConstruct
    private void init(){
        notification.stream()
                .forEach(service -> {
                    TypeCommunication type = service.getType();
                    notificationService.put(type, service);
                });
    }

    public void send(TypeCommunication typeCommunication) {
        Notification notification = notificationService.get(typeCommunication);
        notification.sendNotice();
    }
}

我不知道如何传递字符串并将其动态转换为枚举(不使用开关)并立即获得所需的实现。

也许有一个更灵活的解决方案,其中 Spring 在没有我的情况下已经准备好组件,这样我就不会使用 Postconstruct 并手动创建具有不同服务实现的地图?

我会说它不能“即时”完成。因此,我将在 TypeCommunication 中创建静态 Map 和静态方法 getByType 以按其值检索枚举:

public enum TypeCommunication {

    PHONE("phone"),
    EMAIL("email"),
    KAFKA("kafka");

    private static final Map<String, TypeCommunication> TYPE_COMMUNICATION_MAP;

    private final String value;

    static {
        TYPE_COMMUNICATION_MAP = Arrays.stream(TypeCommunication.values())
                .collect(Collectors.toMap(TypeCommunication::getType, Function.identity(),
                        (existing, replacement) -> existing));
    }

    TypeCommunication(String value) {
        this.value = value;
    }

    public String getType() {
        return this.value;
    }

    public static TypeCommunication getByType(String type) {
        return TYPE_COMMUNICATION_MAP.get(type);
    }
}

如果您在请求正文的控制器中收到此值,则可以在 getType() 方法上使用 @JsonValue 注释以立即 deserialize 它在枚举中的值.

关于您的 NotificationMaster,spring 无法自动收集此类地图,但您可以在构造函数中直接收集您的地图,而无需使用 @PostConstruct,并使用 Collectors.toMap() 方法, 也取 EnumMap, 因为你的密钥是 Enum:

@Component
public class NotificationMaster {

    private final Map<TypeCommunication, Notification> map;

    public NotificationMaster(List<Notification> notifications) { // or Set
        map = notifications.stream()
                .collect(Collectors.toMap(Notification::getType, Function.identity(),
                        (existing, replacement) -> existing,
                        () -> new EnumMap<>(TypeCommunication.class)));
    }


    public void send(TypeCommunication typeCommunication) {
        map.getOrDefault(typeCommunication, new DefaultNotificationImpl()).sendNotice();
    }

    private static class DefaultNotificationImpl implements Notification {
        @Override
        public void sendNotice() {
            // some logic, e.g. throw new UnsupportedOperationException
        }

        @Override
        public TypeCommunication getType() {
            return null;
        }
    }
}

最好在 send 方法中定义 DefaultNotificationImpl 以避免 NullPointerException 并使用现在的 Map#getOrDefault 方法而不是 Map#get

您还可以在单​​独的配置中取出创建地图的逻辑 class,然后在 NotificationMaster 中简单地自动装配此地图:

@Configuration
public class Config {

    @Bean
    public Map<TypeCommunication, Notification> createMap(List<Notification> notifications) {
        return notifications.stream()
                .collect(Collectors.toMap(Notification::getType, Function.identity(),
                        (existing, replacement) -> existing,
                        () -> new EnumMap<>(TypeCommunication.class)));
    }
}

@Component
public class NotificationMaster {

    @Autowired
    private Map<TypeCommunication, Notification> map;

    ...
}