当前位置: 首页 > news >正文

Spring Boot 全局字段处理最佳实践

图片

在日常开发中,我们总会遇到一些琐碎但又无处不在的字段处理需求:

  • • 请求处理: 用户提交的表单,字符串前后带了多余的空格,需要手动 trim()

  • • 响应处理: 返回给前端的 BigDecimal 金额,因为精度问题导致JS处理出错,需要格式化为两位小数的字符串。

  • • 空值处理: 某个VO的 List 字段是 null,序列化成 JSON 后,前端期望得到一个空数组 [] 而不是 null,以避免空指针判断。

如果在每个 DTO 的 setter/getter 或 Service 逻辑中手动处理这些,代码会变得非常臃肿和重复。本文将带你从 0 到 1,构建一个全局的、自动化的字段处理 Starter,通过简单的配置,一次性解决所有这些恼人的“小问题”。

1. 项目设计与核心思路

我们的 global-field-handler-starter 目标如下:

  1. 1. 请求参数自动 Trim: 自动去除所有传入 JSON 请求体中 String 类型字段的前后空格。

  2. 2. BigDecimal 响应自动格式化: 自动将所有传出 JSON 响应体中的 BigDecimal 类型格式化为指定小数位数的字符串。

  3. 3. Null 集合/数组自动转换: 自动将 null 的集合或数组在 JSON 响应中转换为空数组 []

  4. 4. 可配置: 以上所有功能都应提供独立的开关进行控制。

核心实现机制:自定义 Jackson ObjectMapper
Spring Boot 使用 Jackson 作为默认的 JSON 处理器。Jackson 提供了极其丰富的定制化能力。我们将通过创建一个 Jackson2ObjectMapperBuilderCustomizer Bean,来向 Spring Boot 自动配置的 ObjectMapper 中“注入”我们自定义的处理逻辑。

  • • 对于反序列化(请求),我们将注册一个自定义的 JsonDeserializer

  • • 对于序列化(响应),我们将注册几个自定义的 JsonSerializer

2. 创建 Starter 项目与核心组件

我们采用 autoconfigure + starter 的双模块结构。

步骤 2.1: 依赖 (autoconfigure 模块)
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies>
步骤 2.2: 实现自定义的序列化/反序列化器

TrimStringDeserializer.java (字符串 Trim 反序列化器):

package com.example.fieldhandler.autoconfigure.handler;import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;public class TrimStringDeserializer extends JsonDeserializer<String> {@Overridepublic String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {String value = p.getValueAsString();if (value == null) {return null;}return value.trim();}
}

BigDecimalSerializer.java (BigDecimal 格式化序列化器):

package com.example.fieldhandler.autoconfigure.handler;import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;public class BigDecimalSerializer extends JsonSerializer<BigDecimal> {private final int scale; // 小数位数public BigDecimalSerializer(int scale) {this.scale = scale;}@Overridepublic void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers) throws IOException {if (Objects.nonNull(value)) {// 格式化为指定小数位数的字符串gen.writeString(value.setScale(scale, RoundingMode.HALF_UP).toString());} else {gen.writeNull();}}
}

3. 自动装配的魔法 (GlobalFieldHandlerAutoConfiguration)

步骤 3.1: 配置属性类
package com.example.fieldhandler.autoconfigure;import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties(prefix = "global.field-handler")
public class GlobalFieldHandlerProperties {private boolean enabled = false;private boolean trimStringInput = false;private boolean serializeNullCollectionsAsEmpty = false;private boolean serializeNullStringsAsEmpty = false;private int bigDecimalScale = 2; // 默认保留两位小数// Getters and Setters...
}
步骤 3.2: 自动配置主类

这是整个 Starter 的核心,它负责根据配置,将我们的处理器应用到 ObjectMapper

package com.example.fieldhandler.autoconfigure;import com.example.fieldhandler.autoconfigure.handler.BigDecimalSerializer;
import com.example.fieldhandler.autoconfigure.handler.TrimStringDeserializer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Collection;@Configuration
@EnableConfigurationProperties(GlobalFieldHandlerProperties.class)
@ConditionalOnProperty(prefix = "global.field-handler", name = "enabled", havingValue = "true")
public class GlobalFieldHandlerAutoConfiguration {@Beanpublic Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer(GlobalFieldHandlerProperties properties) {return builder -> {// 1. 配置请求参数字符串 Trimif (properties.isTrimStringInput()) {builder.deserializerByType(String.class, new TrimStringDeserializer());}// 2. 配置 BigDecimal 响应格式化builder.serializerByType(BigDecimal.class, new BigDecimalSerializer(properties.getBigDecimalScale()));// 3. 配置 Null 值处理builder.postConfigurer(objectMapper -> {if (properties.isSerializeNullCollectionsAsEmpty()) {objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<>() {@Overridepublic void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {// 对所有类型的 Null 都生效,我们需要筛选// 这里我们简化为对 Collection 的 Null 进行处理// 注意:更精细的控制可能需要更复杂的 JsonSerializer 或 BeanSerializerModifiergen.writeStartArray();gen.writeEndArray();}});}if (properties.isSerializeNullStringsAsEmpty()) {objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<>() {@Overridepublic void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {gen.writeString("");}});}});};}
}
步骤 3.3: 注册自动配置

在 autoconfigure 模块的 resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中添加:

com.example.fieldhandler.autoconfigure.GlobalFieldHandlerAutoConfiguration

4. 如何使用我们的 Starter

步骤 4.1: 引入依赖

<dependency><groupId>com.example</groupId><artifactId>global-field-handler-spring-boot-starter</artifactId><version>1.0.0</version>
</dependency>

步骤 4.2: 在 application.yml 中配置

global:field-handler:enabled: truetrim-string-input: trueserialize-null-collections-as-empty: truebig-decimal-scale: 2

步骤 4.3: 编写 DTO 和 Controller 并验证
DTO:

public class ProductVO {private String name;private BigDecimal price;private List<String> tags;// Getters, Setters...
}

Controller:

@RestController
public class ProductController {// 验证请求参数 Trim@PostMapping("/products")public ProductVO createProduct(@RequestBody ProductVO product) {// 传入的 JSON: {"name": "  My Product  ", ...}// 在这里,product.getName() 的值已经是 "My Product",空格已被自动去除System.out.println("Product name received: '" + product.getName() + "'");return product;}// 验证响应格式化@GetMapping("/products/{id}")public ProductVO getProduct(@PathVariable Long id) {ProductVO product = new ProductVO();product.setName("Awesome Gadget");// 价格精度很高product.setPrice(new BigDecimal("199.998"));// Tags 为 nullproduct.setTags(null);return product;}
}

验证:

