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. 全局生效:拦截器配置后,所有标记注解的字段都会自动格式化,实现统一管控。

六、总结

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

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

相关推荐
孟陬2 分钟前
国外技术周刊 #1:Paul Graham 重新分享最受欢迎的文章《创作者的品味》、本周被划线最多 YouTube《如何在 19 分钟内学会 AI》、为何我不
java·前端·后端
想用offer打牌5 分钟前
一站式了解四种限流算法
java·后端·go
华仔啊35 分钟前
Java 开发千万别给布尔变量加 is 前缀!很容易背锅
java
也些宝1 小时前
Java单例模式:饿汉、懒汉、DCL三种实现及最佳实践
java
Nyarlathotep01132 小时前
SpringBoot Starter的用法以及原理
java·spring boot
wuwen52 小时前
WebFlux + Lettuce Reactive 中 SkyWalking 链路上下文丢失的修复实践
java
SimonKing2 小时前
GitHub 10万星的OpenCode,正在悄悄改变我们的工作流
java·后端·程序员
Seven973 小时前
虚拟线程深度解析:轻量并发编程的未来趋势
java
雨中飘荡的记忆13 小时前
ElasticJob分布式调度从入门到实战
java·后端