Chuyển tới nội dung chính

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 đíchVí dụAi xử lý
Compiler check@Override — kiểm tra method có thực sự overrideCompiler
Code generation@Entity (JPA) — tạo SQL tableFramework
Runtime processing@Autowired (Spring) — inject dependencyRuntime
Documentation@Deprecated — đánh dấu API sẽ bị xóaIDE + 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
}
Tại sao luôn dùng @Override?

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 deprecated
  • forRemoval = 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");
}
Dùng @SuppressWarnings cẩn thận

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
Liên kết Module 10

@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-annotationMục đích
@TargetAnnotation dùng được ở đâu (class, method, field...)
@RetentionAnnotation tồn tại đến khi nào (source, class, runtime)
@DocumentedĐưa annotation vào JavaDoc
@InheritedSubclass kế thừa annotation từ superclass
@RepeatableCho 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 PolicyVòng đờiKhi nào dùngVí dụ
SOURCEChỉ trong source code, mất sau compileCompiler checks, code analysis@Override, @SuppressWarnings
CLASSTrong .class file, không load vào JVMBytecode processing, build toolsLombok @Getter, @Setter
RUNTIMELoad vào JVM, đọc được qua ReflectionRuntime processing, frameworksSpring @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:

ElementTypeMô tảVí dụ
TYPEClass, interface, enum, annotation@Entity
FIELDFields (bao gồm enum constants)@Autowired
METHODMethods@Test, @Override
PARAMETERMethod/constructor parameters@PathVariable
CONSTRUCTORConstructors@Autowired
LOCAL_VARIABLELocal variablesDebug annotations
ANNOTATION_TYPEAnnotation types (meta-annotation)@Target, @Retention
PACKAGEPackage declarations@Deprecated
TYPE_PARAMETERType parameters (Java 8+)class Box<@NonNull T>
TYPE_USEAny type use (Java 8+)@NonNull String, List<@NonNull String>
MODULEModule declarations (Java 9+)Module annotations
RECORD_COMPONENTRecord 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:

FrameworkAnnotations phổ biếnMục đích
Spring@Component, @Autowired, @ServiceDependency Injection
JPA/Hibernate@Entity, @Table, @ColumnORM mapping
JUnit 5@Test, @BeforeEach, @DisplayNameTesting
Jackson@JsonProperty, @JsonIgnoreJSON serialization
Lombok@Getter, @Setter, @BuilderCode generation
Validation@NotNull, @Size, @EmailBean validation
Ví dụ: Spring Boot Entity
@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;
}
Tại sao hiểu Annotations quan trọng?

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 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

AspectCompile-timeRuntime
RetentionSOURCE hoặc CLASSRUNTIME
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, AutoValueSpring, 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

  1. Container annotation phải có method value() return array of repeatable annotation
  2. Container và repeatable annotation phải cùng retention policy
  3. 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
}
}
Giới hạn của @Inherited
  • 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,...)
  • String
  • Class<?> (hoặc subtype)
  • enum type
  • Annotation type khác
  • Array của các kiểu trên
OCP Trap

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

AnnotationMục đích
@OverrideĐảm bảo override đúng method
@DeprecatedĐánh dấu API sắp bị xóa
@SuppressWarningsTắt warning cụ thể
@FunctionalInterfaceĐánh dấu functional interface
@RetentionQuy định vòng đời annotation (SOURCE/CLASS/RUNTIME)
@TargetQuy định nơi đặt annotation (TYPE/METHOD/FIELD...)
@RepeatableCho phép dùng annotation nhiều lần (Java 8+)
Custom @interfaceTạo annotation riêng
@InheritedSubclass tự động kế thừa annotation từ superclass
Default valuesKhông thể là null, chỉ primitives/String/Class/enum/annotation/arrays
Retention PolicyTrong .classReflectionKhi nào dùng
SOURCECompiler checks
CLASSBytecode tools (default)
RUNTIMEFrameworks, runtime processing

Đọc thêm