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<FileBillResDTO> 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<IChannelApplyFileStrategy> 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<FileBillResDTO> 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<FileBillResDTO> 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
