0

Tạo Annotation tùy chỉnh trong Java: Hướng dẫn dành cho Developer

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


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.