Bạn đã bao giờ tự hỏi làm thế nào mà các framework như Spring hay Hibernate lại có thể tạo ra những annotation "thần kỳ" như @Component
hay @Entity
chưa? Chuẩn bị tinh thần nhé, vì chúng ta sắp bước vào thế giới của annotation tùy chỉnh trong Java — và tin mình đi, nó thú vị hơn bạn tưởng nhiều!
Annotation thực chất là gì?
Hãy nghĩ về annotation như những "tờ giấy ghi chú" mà bạn gắn vào mã nguồn của mình. Chúng không trực tiếp thay đổi hành vi của code, nhưng cung cấp metadata (siêu dữ liệu) mà các phần khác của ứng dụng (hoặc framework) có thể đọc và xử lý. Nó giống như việc bạn để lại lời nhắn cho chính mình hoặc đồng đội trong tương lai.
Cấu trúc cơ bản của Annotation tùy chỉnh
Bắt đầu với ví dụ đơn giản:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAwesomeAnnotation { String value() default "default value"; int priority() default 1;
}
Trong đó:
@Target
: Dùng để chỉ định annotation này dùng ở đâu — method, class, field?@Retention
: Chỉ định annotation này tồn tại bao lâu — trong lúc compile, hay chạy chương trình?@interface
: Cú pháp để định nghĩa một annotation (khác với interface thông thường!).
Meta-Annotation: Annotation dùng để Annotate Annotation
Nghe giống phim Inception nhỉ? Đây là những annotation mà bạn đặt lên chính annotation khác:
@Target
– Gắn Annotation ở đâu?
@Target(ElementType.TYPE) // On classes, interfaces, enums
@Target(ElementType.METHOD) // On methods
@Target(ElementType.FIELD) // On fields
@Target(ElementType.PARAMETER) // On method parameters
@Target({ElementType.TYPE, ElementType.METHOD}) // Multiple targets
@Retention
– Tồn tại bao lâu?
@Retention(RetentionPolicy.SOURCE) // Gone after compilation
@Retention(RetentionPolicy.CLASS) // In .class files, but not at runtime
@Retention(RetentionPolicy.RUNTIME) // Available at runtime (most common)
💡 Pro tip: Nếu bạn muốn dùng Reflection để đọc annotation lúc runtime, bạn PHẢI dùng RetentionPolicy.RUNTIME
.
Xây dựng một Annotation hữu ích: @Timer
Tạo một annotation để đo thời gian thực thi của method:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer { String unit() default "ms"; boolean logResult() default true;
}
Sử dụng:
public class MyService { @Timer(unit = "seconds", logResult = true) public void doSomethingTimeConsuming() { // Your slow code here Thread.sleep(2000); } @Timer // Uses defaults public String calculateSomething() { // Some calculation return "result"; }
}
Đọc Annotation bằng Reflection
Tạo annotation mới chỉ là một nửa vấn đề. Giờ ta cần thực thi logic liên quan đến annotation:
import java.lang.reflect.Method; public class TimerProcessor { public static void processTimerAnnotations(Object obj) { Class<?> clazz = obj.getClass(); for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(Timer.class)) { Timer timer = method.getAnnotation(Timer.class); // Wrap the method call with timing logic long startTime = System.currentTimeMillis(); try { method.invoke(obj); } catch (Exception e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); if (timer.logResult()) { System.out.println("Method " + method.getName() + " took " + (endTime - startTime) + " " + timer.unit()); } } } }
}
Tính năng nâng cao (Vì sao không?)
Annotation với Mảng
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidatedBy { Class<?>[] validators(); String[] groups() default {};
} // Usage
@ValidatedBy( validators = {EmailValidator.class, LengthValidator.class}, groups = {"create", "update"}
)
public class User { // User fields
}
Annotation lồng nhau
public @interface ApiEndpoint { String path(); HttpMethod method() default HttpMethod.GET; RateLimit rateLimit() default @RateLimit(requests = 100, per = "minute");
} public @interface RateLimit { int requests(); String per();
}
Ví dụ thực tế: Framework Validation đơn giản
Tạo 2 annotation:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotEmpty { String message() default "Field cannot be empty";
} @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MinLength { int value(); String message() default "Field is too short";
}
Sử dụng:
public class User { @NotEmpty(message = "Username is required") @MinLength(value = 3, message = "Username must be at least 3 characters") private String username; @NotEmpty private String email;
}
Trình kiểm tra đơn giản:
public class Validator { public static boolean validate(Object obj) { Class<?> clazz = obj.getClass(); boolean isValid = true; for (Field field : clazz.getDeclaredFields()) { field.setAccessible(true); try { Object value = field.get(obj); if (field.isAnnotationPresent(NotEmpty.class)) { if (value == null || value.toString().trim().isEmpty()) { NotEmpty annotation = field.getAnnotation(NotEmpty.class); System.out.println(annotation.message()); isValid = false; } } if (field.isAnnotationPresent(MinLength.class)) { MinLength annotation = field.getAnnotation(MinLength.class); if (value != null && value.toString().length() < annotation.value()) { System.out.println(annotation.message()); isValid = false; } } } catch (IllegalAccessException e) { e.printStackTrace(); } } return isValid; }
}
Những sai lầm thường gặp & mẹo
1. Annotation Processing vs Reflection
- Annotation Processing: chạy lúc compile (như Lombok, AutoValue)
- Reflection: chạy lúc runtime
- Chọn tuỳ theo mục tiêu của bạn: tạo code tự động hay xử lý động
2. Luôn dùng giá trị mặc định
Giúp giảm boilerplate và làm annotation dễ dùng hơn
3. Giữ cho đơn giản
Nếu annotation có đến 10 tham số, bạn nên xem lại thiết kế
4. Tài liệu rõ ràng
/** * Marks a method for performance monitoring. * * @param threshold Methods taking longer than this (in ms) will be logged * @param logLevel The log level to use for slow methods */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Monitor { long threshold() default 1000; String logLevel() default "WARN";
}
Khi nào nên tạo Annotation tùy chỉnh?
Nên dùng nếu:
- Bạn xử lý các tác vụ xuyên suốt (logging, bảo mật, validation)
- Cần metadata cấu hình
- Hỗ trợ code generation
- Làm điểm tích hợp với framework
Nên cân nhắc lại nếu:
- Bạn chỉ đang “nghịch cho vui”
- Có thể làm bằng cách truyền tham số thông thường
- Code quá nhiều annotation lộn xộn ("annotation soup")
Kết luận
Annotation tùy chỉnh là siêu năng lực trong Java. Chúng giúp bạn viết code gọn hơn, dễ bảo trì hơn và tạo nền tảng cho những framework mạnh mẽ. Nhưng hãy nhớ: “Quyền năng lớn đi kèm với trách nhiệm lớn.” Hãy dùng annotation một cách thông minh và có tổ chức — đồng đội bạn sẽ cảm ơn bạn đấy.
Giờ thì hãy đi và "annotate" có trách nhiệm nhé!