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.

DeWu Technology
DeWu Technology
DeWu Technology
Dynamic Parameter Handling with Spring SpEL and Strategy Pattern

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendjavaStrategy PatternDynamic ConfigurationSpEL
DeWu Technology
Written by

DeWu Technology

A platform for sharing and discussing tech knowledge, guiding you toward the cloud of technology.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.