Annotations
Annotations là metadata mechanism trong Java — cho phép bạn đính kèm thông tin bổ sung vào code mà compiler, tools, hoặc runtime có thể đọc và xử lý.
Annotations là gì?
Bạn đã dùng @Override từ các bài trước mà chưa được giải thích cơ chế. Annotation bắt đầu bằng @ và cung cấp metadata (dữ liệu về dữ liệu) cho code:
@Override // Annotation cho compiler
public String toString() {
return "Hello";
}
Theo JLS §9.6, annotations là interface types thực sự (khai báo bằng @interface). Chúng không thay đổi hành vi code trực tiếp, mà cung cấp thông tin để các tool khác xử lý.
Tại sao cần Annotations?
| Mục đích | Ví dụ | Ai xử lý |
|---|---|---|
| Compiler check | @Override — kiểm tra method có thực sự override | Compiler |
| Code generation | @Entity (JPA) — tạo SQL table | Framework |
| Runtime processing | @Autowired (Spring) — inject dependency | Runtime |
| Documentation | @Deprecated — đánh dấu API sẽ bị xóa | IDE + Compiler |
Built-in Annotations
@Override
Đánh dấu method override method từ superclass hoặc implement method từ interface:
class Animal {
public void makeSound() {
System.out.println("...");
}
}
class Dog extends Animal {
@Override // Compiler kiểm tra: makeSound() có tồn tại ở Animal không?
public void makeSound() {
System.out.println("Woof!");
}
// @Override
// public void makeSoud() { } // ❌ Compile error! Typo: makeSoud ≠ makeSound
}
Không có @Override, typo sẽ tạo method mới thay vì override — bug rất khó tìm:
// Không có @Override → method mới, KHÔNG override
public void makeSoud() { } // Typo nhưng compile OK, chạy sai logic
// Có @Override → compiler báo lỗi ngay
@Override
public void makeSoud() { } // ❌ Compile error — method không tồn tại ở superclass
@Deprecated
Đánh dấu API không nên dùng nữa (sẽ bị xóa trong tương lai):
public class StringUtils {
/**
* @deprecated Use {@link #isEmpty(String)} instead.
*/
@Deprecated(since = "2.0", forRemoval = true)
public static boolean isBlank(String s) {
return s == null || s.trim().isEmpty();
}
public static boolean isEmpty(String s) {
return s == null || s.isEmpty();
}
}
StringUtils.isBlank("hello"); // ⚠️ IDE hiện gạch ngang + warning
since: Version bắt đầu deprecatedforRemoval = true: Sẽ bị xóa hoàn toàn trong phiên bản tương lai
@SuppressWarnings
Tắt cảnh báo compiler cho đoạn code cụ thể:
@SuppressWarnings("unchecked") // Tắt warning unchecked cast
public List<String> legacy() {
List raw = new ArrayList(); // Raw type — không có generic
raw.add("hello");
return raw; // Unchecked assignment warning
}
@SuppressWarnings("deprecation") // Tắt warning dùng API deprecated
public void oldCode() {
StringUtils.isBlank("test");
}
Chỉ dùng khi bạn hiểu rõ warning và chắc chắn code an toàn. Đừng dùng để "giấu" vấn đề thật.
- Đặt ở scope nhỏ nhất có thể (method, không phải class)
- Thêm comment giải thích tại sao suppress
@FunctionalInterface
Đánh dấu interface là functional interface (chỉ có đúng 1 abstract method):
@FunctionalInterface // Compiler kiểm tra: chỉ có 1 abstract method
public interface Validator<T> {
boolean validate(T item);
// Nếu thêm abstract method thứ 2 → compile error
// boolean check(T item); // ❌ Error: not a functional interface
}
// Functional interface → dùng được với lambda
Validator<String> notEmpty = s -> !s.isEmpty();
Validator<Integer> positive = n -> n > 0;
System.out.println(notEmpty.validate("hello")); // true
System.out.println(positive.validate(-1)); // false
@FunctionalInterface là nền tảng cho Lambda Expressions và Stream API mà bạn sẽ học ở Module 10: Functional Programming.
Annotation Elements (Parameters)
Annotations có thể nhận parameters (gọi là elements):
// Annotation với 1 element → dùng "value" mặc định
@SuppressWarnings("unchecked") // value = "unchecked"
@SuppressWarnings({"unchecked", "deprecation"}) // Array of values
// Annotation với nhiều elements → phải đặt tên
@Deprecated(since = "2.0", forRemoval = true)
Single-Element Shorthand
Khi annotation chỉ có 1 element tên value, có thể bỏ tên:
// Hai cách viết tương đương
@SuppressWarnings(value = "unchecked")
@SuppressWarnings("unchecked") // Shorthand — bỏ "value ="
Meta-Annotations
Meta-annotations là annotations dùng để annotate annotation khác — quy định cách annotation hoạt động:
| Meta-annotation | Mục đích |
|---|---|
@Target | Annotation dùng được ở đâu (class, method, field...) |
@Retention | Annotation tồn tại đến khi nào (source, class, runtime) |
@Documented | Đưa annotation vào JavaDoc |
@Inherited | Subclass kế thừa annotation từ superclass |
@Repeatable | Cho phép dùng cùng annotation nhiều lần trên 1 element |
@Retention — Vòng đời Annotation
Source code → Compile → Bytecode (.class) → Runtime (JVM)
↑ ↑ ↑
SOURCE CLASS RUNTIME
(chỉ trong code) (trong .class file) (đọc được lúc chạy)
@Retention(RetentionPolicy.SOURCE) // Mất sau compile (VD: @Override)
@Retention(RetentionPolicy.CLASS) // Có trong .class, mất lúc runtime (default)
@Retention(RetentionPolicy.RUNTIME) // Đọc được lúc chạy qua Reflection
@Retention — Chi tiết
RetentionPolicy xác định vòng đời của annotation:
| Retention Policy | Vòng đời | Khi nào dùng | Ví dụ |
|---|---|---|---|
| SOURCE | Chỉ trong source code, mất sau compile | Compiler checks, code analysis | @Override, @SuppressWarnings |
| CLASS | Trong .class file, không load vào JVM | Bytecode processing, build tools | Lombok @Getter, @Setter |
| RUNTIME | Load vào JVM, đọc được qua Reflection | Runtime processing, frameworks | Spring @Autowired, JPA @Entity |
// SOURCE - Compiler check only
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface SourceAnnotation {
// Compiler xử lý xong là mất
// Không có trong .class file
}
// CLASS - Default retention
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface ClassAnnotation {
// Có trong .class file
// Nhưng JVM không load vào runtime
// Dùng cho bytecode manipulation tools
}
// RUNTIME - Reflection
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RuntimeAnnotation {
// Có thể đọc qua Reflection lúc runtime
// Frameworks như Spring, Hibernate dùng
}
Retention Policy Flow
Source Code (.java)
↓
[@Retention(SOURCE)] ← Annotation mất tại đây
↓
Compile
↓
Bytecode (.class)
↓
[@Retention(CLASS)] ← Annotation mất tại đây (default)
↓
ClassLoader
↓
JVM Runtime
↓
[@Retention(RUNTIME)] ← Annotation còn, đọc được qua Reflection
@Target — ElementType Combinations
@Target(ElementType.METHOD) // Chỉ dùng trên method
@Target(ElementType.TYPE) // Chỉ dùng trên class/interface/enum
@Target(ElementType.FIELD) // Chỉ dùng trên field
@Target({ElementType.METHOD, ElementType.FIELD}) // Trên method HOẶC field
Tất cả ElementType:
| ElementType | Mô tả | Ví dụ |
|---|---|---|
TYPE | Class, interface, enum, annotation | @Entity |
FIELD | Fields (bao gồm enum constants) | @Autowired |
METHOD | Methods | @Test, @Override |
PARAMETER | Method/constructor parameters | @PathVariable |
CONSTRUCTOR | Constructors | @Autowired |
LOCAL_VARIABLE | Local variables | Debug annotations |
ANNOTATION_TYPE | Annotation types (meta-annotation) | @Target, @Retention |
PACKAGE | Package declarations | @Deprecated |
TYPE_PARAMETER | Type parameters (Java 8+) | class Box<@NonNull T> |
TYPE_USE | Any type use (Java 8+) | @NonNull String, List<@NonNull String> |
MODULE | Module declarations (Java 9+) | Module annotations |
RECORD_COMPONENT | Record components (Java 14+) | Record field annotations |
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface Documented {
String author();
String version() default "1.0";
}
// Usage
@Documented(author = "Luật", version = "2.0")
public class MyClass {
@Documented(author = "Minh")
private String field;
@Documented(author = "Hà", version = "1.5")
public void method() {
// implementation
}
}
Custom Annotations
Tạo Annotation đơn giản
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) // Có thể đọc lúc runtime
@Target(ElementType.METHOD) // Chỉ dùng trên method
public @interface Todo {
String value(); // Nội dung todo
String assignee() default ""; // Người phụ trách (optional)
Priority priority() default Priority.MEDIUM;
enum Priority { LOW, MEDIUM, HIGH }
}
Sử dụng Custom Annotation
public class ProjectService {
@Todo("Thêm validation cho input")
public void createProject(String name) {
// implementation
}
@Todo(value = "Cần tối ưu query", assignee = "Luật", priority = Todo.Priority.HIGH)
public List<String> searchProjects(String keyword) {
// implementation
return new ArrayList<>();
}
}
Đọc Annotation lúc Runtime
import java.lang.reflect.Method;
public class TodoReporter {
public static void main(String[] args) {
// Dùng Reflection để đọc annotations
for (Method method : ProjectService.class.getDeclaredMethods()) {
if (method.isAnnotationPresent(Todo.class)) {
Todo todo = method.getAnnotation(Todo.class);
System.out.printf("Method: %s%n", method.getName());
System.out.printf(" Todo: %s%n", todo.value());
System.out.printf(" Priority: %s%n", todo.priority());
System.out.printf(" Assignee: %s%n", todo.assignee());
System.out.println();
}
}
}
}
Output:
Method: createProject
Todo: Thêm validation cho input
Priority: MEDIUM
Assignee:
Method: searchProjects
Todo: Cần tối ưu query
Priority: HIGH
Assignee: Luật
Annotations trong Thực tế
Annotations là nền tảng của hầu hết frameworks Java hiện đại:
| Framework | Annotations phổ biến | Mục đích |
|---|---|---|
| Spring | @Component, @Autowired, @Service | Dependency Injection |
| JPA/Hibernate | @Entity, @Table, @Column | ORM mapping |
| JUnit 5 | @Test, @BeforeEach, @DisplayName | Testing |
| Jackson | @JsonProperty, @JsonIgnore | JSON serialization |
| Lombok | @Getter, @Setter, @Builder | Code generation |
| Validation | @NotNull, @Size, @Email | Bean validation |
@Entity // JPA: class này map với database table
@Table(name = "products") // JPA: tên table
public class Product {
@Id // JPA: primary key
@GeneratedValue // JPA: auto-increment
private Long id;
@Column(nullable = false) // JPA: NOT NULL
@NotBlank // Validation: không được blank
private String name;
@JsonIgnore // Jackson: không serialize field này
private String internalCode;
}
Khi học Spring Boot, JPA, hoặc bất kỳ Java framework nào, bạn sẽ gặp annotations ở khắp nơi. Hiểu cơ chế annotations giúp bạn:
- Đọc hiểu code framework dễ dàng hơn
- Debug khi annotation không hoạt động như mong đợi
- Tạo custom annotations cho dự án riêng
Lỗi thường gặp
Lỗi 1: Quên @Override khi override method
// ❌ Typo nhưng compile OK — tạo method mới
public boolean equal(Object o) { return true; }
// ✅ Compiler báo lỗi nhờ @Override
@Override
public boolean equal(Object o) { return true; }
// Error: method does not override from superclass (equals ≠ equal)
Lỗi 2: Annotation sai vị trí
// ❌ @Override không dùng được trên field
@Override
private String name;
// ✅ @Override chỉ dùng trên method
@Override
public String toString() { return name; }
Lỗi 3: Custom annotation thiếu @Retention(RUNTIME)
// ❌ Default retention là CLASS — không đọc được lúc runtime
@Target(ElementType.METHOD)
public @interface MyAnnotation { }
// ✅ Thêm RUNTIME nếu cần đọc qua Reflection
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation { }
Bài tập
Bài 1: Sử dụng Built-in Annotations [Cơ bản]
Tạo class Calculator với method add(int, int). Sau đó tạo AdvancedCalculator extends Calculator override method add và thêm method multiply. Sử dụng @Override đúng chỗ.
Xem lời giải
class Calculator {
public int add(int a, int b) {
return a + b;
}
}
class AdvancedCalculator extends Calculator {
@Override
public int add(int a, int b) {
System.out.println("Adding: " + a + " + " + b);
return super.add(a, b);
}
// Không có @Override — method mới, không phải override
public int multiply(int a, int b) {
return a * b;
}
}
Bài 2: Tạo Custom Annotation [Trung bình]
Tạo annotation @Author chứa: name (bắt buộc), date (optional, default "unknown"), version (optional, default 1.0). Dùng được trên class và method. Áp dụng lên 2 class.
Xem lời giải
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Author {
String name();
String date() default "unknown";
double version() default 1.0;
}
@Author(name = "Luật", date = "2024-01-15", version = 2.0)
public class UserService {
@Author(name = "Minh")
public void createUser(String name) {
// implementation
}
}
@Author(name = "Hà", version = 1.5)
public class ProductService {
// implementation
}
Bài 3: Annotation Processor [Thách thức]
Tạo annotation @Validate cho method parameters. Viết một class ValidationRunner dùng Reflection để kiểm tra: nếu method có @Validate, kiểm tra parameters không null trước khi gọi method thực sự. In warning nếu phát hiện null parameter.
Gợi ý
Sử dụng java.lang.reflect.Method, Method.getAnnotation(), và Method.invoke(). Đây là nền tảng của cách Spring Framework xử lý @NotNull.
Annotation Processing — Overview
Annotation processing xảy ra ở 2 thời điểm khác nhau:
1. Compile-time Annotation Processing
Tools xử lý annotations trong quá trình compile để:
- Generate code (Lombok, AutoValue)
- Validate code (Checker Framework)
- Generate documentation
@Retention(RetentionPolicy.SOURCE) // Chỉ cần lúc compile
@Target(ElementType.TYPE)
public @interface GenerateBuilder {
}
@GenerateBuilder
public class Person {
private String name;
private int age;
// Annotation processor tạo Builder class tự động
}
Frameworks sử dụng:
- Lombok:
@Getter,@Setter,@Builder→ Generate boilerplate code - AutoValue: Generate value classes
- Dagger: Dependency injection code generation
2. Runtime Annotation Processing
Frameworks đọc annotations lúc runtime qua Reflection:
@Retention(RetentionPolicy.RUNTIME) // Phải RUNTIME để đọc được
@Target(ElementType.METHOD)
public @interface Transactional {
String value() default "";
}
public class UserService {
@Transactional("readWrite")
public void saveUser(User user) {
// Spring đọc @Transactional lúc runtime
// Wrap method trong transaction
}
}
Frameworks sử dụng:
- Spring:
@Autowired,@Service,@Transactional - Hibernate/JPA:
@Entity,@Table,@Column - JUnit:
@Test,@BeforeEach,@ParameterizedTest
So sánh Compile-time vs Runtime Processing
| Aspect | Compile-time | Runtime |
|---|---|---|
| Retention | SOURCE hoặc CLASS | RUNTIME |
| Performance | ✅ Không ảnh hưởng runtime | ⚠️ Reflection overhead |
| Flexibility | ❌ Cố định sau compile | ✅ Động, có thể thay đổi |
| Type safety | ✅ Compiler check | ⚠️ Runtime errors |
| Ví dụ | Lombok, AutoValue | Spring, Hibernate |
Repeatable Annotations (Java 8+) — Chi tiết
Cho phép dùng cùng annotation nhiều lần trên 1 element:
import java.lang.annotation.*;
// Container annotation - chứa array of repeatable annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Authors {
Author[] value(); // Tên PHẢI là "value"
}
// Repeatable annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(Authors.class) // Chỉ định container
public @interface Author {
String name();
String email() default "";
}
// Usage
public class Book {
@Author(name = "Alice", email = "[email protected]")
@Author(name = "Bob", email = "[email protected]")
@Author(name = "Charlie")
public void write() {
System.out.println("Writing book...");
}
public static void main(String[] args) throws Exception {
var method = Book.class.getMethod("write");
// Cách 1: Đọc repeatable annotations
Author[] authors = method.getAnnotationsByType(Author.class);
for (Author author : authors) {
System.out.println("Author: " + author.name() + " <" + author.email() + ">");
}
// Cách 2: Đọc container annotation
Authors authorsContainer = method.getAnnotation(Authors.class);
if (authorsContainer != null) {
for (Author author : authorsContainer.value()) {
System.out.println("Via container: " + author.name());
}
}
}
}
Quy tắc Repeatable Annotations
- Container annotation phải có method
value()return array of repeatable annotation - Container và repeatable annotation phải cùng retention policy
- Container phải cùng target (hoặc superset) với repeatable annotation
// ❌ Sai - retention khác nhau
@Retention(RetentionPolicy.SOURCE)
@interface Container {
Repeatable[] value();
}
@Retention(RetentionPolicy.RUNTIME) // ❌ Khác container
@Repeatable(Container.class)
@interface Repeatable { }
// ✅ Đúng - cùng retention
@Retention(RetentionPolicy.RUNTIME)
@interface GoodContainer {
GoodRepeatable[] value();
}
@Retention(RetentionPolicy.RUNTIME) // ✅ Cùng container
@Repeatable(GoodContainer.class)
@interface GoodRepeatable { }
@Inherited — Annotation kế thừa
@Inherited là meta-annotation cho phép subclass tự động kế thừa annotation từ superclass (chỉ áp dụng cho class annotation, không áp dụng cho method/field).
Demo: @Inherited với Reflection
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited // ← Subclass sẽ kế thừa annotation này
@interface Auditable {
String author();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
// Không có @Inherited
@interface NonInherited {
String value();
}
@Auditable(author = "Admin")
@NonInherited("test")
class ParentEntity { }
class ChildEntity extends ParentEntity { }
// ChildEntity KHÔNG có @NonInherited
// Nhưng CÓ @Auditable (nhờ @Inherited)
public class InheritedDemo {
public static void main(String[] args) {
// Kiểm tra trên ChildEntity
System.out.println(ChildEntity.class.isAnnotationPresent(Auditable.class));
// true ← Kế thừa từ ParentEntity
System.out.println(ChildEntity.class.isAnnotationPresent(NonInherited.class));
// false ← Không kế thừa vì thiếu @Inherited
Auditable audit = ChildEntity.class.getAnnotation(Auditable.class);
System.out.println("Author: " + audit.author());
// "Author: Admin" ← Đọc được từ child class
}
}
- Chỉ hoạt động với class annotation (không phải method, field, interface)
- Chỉ hoạt động với class kế thừa (extends), không phải interface implementation
- Nếu child class có cùng annotation → override annotation của parent
Annotation Default Values — Không thể là null
Khi định nghĩa custom annotation, default value không thể là null:
@interface MyAnnotation {
String name(); // Bắt buộc — không có default
String description() default "N/A"; // ✅ OK — String literal
int priority() default 0; // ✅ OK — primitive
Class<?> type() default Object.class; // ✅ OK — Class literal
String[] tags() default {}; // ✅ OK — empty array
// ❌ Compile error! Default value không thể null
// String comment() default null;
}
Các kiểu được phép làm annotation element:
- Primitives (
int,long,boolean,...) StringClass<?>(hoặc subtype)enumtype- Annotation type khác
- Array của các kiểu trên
Câu hỏi: Đoạn code sau có compile được không?
@interface Config {
String value() default null;
}
Đáp án: Không! Annotation element default value không thể là null. Dùng chuỗi rỗng "" hoặc giá trị sentinel thay thế.
Tóm tắt
| Annotation | Mục đích |
|---|---|
@Override | Đảm bảo override đúng method |
@Deprecated | Đánh dấu API sắp bị xóa |
@SuppressWarnings | Tắt warning cụ thể |
@FunctionalInterface | Đánh dấu functional interface |
@Retention | Quy định vòng đời annotation (SOURCE/CLASS/RUNTIME) |
@Target | Quy định nơi đặt annotation (TYPE/METHOD/FIELD...) |
@Repeatable | Cho phép dùng annotation nhiều lần (Java 8+) |
Custom @interface | Tạo annotation riêng |
@Inherited | Subclass tự động kế thừa annotation từ superclass |
| Default values | Không thể là null, chỉ primitives/String/Class/enum/annotation/arrays |
| Retention Policy | Trong .class | Reflection | Khi nào dùng |
|---|---|---|---|
SOURCE | ❌ | ❌ | Compiler checks |
CLASS | ✅ | ❌ | Bytecode tools (default) |
RUNTIME | ✅ | ✅ | Frameworks, runtime processing |