SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案

  • [SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案](#SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案)

SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案

在金融、电商等涉及金额的项目中,我们经常需要对 BigDecimal 类型的金额字段统一保留指定小数位数。如果在每个业务方法中手动调用 setScale 方法,会造成大量代码冗余,且难以统一维护。

本文将介绍一种基于 SpringBoot 拦截器 + 自定义注解 的方案,实现金额字段的自动格式化,让代码更简洁、更易维护。

一、核心思路

  1. 定义一个自定义注解 @AmountFormat,用于标记需要格式化的金额字段,并支持指定小数位数。
  2. 编写一个拦截器 AmountFormatInterceptor,在请求处理阶段,通过反射扫描被注解标记的字段。
  3. 对扫描到的 BigDecimal 类型字段,按照注解指定的小数位数进行格式化处理。
  4. 注册拦截器,配置拦截路径,实现全局生效。

二、实现步骤

1. 创建自定义注解 @AmountFormat

该注解用于标记需要格式化的金额字段,支持通过 scale 属性指定小数位数,默认保留2位小数。

java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 金额格式化注解,用于标记需要统一处理小数位数的字段
 */
@Retention(RetentionPolicy.RUNTIME)  // 注解在运行时生效,允许反射获取
@Target(ElementType.FIELD)           // 注解仅作用于类的字段
public @interface AmountFormat {
    /**
     * 保留小数位数,默认2位
     */
    int scale() default 2;
}

2. 编写金额格式化拦截器

拦截器实现 HandlerInterceptor 接口,在 preHandle 方法中完成核心逻辑:扫描目标类的字段,对被 @AmountFormat 标记的 BigDecimal 字段进行格式化。

java 复制代码
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * 金额格式化拦截器,自动处理被@AmountFormat标记的字段
 */
public class AmountFormatInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 只处理 Controller 中的方法(HandlerMethod 类型)
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 获取目标类的实例
            Object targetBean = handlerMethod.getBean();
            // 获取目标类的所有字段
            Field[] fields = targetBean.getClass().getDeclaredFields();

            for (Field field : fields) {
                // 判断字段是否被@AmountFormat注解标记
                if (field.isAnnotationPresent(AmountFormat.class)) {
                    // 设置私有字段可访问
                    field.setAccessible(true);
                    // 获取字段的值
                    Object fieldValue = field.get(targetBean);
                    // 仅处理BigDecimal类型的字段
                    if (fieldValue instanceof BigDecimal) {
                        AmountFormat annotation = field.getAnnotation(AmountFormat.class);
                        int scale = annotation.scale();
                        // 格式化金额:四舍五入,保留指定小数位数
                        BigDecimal formattedValue = ((BigDecimal) fieldValue).setScale(scale, RoundingMode.HALF_UP);
                        // 将格式化后的值设置回字段
                        field.set(targetBean, formattedValue);
                    }
                }
            }
        }
        // 返回true,继续执行后续拦截器和Controller方法
        return true;
    }
}

关键说明

  • preHandle 方法在 Controller 方法执行前调用,保证格式化逻辑优先执行。
  • 通过反射获取字段值时,需要调用 field.setAccessible(true) 来访问私有字段。
  • 采用 RoundingMode.HALF_UP 模式进行四舍五入,符合日常金额计算的需求。

3. 注册拦截器并配置拦截路径

通过配置类 WebConfig 实现 WebMvcConfigurer 接口,注册拦截器并设置拦截范围。

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Web 配置类,用于注册拦截器
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册金额格式化拦截器
        registry.addInterceptor(new AmountFormatInterceptor())
                // 拦截所有请求
                .addPathPatterns("/**")
                // 排除不需要拦截的路径(可选)
                .excludePathPatterns("/static/**", "/error");
    }
}

4. 在实体类字段上使用注解

在需要格式化的 BigDecimal 类型金额字段上添加 @AmountFormat 注解,即可实现自动格式化。

java 复制代码
import java.math.BigDecimal;

/**
 * 商品实体类
 */
public class Product {
    private Long id;
    private String productName;

    // 金额字段:默认保留2位小数
    @AmountFormat
    private BigDecimal price;

    // 金额字段:自定义保留3位小数
    @AmountFormat(scale = 3)
    private BigDecimal discountPrice;

    // 省略 getter/setter 方法
}

三、拦截器执行时机说明

SpringMVC 拦截器有三个核心方法,各自的执行时机不同:

  1. preHandle:在 Controller 方法执行前调用。本文的格式化逻辑放在此方法,保证业务逻辑处理的是格式化后的金额。
  2. postHandle:在 Controller 方法执行后、视图渲染前调用。适合对处理结果进行二次加工。
  3. afterCompletion:在整个请求结束后调用。适合做资源清理等收尾工作。

四、注意事项

  1. 字段类型限制 :拦截器仅对 BigDecimal 类型的字段生效,其他类型(如 Double)不会处理。建议金额字段统一使用 BigDecimal,避免精度丢失。
  2. 反射权限问题 :必须调用 field.setAccessible(true),否则无法访问实体类的私有字段,会抛出 IllegalAccessException 异常。
  3. 拦截路径配置 :通过 addPathPatternsexcludePathPatterns 精准控制拦截范围,避免拦截静态资源、错误页面等不需要处理的请求。
  4. 四舍五入模式 :本文使用 RoundingMode.HALF_UP,如果业务需要其他舍入模式(如向下取整),可以在注解中新增属性配置。

五、方案优势

  1. 解耦性强 :格式化逻辑与业务逻辑完全分离,无需在业务代码中重复编写 setScale 方法。
  2. 维护成本低 :如果需要调整小数位数,只需修改注解的 scale 参数,无需改动大量业务代码。
  3. 全局生效:拦截器配置后,所有标记注解的字段都会自动格式化,实现统一管控。

六、总结

通过 拦截器 + 自定义注解 的组合,我们可以优雅地解决项目中金额字段格式化的问题。这种方案不仅减少了代码冗余,还提高了代码的可维护性,特别适合金融、电商等对金额精度要求高的项目。

在实际开发中,我们还可以基于此思路扩展更多功能,比如对日期字段的统一格式化、对敏感字段的脱敏处理等。

相关推荐
Coder_Boy_5 小时前
技术让开发更轻松的底层矛盾
java·大数据·数据库·人工智能·深度学习
invicinble6 小时前
对tomcat的提供的功能与底层拓扑结构与实现机制的理解
java·tomcat
较真的菜鸟6 小时前
使用ASM和agent监控属性变化
java
黎雁·泠崖6 小时前
【魔法森林冒险】5/14 Allen类(三):任务进度与状态管理
java·开发语言
qq_12498707537 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_7 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
Mr_sun.7 小时前
Day06——权限认证-项目集成
java
瑶山7 小时前
Spring Cloud微服务搭建四、集成RocketMQ消息队列
java·spring cloud·微服务·rocketmq·dashboard
abluckyboy7 小时前
Java 实现求 n 的 n^n 次方的最后一位数字
java·python·算法