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

六、总结

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

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

相关推荐
paopaokaka_luck4 小时前
基于springboot3+vue3的企业考勤管理系统(部门树递归、Echarts图形化分析)
开发语言·spring boot·学习·echarts·mybatis·需求分析·代码规范
Pluchon5 小时前
Java个人综合项目——萌部落社区V2.0
java·python·spring·spring cloud·postman·idea
梦雨生生6 小时前
java开发工具(学习第一天)
java·开发语言·学习
她说可以呀6 小时前
Spring-ai-alibaba文生图
java·人工智能·spring
松仔log8 小时前
Java中级——组合和继承
android·java·开发语言
不才不才不不才8 小时前
Spring 源码系列(16): doDispatch 全流程——一次请求的主干链路
java·后端·spring
snow@li9 小时前
HikariCP:高性能数据库连接池全景深入梳理
java·数据库
sugar__salt9 小时前
Spring 三层架构与 IoC、依赖注入完全指南
java·spring boot·后端·spring·架构·maven·intellij-idea
edwarddamon9 小时前
Spring Cloud 配置热更新与 Bean 代理机制梳理
java·后端
Raas10010 小时前
MAIGateway,魔芋企业级AI网关的FinAPI成本竞争力设计
java·后端·网关·api网关·魔芋ai·finapi·mai gateway