如何自动装配多个接口实现并使用 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;
...
}
例如,有一个任务,在不违反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;
...
}