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://www.pswp.cn/pingmian/95655.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/95655.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

三坐标测量机在汽车制造行业中的应用

在汽车制造业中&#xff0c;零部件精度决定着整车性能。从发动机活塞的微米级公差&#xff0c;到车身焊接的毫米级间隙&#xff0c;汽车制造“差之毫厘&#xff0c;谬以千里” &#xff0c;任何细微偏差都可能引发连锁反应&#xff1a;发动机抖动、异响、油耗飙升&#xff0c;车…

机床夹具设计 +选型

机床夹具设计—第2组&#xff08;钻床夹具&#xff09;仿真组装视频_哔哩哔哩_bilibili 夹具-商品搜索-怡合达一站式采购平台 米思米FA标准品电子目录new 可能要吧这些定位块单独用yolo训练一边才能搞识别分析 3长条一短销定位&#xff0c;黄色的用来夹紧 一个面加一短轴一棱…

表格识别技术:通过计算机视觉和OCR,实现非结构化表格向结构化数据的转换,推动数字化转型。

在日常工作和生活中&#xff0c;我们无处不在与表格打交道。从财务报表、发票收据&#xff0c;到科研论文中的数据表、医疗报告&#xff0c;表格以其清晰、结构化的方式&#xff0c;承载着大量关键信息。然而&#xff0c;当这些表格以纸质或图片等非结构化形式存在时&#xff0…

Go基础(②Viper)

Viper 读取配置创建一个配置文件 config.yamlserver:port: 8080timeout: 30 # 超时时间&#xff08;秒&#xff09; database:host: "localhost"user: "root"password: "123456"name: "mydb"然后用 Viper 读取这个配置&#xff0c;代…

kafka Partition(分区)详解

一、什么是 PartitionPartition&#xff08;分区&#xff09; 是 Kafka Topic&#xff08;主题&#xff09; 的最小并行单位。一个 Topic 可以包含多个 Partition&#xff0c;每个 Partition 底层对应一个有序、不可变的消息队列&#xff0c;消息只会顺序追加。Partition 内部消…

中创中间件适配HGDB

文章目录环境文档用途详细信息环境 系统平台&#xff1a;Microsoft Windows (64-bit) 10 版本&#xff1a;5.6.5 文档用途 本文章主要介绍中创中间件简单适配HGDB。 详细信息 一、数据源配置 1.数据库准备 &#xff08;1&#xff09;安装HGDB并创建一个名为myhgdb的数据…

服务器内存和普通计算机内存在技术方面有什么区别?

服务器内存和普通计算机内存在技术上的区别&#xff0c;主要体现在为满足不同工作场景和要求而采用的设计和特性上。下面这个表格汇总了它们的主要技术差异&#xff0c;方便你快速了解&#xff1a; ​技术特性​​服务器内存​​普通计算机内存​​错误校验 (ECC)​​支持ECC(…

哪款AI生成PPT工具对职场新人最友好?操作门槛最低的是哪个?

一句话生成专业PPT&#xff0c;职场新人也能轻松做出高质量演示文稿现代职场节奏快&#xff0c;PPT制作已成为必备技能。然而&#xff0c;职场新人常面临两大挑战&#xff1a;缺乏设计经验&#xff0c;以及需要在有限时间内完成高质量演示。传统PPT制作耗时费力&#xff0c;需梳…

1.注解的力量:Spring Boot如何用注解重构IoC容器

文章目录1.1 IoC容器&#xff1a;Spring的智能管家1.2 注解驱动&#xff1a;给管家下指令1.2.1 SpringBootApplication&#xff1a;总管家的聘书1.2.2 组件注解&#xff1a;员工的身份标识1.2.3 Autowired&#xff1a;依赖注入的三种方式1.2.4 Bean注解&#xff1a;手动招聘特殊…

【算法】92.翻转链表Ⅱ--通俗讲解

一、题目是啥?一句话说清 给你一个链表和两个整数 left 和 right,反转从第 left 个节点到第 right 个节点的子链表,并返回反转后的链表。其他部分保持不变。 示例: 输入:head = [1,2,3,4,5], left = 2, right = 4 输出:[1,4,3,2,5](反转了从第2到第4个节点) 二、解题…

