Dynamic Parameter Handling with Spring SpEL and Strategy Pattern
The article demonstrates replacing fragile if‑else channel logic with a Strategy pattern and Spring Expression Language, storing parameter mappings in a database so that new payment channels or Excel formats can be added simply by configuring SpEL expressions, achieving a flexible, maintainable, data‑driven solution.
This article presents a design for a funds management platform that needs to download and process channel‑specific billing files. Different payment channels require different request parameters, leading to a challenge in designing a flexible and maintainable solution.
Problem Statement : The naive approach uses a series of if‑else statements to assemble channel‑specific request objects, which quickly becomes hard to maintain and violates the Open‑Closed Principle.
Solution 1 – Simple if‑else :
/**
* 资金系统请求支付系统下载渠道账单
* @param instCode 渠道名
* @param instAccountNo 账户
* @return 同步结果
*/
public String applyFileBill(String instCode, String instAccountNo) {
// 不同渠道入参组装
FileBillReqDTO channelReq = new FileBillReqDTO();
if ("支付宝".equals(instCode)) {
channelReq.setBusinessCode("ALIPAY_" + instAccountNo + "_BUSINESS");
channelReq.setPayTool(4);
channelReq.setTransType(50);
} else if ("微信".equals(instCode)) {
channelReq.setBusinessCode("WX_" + instAccountNo);
channelReq.setPayTool(3);
channelReq.setTransType(13);
} else if ("通联".equals(instCode)) {
channelReq.setBusinessCode("TL_" + instAccountNo);
channelReq.setPayTool(5);
channelReq.setTransType(13);
}
// ... 可以继续添加其他渠道的处理逻辑
BaseResult
result = cnRegionDataFetcher.applyFileBill(channelReq, "资金账单下载");
return "处理中";
}Drawbacks: each new channel requires code changes, leading to code bloat and high maintenance cost.
Solution 2 – Strategy Pattern :
// Strategy interface
public interface IChannelApplyFileStrategy {
/** 渠道匹配策略 */
boolean match(String instCode);
/** 入参组装 */
FileBillReqDTO assembleReqData(String instAccountNo);
}
// Alipay implementation
@Component
public class AlipayChannelApplyFileStrategy implements IChannelApplyFileStrategy {
@Override
public boolean match(String instCode) { return "支付宝".equals(instCode); }
@Override
public FileBillReqDTO assembleReqData(String instAccountNo) {
FileBillReqDTO channelReq = new FileBillReqDTO();
channelReq.setBusinessCode("ALIPAY_" + instAccountNo + "_BUSINESS");
channelReq.setPayTool(4);
channelReq.setTransType(50);
return channelReq;
}
}
// Wechat implementation
@Component
public class WechatChannelApplyFileStrategy implements IChannelApplyFileStrategy {
@Override
public boolean match(String instCode) { return "微信".equals(instCode); }
@Override
public FileBillReqDTO assembleReqData(String instAccountNo) {
FileBillReqDTO channelReq = new FileBillReqDTO();
channelReq.setBusinessCode("WX_" + instAccountNo);
channelReq.setPayTool(3);
channelReq.setTransType(13);
return channelReq;
}
}
// Client that selects strategy at runtime
@Component
public class ChannelApplyFileClient {
@Resource
private List
iChannelApplyFileStrategies;
@Resource
private CNRegionDataFetcher cnRegionDataFetcher;
public String applyFileBill(String instCode, String instAccountNo) {
IChannelApplyFileStrategy strategy = iChannelApplyFileStrategies.stream()
.filter(s -> s.match(instCode)).findFirst().orElse(null);
FileBillReqDTO channelReq = strategy.assembleReqData(instAccountNo);
BaseResult
result = cnRegionDataFetcher.applyFileBill(channelReq, "资金账单下载");
return "处理中";
}
}This approach adheres to the Open‑Closed Principle: adding a new channel only requires a new strategy class.
Introducing Spring Expression Language (SpEL)
To further decouple parameter configuration, the article proposes storing channel‑parameter mappings in a database and using SpEL to evaluate expressions at runtime. This enables dynamic, configurable handling without code changes.
Key SpEL concepts covered:
Expression parsing and evaluation
Evaluation context with variables and custom functions
Root object and property access
Simple SpEL example:
public String spELSample(int number) {
// Create parser
ExpressionParser parser = new SpelExpressionParser();
String expressionStr = "#number > 10 ? 'true' : 'false'";
Expression expression = parser.parseExpression(expressionStr);
// Set variable
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("number", number);
// Evaluate
return expression.getValue(context, String.class);
}Dynamic Parameter Service
@Slf4j
@Service
@CacheConfig(cacheNames = CacheNames.EXPRESSION)
public class ExpressionUtil {
private final ExpressionParser expressionParser = new SpelExpressionParser();
public StandardEvaluationContext createContext(String instAccountNo){
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("instAccountNo", instAccountNo);
this.registryFunction(context);
return context;
}
private void registryFunction(StandardEvaluationContext context) {
try {
context.addPropertyAccessor(new MapAccessor());
context.registerFunction("yuanToCent", ExpressionHelper.class.getDeclaredMethod("yuanToCent", String.class));
context.registerFunction("substringBefore", StringUtils.class.getDeclaredMethod("substringBefore", String.class, String.class));
} catch (Exception e) { log.info("SpEL函数注册失败:", e); }
}
@Cacheable(key = "'getExpressionWithCache:'+#cacheKey", unless = "#result == null")
public Expression getExpressionWithCache(String cacheKey, String expressionString) {
try { return expressionParser.parseExpression(expressionString); }
catch (Exception e) { log.error("SpEL表达式解析异常,表达式:[{}]", expressionString, e); throw new BizException(ReturnCode.EXCEPTION.getCode(), String.format("SpEL表达式解析异常:[%s]", expressionString), e); }
}
}
@Service
public class ExpressionService {
@Resource
private ExpressionUtil expressionUtil;
public FileBillReqDTO transform(ChannelEntity channel, String instAccountNo) throws Exception {
StandardEvaluationContext context = expressionUtil.createContext(instAccountNo);
FileBillReqDTO target = ClassHelper.newInstance(FileBillReqDTO.class);
for (ChannelApiEntity api : channel.getApis()) {
Field field = ReflectionUtils.findField(FileBillReqDTO.class, api.getFieldCode());
String expressionString = api.getFieldExpression();
Expression expression = expressionUtil.getExpressionWithCache(api.fieldExpressionKey(), expressionString);
Object value = expression.getValue(context, FileBillReqDTO.class);
field.setAccessible(true);
field.set(target, value);
}
return target;
}
}
@Component
public class ChannelApplyFileClient {
@Resource
private CNRegionDataFetcher cnRegionDataFetcher;
@Resource
private ExpressionService expressionService;
@Resource
private ChannelRepository channelRepository;
public String applyFileBill(String instCode, String instAccountNo) {
ChannelEntity channel = channelRepository.findByInstCode(instCode);
FileBillReqDTO channelReq = expressionService.transform(channel, instAccountNo);
BaseResult
result = cnRegionDataFetcher.applyFileBill(channelReq, "资金账单下载");
return "处理中";
}
}Advantages: the parameter handling logic becomes data‑driven; adding a new channel only requires inserting a row in the configuration table with the appropriate SpEL expression.
Extension – Excel Parsing
The same SpEL‑driven approach can be applied to parse heterogeneous Excel files from different channels. By mapping Excel columns to SpEL expressions stored in a table, the system can dynamically extract and transform data without writing channel‑specific parsers.
Conclusion
Using SpEL together with the Strategy pattern provides a powerful, flexible, and maintainable way to handle dynamic channel parameters in a backend Java system. It reduces code coupling, improves extensibility, and aligns with domain‑driven design principles.
DeWu Technology
A platform for sharing and discussing tech knowledge, guiding you toward the cloud of technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.