基于工厂模式优化跨模块服务调用方案咨询(模块C无依赖调用A/B)
问题背景与优化需求
现有模块A、B各自拥有专属服务,模块C与A、B无依赖关系,需通过工厂模式实现模块C对A、B服务的调用。
已在模块C中定义公共接口ThirdPartyCallsStrategy,供A、B模块的服务实现;当前实现的CoreCallsFactory工厂类通过反射创建对应实例,但反射方式会导致异常仅能在运行时暴露,无法在编译期发现问题。现寻求更简洁、可靠的优化实现方案。
相关代码
1. 模块C中定义的公共接口
public interface ThirdPartyCallsStrategy { void apply(String orderId); }
2. 工厂类实现
@Component @AllArgsConstructor public class CoreCallsFactory { private static final String A_PACKAGE = "com.testA.service"; private static final String A_CLASS = "TestClassA"; private static final String B_PACKAGE = "com.testB.service"; private static final String B_CLASS = "TestClassA"; @SneakyThrows public ThirdPartyCallsStrategy createThirdPartyCallsStrategy(String type) { Class<?> clazz; if (type.equals("A")) { clazz = Class.forName(String.format("%s.%s", A_PACKAGE , A_CLASS )); return (ThirdPartyCallsStrategy) clazz.getDeclaredConstructor().newInstance(); } if (type.equals("B")) { clazz = Class.forName(String.format("%s.%s", B_PACKAGE , B_CLASS )); return (ThirdPartyCallsStrategy) clazz.getDeclaredConstructor().newInstance(); } return null; } }
3. 模块C中调用逻辑
public String checkStatusAndRedirect(String orderId, String type) { boolean status = checkStatus(orderId); StringBuilder paymentResultUrl = new StringBuilder(frontUrl + "/public/payment/status?success="); if (status) { coreCallsFactory.createThirdPartyCallsStrategy(type).apply(orderId); paymentResultUrl.append(Boolean.TRUE); } else { paymentResultUrl.append(Boolean.FALSE); } return paymentResultUrl.toString(); }
优化方案
针对反射带来的编译期无法校验问题,推荐以下两种可靠实现方式:
方案一:Spring容器自动装配+枚举映射
步骤1:定义枚举统一服务类型
public enum ServiceType { A, B }
步骤2:A、B模块实现类注册Spring Bean
模块A实现类:
@Component("serviceA") public class TestClassA implements ThirdPartyCallsStrategy { @Override public void apply(String orderId) { // 模块A专属业务逻辑 } }
模块B实现类:
@Component("serviceB") public class TestClassB implements ThirdPartyCallsStrategy { @Override public void apply(String orderId) { // 模块B专属业务逻辑 } }
步骤3:改造工厂类,通过Spring容器获取实例
@Component public class CoreCallsFactory { private final Map<String, ThirdPartyCallsStrategy> strategyMap; // Spring自动注入所有ThirdPartyCallsStrategy类型的Bean,key为Bean名称 public CoreCallsFactory(Map<String, ThirdPartyCallsStrategy> strategyMap) { this.strategyMap = strategyMap; } public ThirdPartyCallsStrategy createThirdPartyCallsStrategy(ServiceType type) { return switch (type) { case A -> strategyMap.get("serviceA"); case B -> strategyMap.get("serviceB"); default -> throw new IllegalArgumentException("不支持的服务类型: " + type); }; } }
核心优势:
- 编译期即可校验Bean是否存在,彻底避免运行时反射错误
- 无需手动维护类路径,Spring自动管理Bean生命周期
- 枚举替代字符串参数,从源头避免拼写错误
方案二:SPI(服务提供者接口)机制
步骤1:配置SPI文件
在模块C的resources/META-INF/services目录下创建文件com.testC.ThirdPartyCallsStrategy,文件内容为A、B模块实现类的全类名:
com.testA.service.TestClassA com.testB.service.TestClassB
步骤2:改造工厂类,通过ServiceLoader加载实例
@Component public class CoreCallsFactory { private final Map<String, ThirdPartyCallsStrategy> strategyMap; public CoreCallsFactory() { strategyMap = new HashMap<>(); ServiceLoader<ThirdPartyCallsStrategy> loader = ServiceLoader.load(ThirdPartyCallsStrategy.class); for (ThirdPartyCallsStrategy strategy : loader) { // 根据实现类类名映射服务类型,也可自定义注解实现更灵活的映射 String type = strategy.getClass().getSimpleName().contains("A") ? "A" : "B"; strategyMap.put(type, strategy); } } public ThirdPartyCallsStrategy createThirdPartyCallsStrategy(String type) { ThirdPartyCallsStrategy strategy = strategyMap.get(type); if (strategy == null) { throw new IllegalArgumentException("不支持的服务类型: " + type); } return strategy; } }
核心优势:
- 模块C与A、B完全解耦,无需依赖A、B的Jar包即可编译
- SPI文件可在编译期校验实现类的合法性
- 扩展新服务只需添加SPI条目,无需修改工厂类代码
调用逻辑改造建议
将原有的字符串type参数替换为枚举类型,进一步提升代码健壮性:
public String checkStatusAndRedirect(String orderId, ServiceType type) { boolean status = checkStatus(orderId); StringBuilder paymentResultUrl = new StringBuilder(frontUrl + "/public/payment/status?success="); if (status) { ThirdPartyCallsStrategy strategy = coreCallsFactory.createThirdPartyCallsStrategy(type); strategy.apply(orderId); paymentResultUrl.append(Boolean.TRUE); } else { paymentResultUrl.append(Boolean.FALSE); } return paymentResultUrl.toString(); }
内容的提问来源于stack exchange,提问作者Ramzi Mrabet
相关产品推荐
相关产品推荐