Nature子刊:新发现!深层脑网络中发现强迫症症状的神经生物标志物

强迫症&#xff08;OCD&#xff09;是一种令人困扰的精神疾病&#xff0c;患者常常被强迫思维和强迫行为所困扰。例如&#xff0c;有些人会反复洗手&#xff0c;无法控制自己的清洁冲动&#xff1b;还有些人会不断检查门窗是否关好&#xff0c;即便他们已经确认过无数次。这些行…

Onlyoffice集成与AI交互操作指引(Iframe版)

Onlyoffice集成与AI交互操作指引&#xff08;Iframe版&#xff09; 本文档系统介绍了软件系统集成OnlyOffice实现在线编辑与AI辅助功能的方案。主要内容包括&#xff1a;后端需提供文档配置信息并实现Callback接口以处理文档保存&#xff1b;前端通过Vue集成编辑器&#xff0c…

TypeScript 中 keyof、typeof 和 instanceof

在 TypeScript 开发中&#xff0c;keyof、typeof 和 instanceof 是核心的类型操作符和操作符&#xff0c;专门用于提升类型安全、代码可读性和维护性。1. keyof 操作符定义和用途&#xff1a;keyof 是一个类型操作符&#xff0c;用于获取对象类型的所有键&#xff08;属性名&am…

分布式专题——1.1 Redis单机、主从、哨兵、集群部署

1 Redis 部署 下面演示在 Linux 环境下部署 Redis7。 1.1 单机部署 1.1.1 检查安装 gcc 环境Redis 是由 C 语言编写的&#xff0c;它的运行需要 C 环境&#xff0c;因此我们需要先安装 gcc&#xff1b; # 关闭防⽕墙 systemctl stop firewalld.service # 查看防火墙状态 firewa…

2025年渗透测试面试题总结-54(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。1、SQL注入的防护方法有哪些&#xff1f; 2、永恒之蓝的漏洞原理是什么&#xff1f;怎么做到的&#xff1f; 3、命令…

安卓学习 之 按钮点击事件

今天学习安卓应用中的按钮点击事件&#xff1a;总结下来在安卓应用中的Button注册点击事件的方法主要是以下4种方法&#xff0c;稍后会逐个介绍&#xff1a; 第一种方法&#xff1a;自定义内部类的方法 第二种方法&#xff1a;匿名内部类的方法 第三种方法&#xff1a;当前Acti…

鸿蒙NEXT主题设置指南:应用级与页面级主题定制详解

在鸿蒙应用开发中&#xff0c;灵活的主题设置能力是实现个性化用户体验的关键技术&#xff0c;HarmonyOS NEXT提供了强大而灵活的主题设置功能&#xff0c;让开发者能够轻松实现应用级和页面级的主题定制。在当今追求个性化的时代&#xff0c;用户希望应用能够根据自己的喜好呈…

全球汽车氮化镓技术市场规模将于2031年增长至180.5亿美元,2025-2031年复合增长率达94.3%,由Infineon和Navitas驱动

全球汽车氮化镓技术市场规模将于2031年增长至180.5亿美元&#xff0c;2025-2031年复合增长率达94.3%&#xff0c;由Infineon和Navitas驱动汽车氮化镓技术正从一个有前景的细分市场加速进入主流电力电子领域。根据QYResearch&#xff08;恒州博智&#xff09;的《全球汽车GaN技术…

xftp断网后提示错误如何继续下载?

问题&#xff1a;xftp断网后提示错误如何继续下载&#xff1f;解决方法&#xff1a;断网后&#xff0c;先连接上网&#xff0c;然后继续双击右侧的那两个要传输的文件&#xff0c;然后会弹出一个覆盖还是继续下载&#xff08;如下图&#xff09;的选择框&#xff0c;选择继续下…

Day22_【机器学习—集成学习(4)—Boosting—GBDT算法】

提升树 &#xff08;Boosting Decision Tree &#xff09;每一个弱学习器通过拟合残差来构建强学习器梯度提升树 &#xff08;Gradient Boosting Decision Tree&#xff09;每一个弱学习器通过拟合负梯度来构建强学习器一、提升树残差数学公式为&#xff1a;残差真实值−预测值…