99 Ngày Java — Ngày 96: Reflection và Annotation
Spring biết tiêm bean vào constructor, Jackson biết biến đối tượng thành JSON, JUnit biết phương thức nào là test. Tất cả dựa trên hai cơ chế: reflection cho phép chương trình tự xem xét lớp của mình lúc chạy, và annotation gắn siêu dữ liệu vào code để công cụ đọc. Hiểu chúng giúp bạn hết coi framework là phép màu, và biết khi nào không nên tự dùng.

1. Class là cửa vào
Mỗi kiểu được nạp vào JVM có một đối tượng Class mô tả nó. Có ba cách lấy: literal Order.class, phương thức getClass() của một đối tượng, hoặc Class.forName với tên đầy đủ. Từ đó bạn liệt kê được trường, phương thức, constructor và annotation. Các phương thức getDeclared... trả về mọi thành viên khai báo trong chính lớp đó, kể cả private; bản không có Declared trả về thành viên public, gồm cả phần kế thừa.
Class<?> type = Order.class;
System.out.println(type.getName()); // vn.motdev.shop.Order
for (var component : type.getRecordComponents()) {
System.out.println(component.getType().getSimpleName()
+ " " + component.getName());
}
for (var method : type.getDeclaredMethods()) {
System.out.println(method.getName());
}2. Đọc và gọi thành viên lúc chạy
Với một Method, bạn gọi nó bằng invoke; với một Constructor, tạo đối tượng bằng newInstance. Thành viên private cần setAccessible(true), và với code dùng module thì package còn phải được opens như đã nói ở ngày 85. Mọi lỗi mà trình biên dịch thường bắt giúp giờ dời sang lúc chạy: sai tên phương thức là NoSuchMethodException, sai kiểu đối số là IllegalArgumentException, và ngoại lệ của chính phương thức được bọc trong InvocationTargetException.
Class<?> type = Class.forName("vn.motdev.shop.PriceCalculator");
Object calculator = type.getDeclaredConstructor().newInstance();
Method total = type.getMethod("total", long.class);
Object result = total.invoke(calculator, 1_500_000L); // boxed Long3. Tự tạo annotation
Annotation khai báo bằng @interface. Hai meta-annotation quyết định nó dùng được ở đâu và sống tới khi nào: @Target giới hạn vị trí gắn, @Retention chọn mức giữ lại. Chỉ RetentionPolicy.RUNTIME mới đọc được bằng reflection; mặc định là CLASS, tức annotation có trong file .class nhưng biến mất lúc chạy. Annotation tự nó không làm gì; phải có code đọc nó và hành động.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.RECORD_COMPONENT)
@interface MaxLength {
int value();
String message() default "is too long";
}
record Customer(@MaxLength(50) String name, @MaxLength(20) String phone) {}
static List<String> validate(Record target) throws ReflectiveOperationException {
var errors = new ArrayList<String>();
for (var component : target.getClass().getRecordComponents()) {
MaxLength rule = component.getAnnotation(MaxLength.class);
if (rule == null) continue;
Object value = component.getAccessor().invoke(target);
if (value instanceof String text && text.length() > rule.value()) {
errors.add(component.getName() + " " + rule.message());
}
}
return errors;
}4. Đánh đổi
Lời gọi qua reflection chậm hơn lời gọi trực tiếp và khó được JIT tối ưu, dù các JDK gần đây đã thu hẹp khoảng cách; phần tốn nhất thường là việc tra cứu, nên framework cache lại đối tượng Method. Cái giá lớn hơn là mất an toàn kiểu, phá vỡ đóng gói và làm công cụ phân tích tĩnh không còn thấy lời gọi. Reflection cũng là thứ gây khó cho module và native image.
Viết code ứng dụng thì ưu tiên interface, lambda và generics trước khi nghĩ tới reflection.
Viết thư viện hoặc framework thì reflection là công cụ chính đáng; cache kết quả tra cứu.
Cần sinh code hoặc kiểm tra lúc biên dịch thì dùng annotation processor.
Không dùng setAccessible để chọc vào phần nội bộ của thư viện khác.
Reflection cho phép code làm việc với kiểu nó chưa từng biết, và trả giá bằng những gì trình biên dịch vốn bảo đảm. Ngày 97 quay về việc rất thực tế: nói chuyện với database qua JDBC.
Tài liệu đối chiếu
https://docs.oracle.com/javase/tutorial/reflect/
https://docs.oracle.com/javase/tutorial/java/annotations/
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


