背景
我们经常会遇到需要根据不同的条件或业务逻辑来动态选择和调用不同的处理器(Handler)的场景。这种需求在实现功能模块化、业务流程定制化以及代码解耦方面尤为重要。为了应对这种需求,Spring框架提供了多种灵活的实现策略,允许开发者根据不同的业务规则或输入参数来选择和执行相应的处理器逻辑。
本文将探讨几种在Spring框架中实现动态处理器选择的策略,包括基于类名映射、构造器注入、注解驱动、Java反射机制以及利用ApplicationContextAware
接口。每种方法都有其独特的优势和局限性,适用于不同的应用场景。通过对比这些方法,我们可以更好地理解Spring框架的灵活性和强大功能,以及如何在实际开发中根据具体需求选择合适的实现方式。
基于类名映射的处理器管理
Handler 接口
public interface Handler {
void doSomething();
}
实现类
/**
* @Description 实现类
* @User xudehui1
* @Date : 2024/11/2 15:31
*/
@Service
@Slf4j
public class FirstHandler implements Handler {
@Override
public void doSomething() {
log.info("first handler");
}
}
/**
* @Description 实现类
* @User xudehui1
* @Date : 2024/11/2 15:31
*/
@Service
@Slf4j
public class SecondHandler implements Handler {
@Override
public void doSomething() {
log.info("second handler");
}
}
上下文
/**
* @Description 上下文
* @User xudehui1
* @Date : 2024/11/2 15:36
*/
@Component
public class HandlerContext {
// 会注入 Handler 所有的实现类,key 为 bean 名称,value为 Handler 实例
@Autowired
private Map<String, Handler> handlerMap;
// 获取处理器,非静态方法
public Handler getHandler(HandlerEnum handlerEnum) {
if (HandlerEnum.FIRST.equals(handlerEnum)) {
return handlerMap.get("firstHandler");
} else if (HandlerEnum.SECOND.equals(handlerEnum)) {
return handlerMap.get("secondHandler");
} else {
throw new RuntimeException("未匹配到处理器");
}
}
}
测试
/**
* @Description
* @User xudehui1
* @Date : 2024/11/2 15:41
*/
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@Resource
private HandlerContext handlerContext;
@GetMapping("/getHandler")
public void getHandler( @RequestParam Integer code) {
Handler handler = handlerContext.getHandler(HandlerEnum.getHandlerEnum(code));
handler.doSomething();
}
}
缺点
- 随着处理器数量的增加,
getHandler
方法中的if-else
或switch
语句会不断增长,导致代码难以维护。
基于构造器获取
Handler 接口
/**
* @Description 接口
* @User xudehui1
* @Date : 2024/11/2 15:31
*/
public interface HandlerV2 {
HandlerV2Enum getHandlerV2Type();
void doSomething();
}
实现类
/**
* @Description 实现类
* @User xudehui1
* @Date : 2024/11/2 15:31
*/
@Service
@Slf4j
public class FirstHandlerV2 implements HandlerV2 {
@Override
public HandlerV2Enum getHandlerV2Type() {
return HandlerV2Enum.FIRST;
}
@Override
public void doSomething() {
log.info("first handlerV2");
}
}
/**
* @Description 实现类
* @User xudehui1
* @Date : 2024/11/2 15:31
*/
@Service
@Slf4j
public class SecondHandlerV2 implements HandlerV2 {
@Override
public HandlerV2Enum getHandlerV2Type() {
return HandlerV2Enum.SECOND;
}
@Override
public void doSomething() {
log.info("second handlerV2");
}
}
上下文
/**
* @Description 上下文
* @User xudehui1
* @Date : 2024/11/2 15:36
*/
@Component
public class HandlerV2Context {
private static final Map<HandlerV2Enum, HandlerV2> handlerV2Map = new HashMap<>();
// 构造器注入,会注入 HandlerV2 所有的实现类
public HandlerV2Context(List<HandlerV2> handlerV2List) {
handlerV2List.forEach(handlerV2 -> handlerV2Map.put(handlerV2.getHandlerV2Type(), handlerV2));
}
// 获取处理器,非静态方法
public static HandlerV2 getHandlerV2(HandlerV2Enum handlerV2Enum) {
return handlerV2Map.get(handlerV2Enum);
}
}
- 定义成静态的属性是为了初始化时机:静态属性可以在类加载时或应用程序启动时初始化,这为初始化处理器映射提供了一个清晰的时机,确保在应用程序运行期间映射始终可用。
测试
/**
* @Description
* @User xudehui1
* @Date : 2024/11/2 15:41
*/
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/getHandlerV2")
public void getHandlerV2( @RequestParam Integer code) {
HandlerV2 handlerV2 = HandlerV2Context.getHandlerV2(HandlerV2Enum.getHandlerEnum(code));
handlerV2.doSomething();
}
}
优点
- 通过使用枚举和映射,避免了在
getHandler
方法中不断增加的if-else
或switch
语句,使得代码更加简洁和易于维护
基于注解获取
Handler 接口
/**
* @Description 实现类
* @User xudehui1
* @Date: 2024/11/2 15:31
*/
@Service
@Slf4j
@HandlerTypeAnnotation(value = "firstHandlerV3")
public class FirstHandlerV3 implements HandlerV3 {
@Override
public void doSomething() {
log.info("first handlerV3");
}
}
实现类
/**
* @Description 实现类
* @User xudehui1
* @Date: 2024/11/2 15:31
*/
@Service
@Slf4j
@HandlerTypeAnnotation(value = "secondHandlerV3")
public class SecondHandlerV3 implements HandlerV3 {
@Override
public void doSomething() {
log.info("second handlerV3");
}
}
HandlerTypeAnnotation 注解
/**
* @Description
* @User xudehui1
* @Date: 2024/11/2 21:36
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlerTypeAnnotation {
String value() default "";
}
上下文
/**
* @Description 上下文
* @User xudehui1
* @Date: 2024/11/2 15:36
*/
@Component
@Slf4j
public class HandlerV3Context {
private static final Map<String, HandlerV3> handlerV3Map = new HashMap<>();
// 会注入FileHandler所有的实现类
@Resource
private List<HandlerV3> handlerV3List;
@PostConstruct
public void init() {
log.info("执行 init");
handlerV3List.forEach(x -> {
HandlerTypeAnnotation annotation = AnnotationUtils.findAnnotation(x.getClass(), HandlerTypeAnnotation.class);
if (annotation != null) {
handlerV3Map.put(annotation.value(), x);
}
});
}
public static HandlerV3 getHandlerV3(HandlerV3Enum handlerV3Enum) {
return handlerV3Map.get(handlerV3Enum.getCode());
}
}
测试
/**
* @Description
* @User xudehui1
* @Date: 2024/11/2 15:41
*/
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/getHandlerV3")
public void getHandlerV3(@RequestParam String code) {
HandlerV3 handlerV3 = HandlerV3Context.getHandlerV3(HandlerV3Enum.getHandlerEnum(code));
handlerV3.doSomething();
}
}
优点
- 提供了一种灵活的方式来标记和识别不同的处理器实现。
- 可以轻松地添加新的处理器实现,只需添加相应的注解即可。
缺点
- 需要手动维护注解和处理器名称之间的映射关系。
- 增加了代码的复杂性,需要额外的注解和处理逻辑。
基于Java反射
Handler 接口
/**
* @Description 接口
* @User xudehui1
* @Date: 2024/11/2 15:31
*/
public interface HandlerV4 {
void doSomething();
}
实现类
/**
* @Description 实现类
* @User xudehui1
* @Date: 2024/11/2 15:31
*/
@Service
@Slf4j
public class FirstHandlerV4 implements HandlerV4 {
@Override
public void doSomething() {
log.info("first handlerV4");
}
}
/**
* @Description 实现类
* @User xudehui1
* @Date: 2024/11/2 15:31
*/
@Service
@Slf4j
public class SecondHandlerV4 implements HandlerV4 {
@Override
public void doSomething() {
log.info("second handlerV4");
}
}
/**
* @Description 处理器枚举
* @User xudehui1
* @Date: 2024/11/2 15:37
*/
@Getter
@AllArgsConstructor
public enum HandlerV4Enum {
FIRST(10, "firstHandlerV4", "first"),
SECOND(20, "secondHandlerV4", "second"),
;
private final Integer code;
private final String name;
private final String desc;
public static HandlerV4Enum getHandlerEnum(Integer code) {
for (HandlerV4Enum handlerEnum : HandlerV4Enum.values()) {
if (handlerEnum.getCode().equals(code)) {
return handlerEnum;
}
}
return null;
}
}
上下文
/**
* @Description
* @User xudehui1
* @Date: 2024/11/2 21:52
*/
public class HandlerV4Context {
private static final Map<String, Class<?>> handlerClasses = new HashMap<>();
static {
handlerClasses.put("firstHandlerV4", FirstHandlerV4.class);
handlerClasses.put("secondHandlerV4", SecondHandlerV4.class);
// 其他处理器映射
}
public static HandlerV4 getHandler(String handlerName) {
Class<?> handlerClass = handlerClasses.get(handlerName);
if (handlerClass == null) {
throw new IllegalArgumentException("No handler found for name: " + handlerName);
}
try {
return (HandlerV4) handlerClass.getDeclaredConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) {
throw new RuntimeException("Failed to instantiate handler", e);
}
}
}
测试
/**
* @Description
* @User xudehui1
* @Date: 2024/11/2 15:41
*/
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/getHandlerV4")
public void getHandlerV4(@RequestParam Integer code) {
HandlerV4 handlerV4 = HandlerV4Context.getHandler(HandlerV4Enum.getHandlerEnum(code).getName());
handlerV4.doSomething();
}
}
优点
- 不依赖于Spring框架,可以在任何Java环境中使用。
- 提供了一种动态获取实现类实例的方式。
缺点
- 需要手动管理实现类与接口的映射关系。
- 反射可能会带来性能开销。
- 需要处理反射相关的异常。
使用 ApplicationContextAware
Handler 接口
/**
* @Description 接口
* @User xudehui1
* @Date: 2024/11/2 15:31
*/
public interface HandlerV5 {
void doSomething();
}
实现类
/**
* @Description 实现类
* @User xudehui1
* @Date: 2024/11/2 15:31
*/
@Service
@Slf4j
public class FirstHandlerV5 implements HandlerV5 {
@Override
public void doSomething() {
log.info("first handlerV5");
}
}
/**
* @Description 实现类
* @User xudehui1
* @Date: 2024/11/2 15:31
*/
@Service
@Slf4j
public class SecondHandlerV5 implements HandlerV5 {
@Override
public void doSomething() {
log.info("second handlerV5");
}
}
上下文
/**
* @Description 处理器枚举
* @User xudehui1
* @Date: 2024/11/2 15:37
*/
@Getter
@AllArgsConstructor
public enum HandlerV5Enum {
FIRST(10, "firstHandlerV5", "first"),
SECOND(20, "secondHandlerV5", "second"),
;
private final Integer code;
private final String name;
private final String desc;
public static HandlerV5Enum getHandlerEnum(Integer code) {
for (HandlerV5Enum handlerEnum : HandlerV5Enum.values()) {
if (handlerEnum.getCode().equals(code)) {
return handlerEnum;
}
}
return null;
}
}
/**
* @Description
* @User xudehui1
* @Date: 2024/11/2 22:02
*/
@Component
public class HandlerRetriever implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public HandlerV5 getHandler(String beanName) {
return applicationContext.getBean(beanName, HandlerV5.class);
}
}
测试
/**
* @Description
* @User xudehui1
* @Date: 2024/11/2 15:41
*/
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@Resource
private HandlerRetriever handlerRetriever;
@GetMapping("/getHandlerV5")
public void getHandlerV5(@RequestParam Integer code) {
HandlerV5 handlerV5 = handlerRetriever.getHandler(HandlerV5Enum.getHandlerEnum(code).getName());
handlerV5.doSomething();
}
}
总结
每种方法都有其适用场景,选择哪一种取决于具体的业务需求和项目结构。例如,如果项目中处理器数量不多,或者需要快速实现功能,可能会选择基于类名映射的方式。而如果项目需要高度的灵活性和扩展性,可能会选择基于注解或反射的方式。总的来说,这些方法提供了多种选择,以适应不同的设计和需求。