在 Spring Boot 3 中使用 Java Record
Record
在 Java 14 中作为预览功能引入,并在 JDK 16 中成为标准功能。Record
是不可变数据类(data class)的简洁表示。
在使用 Record
之前,我们通常是这样创建不可变 class 的。
import java.util.Objects;
class Person {
private final Long id;
private final String name;
public Person(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return this.id;
}
public String getName() {
return this.name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Person person = (Person) o;
return Objects.equals(id, person.id) && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Person{" + "id=" + id + ", name='" + name + '\'' + '}';
}
}
虽然大多数情况下我们通常使用 IDE 生成或使用 Lombok
生成 equals()
、hashCode()
和 toString()
,但这会产生更多相同且枯燥的代码。同样的 Person
class 可以写成如下的 Record
:
public record Person(Long id, String name){ }
就这样,这会为 Record 自动生成equals()
、hashCode()
和 toString()
方法。但请注意,getter 并不遵循通常的 getId()
、getName()
模式。相反,它会生成 person.id()
和 person.name()
accessor 方法。
在 SpringBoot 3 中使用 Java Record
Spring Boot 3 于 2022 年 11 月 24 日发布,要求 Java 17 以上。让我们看看如何在 SpringBoot 中使用 Record。
绑定 Application Properties
如果你熟悉 SpringBoot application properties 与类的绑定,像这样:
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties(prefix = "app")
@Validated
class ApplicationProperties {
@Min(1)
@Max(100)
private int pageSize;
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
}
SpringBoot 2.2.0 引入了对 ConstructorBinding
的支持,可用于将 properties 绑定到不可变类。
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.ConstructorBinding;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties(prefix = "app")
@Validated
public class ApplicationProperties {
@Min(1)
@Max(100)
private final int pageSize;
@ConstructorBinding
public ApplicationProperties(int pageSize) {
this.pageSize = pageSize;
}
public int getPageSize() {
return pageSize;
}
}
你很可能希望 ApplicationProperties
对象是不可变的,Record 是一个不错的选择。因此,我们可以将 ApplicationProperties
创建为一个 Record,如下:
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties(prefix = "app")
@Validated
public record ApplicationProperties(
@Min(1)
@Max(100)
int pageSize
) {
}
这样做非常简洁,还能防止意外修改配置属性值。
绑定 Http 请求体/响应体
我们通常创建带有 setter 和 getter 的 DTO 类来绑定传入的 HTTP 请求体。
SpringBoot 默认使用 Jackson 库反序列化JSON请求体为Java对象,或者序列化Java对象为JSON响应体。Jackson 2.12 引入了对 Record 的支持。因此,我们可以使用 Record 绑定传入的请求体,并将 Record 作为响应返回。
下面是一个应用了 Bean Validation 约束的 Record:
import jakarta.validation.constraints.NotEmpty;
import java.time.Instant;
public record Bookmark(
Long id,
@NotEmpty(message = "Title is mandatory")
String title,
@NotEmpty(message = "Url is mandatory")
String url,
Instant createdAt) {
}
我们可以在 SpringMVC Controller 中使用 Bookmark
record,如下所示:
import com.sivalabs.bookmarks.domain.Bookmark;
import com.sivalabs.bookmarks.domain.BookmarkService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.Instant;
@RestController
@RequestMapping("/api/bookmarks")
@RequiredArgsConstructor
public class BookmarkController {
private final BookmarkService service;
@PostMapping
public ResponseEntity<Bookmark> save(@Valid @RequestBody Bookmark bookmark) {
Bookmark savedBookmark = service.save(bookmark);
return ResponseEntity.status(HttpStatus.CREATED).body(savedBookmark);
}
}
上面的代码将 JSON 请求体绑定到 Bookmark
record,并将 Bookmark
record 作为响应体返回,Jackson 将把响应体对象转换为 JSON 格式。
总结
Java record 非常有用,能以非常简洁的语法为不可变数据对象建模。然而,Record 并不是适用于所有情况的灵丹妙药。在某些情况下,普通 class 可能比 Record 更合适。
参考:https://www.sivalabs.in/using-java-records-with-spring-boot-3/