外观
Spring 项目接口爆炸?这个 Starter 一键帮你搞定多策略调度!
在企业级项目中,我们经常遇到这种场景:一个业务接口会有多个子 Service 的实现,在调用的时候,我们需要通过不同的场景参数调用不同的实现,这个时候我们经常会有 if-else 的操作,这种方式不仅不好维护,而且代码的美观度受到极大影响。
今天给大家分享一套代码(ml-base-ml-strategy-boot-starter)用更优雅的方式满足以下场景:
多个子业务共用同一套接口规范,但实际实现逻辑却因业务类型、租户、版本等维度而异,保证代码清晰、扩展性强和性能可控的同时,更简洁的进行调用。
如:
- 多租户项目中,不同租户策略不同(如字段配置)
- 多版本接口共存(如 layoutV1 / layoutV2)
- 多模块服务调用(如 excel/pdf/word)策略组合
- 配置驱动行为切换(动态配置使用哪个实现)
GitHub 地址:https://github.com/MuluoXue/ml-base-ml-strategy-boot-starter
🚀 一、简介
ml-strategy-spring-boot-starter 是一个基于 Spring Boot 的轻量级策略模式扩展组件。它通过自定义注解和自动注册机制,实现多维度(如类型、分组、版本)策略类的灵活管理与动态调用,极大提升了多业务、多版本、多租户项目中接口实现的可扩展性和维护性,同时保证高性能和线程安全。适合企业级项目快速构建可插拔、可扩展的服务架构。
🚀 二、什么是策略模式
策略模式是一种行为型设计模式,它定义了一系列的算法,把它们一个个封装起来,并且使它们可以互相替换。通俗讲,就是"不同情况用不同处理逻辑",通过接口统一管理,不改变原有代码即可添加新的逻辑策略。
🚀 三、设计目标
- ✅ 高扩展性:通过注解标记策略类,按需注册
- ✅ 自动注册:基于 Spring 的 BeanPostProcessor 自动收集
- ✅ 支持多维度匹配:如 type、group、version 等
- ✅ 线程安全:使用 ConcurrentHashMap 保证并发环境安全
- ✅ 轻量高性能:注册逻辑简单,启动性能基本无影响
🚀 四、优点
- ✅ 解耦强:使用接口调用策略类,新增不影响旧逻辑
- ✅ 支持扩展:支持 group / version 多维度扩展策略
- ✅ 自动注册:不需要手动维护 Map 或工厂类
- ✅ 线程安全:并发环境注册与调用均安全
- ✅ 启动快:注册仅遍历 Spring Bean,启动基本无负担
🚀 五、核心代码
1. 策略注解
java
/**
* @author ml
* @date 2025年05月18日 22:31
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Strategy {
/**
* 策略类型标识(必须)
* @return 策略类型标识
*/
String type();
/**
* 策略所属分组(可选)
* 用于区分不同的策略分组,默认值为空字符串
* @return 策略所属分组
*/
String group() default "";
/**
* 策略版本(可选)
* 用于区分不同的策略版本,默认值为空字符串
* @return 策略版本
*/
String version() default "";
}2. 策略注册器
java
/**
* 策略注册器
* @author ml
* @date 2025年05月18日 22:32
*/
@Component
public class StrategyContainer {
// 多级Map:接口类型 -> 策略Key -> 策略实例
private final ConcurrentHashMap<Class<?>, ConcurrentHashMap<String, Object>> strategyMap = new ConcurrentHashMap<>();
public void register(Class<?> strategyType, String key, Object bean) {
strategyMap
.computeIfAbsent(strategyType, k -> new ConcurrentHashMap<>())
.putIfAbsent(key, bean);
}
public <T> T getStrategy(Class<T> strategyType, String key) {
ConcurrentHashMap<String, Object> map = strategyMap.get(strategyType);
if (map == null || !map.containsKey(key)) {
throw new IllegalArgumentException("No strategy found for type: " + strategyType.getSimpleName() + ", key: " + key);
}
return strategyType.cast(map.get(key));
}
public String buildKey(String type, String group, String version) {
return String.format("%s:%s:%s", type, group, version);
}
}3. 自动注册策略
java
/**
* @author ml
* @date 2025年05月18日 22:40
*/
@Component
public class StrategyBeanPostProcessor implements BeanPostProcessor {
private final StrategyContainer container;
private final StrategyProperties properties;
public StrategyBeanPostProcessor(StrategyContainer container, StrategyProperties properties) {
this.container = container;
this.properties = properties;
}
@PostConstruct
public void init() {
System.out.println(">>>> requireDirectInterface = " + properties.isRequireDirectInterface());
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Strategy strategy = AnnotationUtils.findAnnotation(bean.getClass(), Strategy.class);
if (strategy != null) {
Class<?> clazz = bean.getClass();
List<Class<?>> interfaces = null;
if (properties.isRequireDirectInterface()) {
interfaces = getAnyInterface(clazz);
if (interfaces == null || interfaces.isEmpty()) {
throw new IllegalArgumentException("策略类 [" + clazz.getName() + "] 必须实现接口(可继承父类),否则不符合注册条件");
}
} else {
// 必须直接实现接口
interfaces = getDirectInterface(clazz);
if (interfaces == null || interfaces.isEmpty()) {
throw new IllegalArgumentException("策略类 [" + clazz.getName() + "] 必须**直接实现接口**,或配置 ml.require-direct-interface=true");
}
}
Class<?> strategyInterface = interfaces.get(0);
if (strategyInterface == null) {
throw new IllegalStateException("未能解析出注册接口类型,请确认策略类是否实现了接口");
}
String key = container.buildKey(strategy.type(), strategy.group(), strategy.version());
container.register(strategyInterface, key, bean);
}
return bean;
}
/**
* 获取实现接口, 排除spring的接口
*
* @param clazz 当前类
* @return 接口列表
*/
private List<Class<?>> getDirectInterface(Class<?> clazz) {
Class<?>[] interfaces = clazz.getInterfaces();
if (interfaces.length == 0) {
return null;
}
return Arrays.stream(interfaces)
.filter(interfaceClass -> !interfaceClass.getName().startsWith("org.springframework"))
.collect(Collectors.toList());
}
/**
* 检查类是否有接口(包括父类)
*
* @param clazz 当前类
* @return true 如果有接口,否则 false
*/
private List<Class<?>> getAnyInterface(Class<?> clazz) {
while (clazz != null && clazz != Object.class) {
List<Class<?>> interfaces = getDirectInterface(clazz);
if (interfaces != null && !interfaces.isEmpty()) {
return interfaces;
}
clazz = clazz.getSuperclass();
}
return null;
}
}🚀 六、使用示例
场景如:假设你有一个导出服务接口 ExportService,不同策略类实现它,如 ExcelExportService、PdfExportService。你希望运行时根据用户传入的格式来选择对应导出策略。
1. 定义接口
java
// 声明导出接口
public interface ExportService {
void export(String data);
}2. 定义 excel 导出实现
java
// excel导出实现接口
@Strategy(type = "excel", group = "export", version = "v1")
public class ExcelExportService implements ExportService {
@Override
public void export(String data) {
System.out.println("导出为 Excel:" + data);
}
}3. 定义 pdf 导出实现
java
// pdf导出实现接口
@Strategy(type = "pdf", group = "export", version = "v1")
public class PdfExportService implements ExportService {
@Override
public void export(String data) {
System.out.println("导出为 PDF:" + data);
}
}4. 调用策略类
java
// 接口调用
@RestController
@RequestMapping("/export")
public class ExportController {
private final StrategyContainer strategyContainer;
public ExportController(StrategyContainer strategyContainer) {
this.strategyContainer = strategyContainer;
}
@GetMapping
public String export(@RequestParam String format, @RequestParam String data) {
try {
// 构建策略 key
String key = strategyContainer.buildKey(format, "export", "v1");
// 获取对应导出策略
ExportService exportService = strategyContainer.getStrategy(ExportService.class, key);
// 调用导出逻辑
exportService.export(data);
return "导出成功(格式:" + format + ")";
} catch (Exception e) {
return "导出失败:" + e.getMessage();
}
}
}🚀 七、总结
ml-strategy-spring-boot-starter 通过注解 + 注册 + 容器调用的方式,把策略模式做到了高内聚、低耦合。非常适合在多业务线、多版本、多租户的企业级系统中灵活应用。
如果你也有类似需求,不妨试试这种方式,让代码更加优雅、清晰、可扩展!