@Value đủ cho vài thuộc tính. Với cấu hình thật, có cách tốt hơn nhiều.
Gom thành một record
@ConfigurationProperties(prefix = "app")
record AppProps(
@NotBlank String ten,
@Min(1) @Max(65535) int cong,
Duration hanCho,
DataSize kichThuoc,
List<String> danhSach,
Map<String, Integer> bang) {}
app:
ten: "dịch vụ đơn hàng"
cong: 8080
han-cho: 30s
kich-thuoc: 10MB
danh-sach: [a, b, c]
bang: {x: 1, y: 2}
ten=dịch vụ đơn hàng cong=8080
hanCho=PT30S (Duration, parse từ '30s')
kichThuoc=10485760B (DataSize, parse từ '10MB')
danhSach=[a, b, c] bang={x=1, y=2}
Bốn thứ đáng chú ý.
record dùng được từ Boot 2.6 — bất biến, gọn, không cần getter/setter. Đây là cách tôi khuyên cho mã mới.
Chuyển kiểu tự động. 30s thành Duration, 10MB thành DataSize với 10.485.760 byte. Không phải tự parse, không phải nhớ đơn vị.
Duration nhận 30s, 5m, 2h, PT30S. DataSize nhận 10MB, 512KB, 1GB.
List và Map ánh xạ thẳng từ YAML.
Tên gạch nối thành camelCase. han-cho thành hanCho nhờ relaxed binding.
Bật nó lên
@SpringBootApplication
@EnableConfigurationProperties(AppProps.class)
public class App { }
Hoặc gắn @ConfigurationPropertiesScan để quét tự động, hoặc thêm @Component vào lớp props.
Với record, @EnableConfigurationProperties là cách rõ ràng nhất.
Ba lý do tốt hơn @Value
Kiểu mạnh và chuyển kiểu. @Value("${app.han-cho}") String hanCho cho bạn một chuỗi "30s" mà bạn phải tự parse. @ConfigurationProperties cho Duration.
Validation lúc khởi động:
@ConfigurationProperties(prefix = "app")
@Validated
record AppProps(@NotBlank String ten, @Min(1) @Max(65535) int cong) {}
Cấu hình sai làm ứng dụng không khởi động, kèm thông báo rõ ràng chỉ đúng thuộc tính nào sai. So với @Value, nơi giá trị sai chỉ lộ ra khi có ai đó dùng tới.
Cần spring-boot-starter-validation trong pom.xml.
Nhóm lại được và test được. Một đối tượng truyền vào hàm khởi tạo, thay vì mười @Value rải rác. Test thì new AppProps("x", 8080, ...) là xong — không cần Spring.
Tiêm vào bean
@Service
class DichVu {
private final AppProps props;
DichVu(AppProps props) { this.props = props; }
}
Như mọi bean khác, đúng nguyên tắc tiêm qua hàm khởi tạo ở bài 4.
Lồng nhau
@ConfigurationProperties(prefix = "app")
record AppProps(String ten, Csdl csdl, Mail mail) {
record Csdl(String url, int pool) {}
record Mail(String host, int cong) {}
}
app:
ten: x
csdl:
url: jdbc:...
pool: 20
Cấu trúc phản chiếu đúng cấu trúc YAML, và người đọc nhìn một lớp là biết ứng dụng cần gì.
Giá trị mặc định
Với record, dùng hàm khởi tạo rút gọn:
record AppProps(String ten, Integer cong) {
AppProps {
if (cong == null) cong = 8080;
}
}
Hoặc đơn giản hơn: để mặc định trong application.yml. Cách thứ hai rõ hơn vì mọi giá trị nằm cùng chỗ.
Nhớ nguyên tắc ở bài 6: mặc định cho thứ vô hại, bắt buộc cho bí mật.
Gợi ý trong IDE
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
Nó sinh metadata để IDE tự động hoàn thành tên thuộc tính trong application.yml và cảnh báo khi bạn gõ sai. Rẻ và rất đáng thêm.
Cấu hình đổi lúc chạy
@ConfigurationProperties đọc một lần lúc khởi động. Đổi tệp không có tác dụng.
Cần đổi lúc chạy thì có @RefreshScope của Spring Cloud, nhưng nó kéo theo cả một hệ sinh thái. Với đa số dịch vụ, khởi động lại là cách đơn giản và đúng đắn hơn — nhất là khi bạn đã triển khai cuốn chiếu.
Ngoại lệ đáng làm: mức log. Actuator cho đổi lúc chạy qua /actuator/loggers, và bài 10 sẽ nói.
Thử ba mươi giây
Đếm @Value trong dự án của bạn:
grep -rn '@Value' --include='*.java' src/main | wc -l
Nếu con số lớn hơn năm, gom chúng vào một hoặc vài record @ConfigurationProperties. Bạn được kiểu mạnh, validation lúc khởi động, và một chỗ duy nhất để nhìn thấy dịch vụ cần gì để chạy.
Ngày mai: auto-configuration — cách Boot tạo ra 263 bean mà bạn không khai.