在 Spring Boot 项目中,DTO(Data Transfer Object)是 Controller 和 Service 之间的“搬运工”,但传统写法冗长乏味。Java 16 引入的 Records 让 DTO 瘦身成功,再结合 Jakarta Bean Validation(如 Hibernate Validator),还能轻松校验输入。今天我们聊聊这套组合拳,配上实战代码,带你打造优雅又健壮的 DTO!
Records 简介:DTO 的“减肥神器”
Records 是 Java 的记录类,天生为不可变数据设计,一行代码就能搞定字段、构造器、getter、equals
、hashCode
和 toString
。相比传统 DTO,它省去了大量样板代码。
传统 DTO:
public class UserDto {private final String name;private final int age;public UserDto(String name, int age) {this.name = name;this.age = age;}public String getName() { return name; }public int getAge() { return age; }// 还有 equals, hashCode, toString...
}
Records 重写:
public record UserDto(String name, int age) {}
效果:简洁到飞起,还自带不可变性,完美契合 DTO 的需求。
与 Spring Boot 集成
Spring Boot 无缝支持 Records,但在 Controller 中使用时,需注意 JSON 序列化(默认字段名如 name()
)和校验配置。
依赖配置,在 pom.xml
中添加:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
</dependencies>
加入 Jakarta Bean Validation,定义带校验的 DTO
Jakarta Bean Validation(JSR 380)提供注解(如 @NotNull
、@Min
)校验字段,我们用它强化 Records。
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Email;public record UserDto(@NotBlank(message = "姓名不能为空")String name,@Min(value = 0, message = "年龄不能为负数")int age,@Email(message = "邮箱格式无效")String email
) {}
说明:
@NotBlank
:确保name
非空且非空白。@Min
:限制age
最小值。@Email
:校验email
格式。
Spring Boot 实战
Controller 层
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/users")
@Validated
public class UserController {@PostMappingpublic ResponseEntity<String> createUser(@Valid @RequestBody UserDto userDto) {return ResponseEntity.ok("User created: " + userDto);}@GetMapping("/{id}")public ResponseEntity<UserDto> getUser(@PathVariable Long id) {UserDto user = new UserDto("Alice", 25, "alice@example.com");return ResponseEntity.ok(user);}
}
关键点:
@Valid
:触发 DTO 的校验。@Validated
:启用方法级校验。
测试输入
-
合法请求:
POST /users {"name": "Alice", "age": 25, "email": "alice@example.com"}
响应:
User created: UserDto[name=Alice, age=25, email=alice@example.com]
-
非法请求:
POST /users {"name": "", "age": -1, "email": "invalid"}
响应:400 Bad Request,错误信息:
{"errors": [{"field": "name", "message": "姓名不能为空"},{"field": "age", "message": "年龄不能为负数"},{"field": "email", "message": "邮箱格式无效"}] }
自定义校验逻辑
想加更复杂的校验?Records 支持自定义构造器:
public record UserDto(@NotBlank(message = "姓名不能为空")String name,@Min(value = 0, message = "年龄不能为负数")int age,@Email(message = "邮箱格式无效")String email
) {public UserDto {if (name != null && name.length() > 50) {throw new IllegalArgumentException("姓名长度不能超过 50");}}
}
效果:Spring 会捕获异常,返回 400 错误。
JSON 序列化兼容性
默认情况下,Jackson 用 name()
而非 getName()
访问字段,可能与前端期望不符。解决办法:
-
加注解:
import com.fasterxml.jackson.annotation.JsonProperty;public record UserDto(@JsonProperty("userName") String name,int age,String email ) {}
输出 JSON:
{"userName": "Alice", "age": 25, "email": "alice@example.com"}
-
全局配置: 在
application.yml
中:spring:jackson:property-naming-strategy: SNAKE_CASE
输出:
{"name": "Alice", "age": 25, "email": "alice@example_com"}
Records 的优势
- 简洁:一行定义 DTO,省去 getter/setter。
- 健壮:结合 Validation,输入安全有保障。
- Spring 友好:与 Controller、JSON 序列化无缝衔接。
- 不可变:天生适合 REST API 数据传递。
注意事项
- 版本:Java 16+(Spring Boot 3.x 默认支持)。
- 限制:无 setter,不适合可变对象。
- 异常处理:建议全局配置
ControllerAdvice
统一返回校验错误。
小结
Records 让 DTO 摆脱臃肿,Jakarta Bean Validation 加上防护盾,再融入 Spring Boot,简直是现代 Java 开发的梦幻组合。