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

六、总结

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

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

相关推荐
降临-max6 小时前
IDEA常用git操作
java·github·intellij-idea
爱丽_6 小时前
G1 深入:Region、Remembered Set、三色标记与“可预测停顿”
java·数据库·算法
她说彩礼65万6 小时前
C语言 Static的用法
java·linux·c语言
spencer_tseng6 小时前
java.lang.ClassNotFoundException: org.slf4j.Logger
java·spring·maven
小江的记录本6 小时前
【VO、DTO、Entity】VO、DTO、Entity三大核心数据对象全解析(附核心对比表 + 代码示例)
java·数据库·spring boot·spring·架构·mybatis·数据库架构
无籽西瓜a6 小时前
TCP三次握手与四次挥手详解含图解
java·服务器·网络·tcp/ip
晨陌y6 小时前
Maven完整配置教程:从零基础到实战,新手零踩坑
java·maven
SuniaWang7 小时前
《Spring AI + 大模型全栈实战》学习手册系列·专题一:《RAG技术全景解析:从原理到架构设计》
java·javascript·人工智能·spring boot·后端·spring·架构
java1234_小锋7 小时前
Java高频面试题:Spring是如何解决Bean的循环依赖?
java·开发语言·spring
历程里程碑7 小时前
43. TCP -2实现英文查中文功能
java·linux·开发语言·c++·udp·c#·排序算法