  1. 1. POST /products 请求,Body 为 {"name": " Spaceship ", "price": 1000}

    • • 后台日志输出: Product name received: 'Spaceship' (空格已被 trim)。

  2. 2. GET /products/1 请求。

    • • 前端收到的 JSON 响应:
      {"name": "Awesome Gadget","price": "200.00", "tags": [] 
      }
      可以看到,price 被自动格式化为两位小数的字符串,tags 从 null 变成了 []

总结

通过自定义一个 Spring Boot Starter 和深入利用 Jackson 的定制化能力,我们成功地将一系列琐碎、重复但又非常重要的字段处理逻辑,沉淀为了一个可配置、自动化的基础设施。


文章转载自:

http://q2uKxJK3.sprbs.cn
http://HlIL9s74.sprbs.cn
http://Yibh6gwK.sprbs.cn
http://PHYZWBJ7.sprbs.cn
http://pPJ5kC3k.sprbs.cn
http://H17bBQZX.sprbs.cn
http://4pGK3b0N.sprbs.cn
http://V2LAA4FY.sprbs.cn
http://p9zQFTNq.sprbs.cn
http://c3vnSRl9.sprbs.cn
http://tRsakvnC.sprbs.cn
http://yhnfAbBX.sprbs.cn
http://6weYShYr.sprbs.cn
http://8hSWGRaR.sprbs.cn
http://srU4XVfR.sprbs.cn
http://h8ZayaLo.sprbs.cn
http://paGlAYUm.sprbs.cn
http://QANHzouS.sprbs.cn
http://TNlCHdBJ.sprbs.cn
http://YGqdbfMB.sprbs.cn
http://Ns1ziv5x.sprbs.cn
http://EVwG1TDx.sprbs.cn
http://W3jeIwom.sprbs.cn
http://tjPdXUm3.sprbs.cn
http://u9pWXkXS.sprbs.cn
http://yojyczGC.sprbs.cn
http://dOmCeYVI.sprbs.cn
http://R6KuCly5.sprbs.cn
http://7WekCtXZ.sprbs.cn
http://lzxBfcg2.sprbs.cn
http://www.dtcms.com/a/363258.html

相关文章:

  • 【程序员必备的Linux信号处理知识】
  • 【通用视觉框架】基于Python+OpenCV+PyQt5开发的视觉框架软件,全套源码,开箱即用
  • 变频器实习DAY41 单元测试介绍
  • % g++ *.cpp ...: fatal error: ‘opencv2/opencv.hpp‘ file not found 1
  • 趣味学RUST基础篇(错误处理)
  • Delphi 5 操作Word表格选区问题解析
  • 大数据毕业设计选题推荐-基于大数据的电脑硬件数据分析系统-Hadoop-Spark-数据可视化-BigData
  • 水电站电动机绝缘安全 “不掉线”!在线监测方案筑牢发电保障
  • ReactAgent接入MCP服务工具
  • 拷打字节面试官之-吃透c语言-哈希算法 如何在3面拷打字节cto 3万行算法源码带你吃透算法面试所有考题
  • C/C++条件编译:深入理解#ifndef/#endif守卫
  • 20.Linux进程信号(一)
  • C++拷贝语义和移动语义,左值引用与右值引用
  • 汉得H-AI飞码智能编码助手V1.2.4正式发布!
  • Turso数据库:用Rust重构的下一代SQLite——轻量级嵌入式数据库的未来选择
  • 三维重建——基础理论(四):三维重建基础与极几何原理(三维重建基础、单视图回忆、双目视觉、极几何、本质矩阵与基础矩阵、基础矩阵估计)
  • 虚实交互新突破:Three.js融合AR技术的孪生数据操控方法
  • 什么是 AWS 和 GCE ?
  • 解决Mac电脑连接蓝牙鼠标的延迟问题
  • 对于牛客网—语言学习篇—编程初学者入门训练—复合类型:BC140 杨辉三角、BC133 回型矩阵、BC134 蛇形矩阵题目的解析
  • A-Level课程选择与机构报名指南
  • 净利润超10亿元,智能类产品18倍增长!顾家家居2025年半年报业绩:零售增长强劲,整家定制多维突破,全球深化布局!|商派
  • Selenium核心技巧:元素定位与等待策略
  • 苹果内部 AI聊天机器人“Asa”曝光,为零售员工打造专属A
  • 【国内外云计算平台对比:AWS/阿里云/Azure】
  • react用useImages读取图片,方便backgroundImage
  • 硬件开发_基于物联网的自动售卖机系统
  • Spring Boot数据校验validation实战:写少一半代码,还更优雅!
  • arm架构本地部署iotdb集群
  • 物联网开发学习总结(1)—— IOT 设备 OTA 升级方案