Spring

月伴飞鱼 2024-12-28 19:00:47
实战相关
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!

SpEL

SpELSpring 表达式语言,可以在运行时评估表达式并生成值。

应用场景

动态参数配置:

可以通过 SpEL 将应用程序中的各种参数配置化。

  • 例如配置文件中的数据库连接信息、业务规则等。

通过动态配置,可以在运行时根据不同的环境或需求来进行灵活的参数设置。

运行时注入:

使用SpEL,可以在运行时动态注入属性值,而不需要在编码时硬编码。

条件判断与业务逻辑:

SpEL支持复杂的条件判断和逻辑计算,可以方便地在运行时根据条件来执行特定的代码逻辑。

表达式模板化:

SpEL支持在表达式中使用模板语法,允许将一些常用的表达式作为模板。

  • 然后在运行时通过填充不同的值来生成最终的表达式。

系统场景

Excel 解析:

SpEL 可以用于解析 Excel 表格中的数据。

可以使用 SpEL 表达式来指定需要解析的单元格、行、列等等,提取数据并应用相应的逻辑。

  • 这使得解析过程更加灵活和可扩展。

规则引擎:

在使用规则引擎时,SpEL 可以用于定义规则条件和执行动作。

通过 SpEL 表达式,可以动态地根据特定的条件对数据进行处理和决策。

模板引擎:

SpEL 可以用于填充模板数据。

通过 SpEL 表达式,可以在模板中引用对象的属性、方法或函数。

配置文件解析:

SpEL 可以用于解析配置文件中的动态值。

通过 SpEL 表达式,可以在配置文件中引用其他属性或方法的值。

验证规则:

在数据验证的场景中,SpEL 可以用于定义验证规则。

通过 SpEL 表达式,可以对数据进行复杂的验证和处理。

简单举例

/**
 * 验证数字是否大于10
 */
public String spELSample(int number) {
    // 创建ExpressionParser对象,用于解析SpEL表达式
    ExpressionParser parser = new SpelExpressionParser();
    String expressionStr = "#number > 10 ? 'true' : 'false'";
    Expression expression = parser.parseExpression(expressionStr);

    // 创建EvaluationContext对象,用于设置参数值
    StandardEvaluationContext context = new StandardEvaluationContext();
    context.setVariable("number", number);

    // 求解表达式,获取结果
    return expression.getValue(context, String.class);
}

实现动态参数处理策略

举例:每新增一个渠道接入时不需要进行代码开发,只需在配置表中维护关联关系。

根据标识匹配对应策略标识,根据策略标识找到具体参数处理策略表达式。

// 定义解析工具类
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);
        }
    }
}
// 定义解析类
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);
        // t_channel_api表配置的api映射表达式
        for (ChannelApiEntity api : channel.getApis()) {
            // 通过反射获取FileBillReqDTO类属性名对象
            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);
            // 将结果通过反射赋值给FileBillReqDTO对象中指定属性字段
            field.setAccessible(true);
            field.set(target, value);
        }
        // 返回解析赋值后的完整对象
        return target;
    }
}
// 调用类
public class ChannelApplyFileClient {
    @Resource
    private CNRegionDataFetcher cnRegionDataFetcher;
    @Resource
    private ExpressionService expressionService;
    @Resource
    private ChannelRepository channelRepository;

    public String applyFileBill(String instCode, String instAccountNo) {
        // 根据渠道码查询t_channel、t_channel_api表,返回ChannelEntity对象
        ChannelEntity channel = channelRepository.findByInstCode(instCode);
        // 通过SpEL解析t_channel_api表中表达式,并将值赋值给对应属性中,返回完整请求对象
        FileBillReqDTO channelReq = expressionService.transform(channel, instAccountNo);
        // 请求支付系统拉取账单文件,同步返回处理中,异步MQ通知下载结果
        BaseResult<FileBillResDTO> result = cnRegionDataFetcher.applyFileBill(channelReq, "资金账单下载");
        return "处理中";
    }
}

Excel解析

举例:从不同的渠道下载账单,并对账单进行解析,解析后的数据落入流水表。

注意不同渠道的账单的头字段和格式存在差异。

传统的方式中,解析 Excel 通常需要通过创建实体类来映射 Excel 的结构和数据。

每个实体类代表一个 Excel 行或列,需要手动编写代码来将 Excel 数据解析为相应的实体对象。

以下是使用 SpEL 方式动态解析 Excel 的一般步骤:

  • 使用 Apache POI 等工具读取 Excel 数据表。
  • 根据配置表,将 Excel 中的列与 SpEL 表达式进行关联。
  • 使用 SpEL 解析器,在运行时解析这些 SpEL 表达式。
  • 将解析后的结果做数据清洗后落表,应用于现金流打标业务。
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!