Backend

99 Ngày Java — Ngày 96: Reflection và Annotation

SSite Admin
30 tháng 10, 2026 3 phút đọc 6 lượt xem
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.

Reflection và Annotation

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 Long

3. 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/

S

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.

Bài viết liên quan

99 Ngày Spring — Ngày 96: Performance Tuning

Đo trước khi chỉnh, kích thước pool HikariCP, các nút thắt thường gặp ở tầng dữ liệu, tùy chọn JVM và load test sơ lược.

30 thg 10, 20263 phút6
Kafka trong dự án thực tế: đồng bộ PostgreSQL sang Elasticsearch bằng Debezium CDC

Thiết kế pipeline CDC cho catalog sản phẩm: Kafka Connect, snapshot, update/delete, WAL, retry, retention và dựng lại Elasticsearch bằng index mới cùng alias — kèm cấu hình và sơ đồ thực tế.

30 thg 10, 202633 phút4
99 Ngày Spring — Ngày 95: Deploy lên Kubernetes

Deployment, Service và Ingress, probe từ Actuator, ConfigMap và Secret, cùng graceful shutdown để rolling update không rớt request.

30 thg 10, 20263 phút7