0

[Java Backend Zero to Hello] BÀI 2.9: ANNOTATION & REFLECTION

Java Backend Zero to Hello

📚 Bài viết thuộc series Java Backend Zero to Hello 📌 Phần: Phase 2: Java Core Nâng Cao | Bài 24/86


BÀI 2.9: ANNOTATION & REFLECTION

Mục tiêu

  • Hiểu Annotation và cách sử dụng
  • Tạo Custom Annotation
  • Sử dụng Reflection API
  • Áp dụng vào Framework

1. ANNOTATION LÀ GÌ?

Annotation là metadata (siêu dữ liệu) gắn vào code, cung cấp thông tin cho compiler, tool hoặc runtime.

Các Annotation có sẵn

@Override          // Ghi đè method
@Deprecated        // Không nên dùng
@SuppressWarnings  // Tắt cảnh báo
@FunctionalInterface  // Interface có 1 abstract method
@SafeVarargs       // Varargs an toàn

2. SỬ DỤNG ANNOTATION

2.1 Áp dụng cho các phần tử

@Deprecated
public void oldMethod() { }

@Override
public String toString() { return ""; }

@SuppressWarnings("unchecked")
public List rawList() { return new ArrayList(); }

2.2 Annotation với tham số

@SuppressWarnings(value = "unchecked")
@SuppressWarnings("unchecked")  // value có thể bỏ

@Deprecated(since = "2.0", forRemoval = true)
public void oldMethod() { }

3. TẠO CUSTOM ANNOTATION

3.1 Khai báo

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value() default "";
    int priority() default 0;
}

3.2 Meta-Annotation

Annotation Mô tả
@Retention Khi nào annotation tồn tại
@Target Áp dụng cho đâu
@Documented Có trong Javadoc
@Inherited Kế thừa cho class con
@Repeatable Lặp lại nhiều lần

3.3 RetentionPolicy

@Retention(RetentionPolicy.SOURCE)   // Chỉ source code, mất khi compile
@Retention(RetentionPolicy.CLASS)    // Trong .class, mất khi runtime
@Retention(RetentionPolicy.RUNTIME)  // Có ở runtime - dùng với reflection

3.4 ElementType

@Target(ElementType.TYPE)            // Class, interface, enum
@Target(ElementType.FIELD)           // Field
@Target(ElementType.METHOD)          // Method
@Target(ElementType.PARAMETER)       // Parameter
@Target(ElementType.CONSTRUCTOR)     // Constructor
@Target(ElementType.LOCAL_VARIABLE)  // Biến local
@Target(ElementType.ANNOTATION_TYPE) // Annotation

// Nhiều target
@Target({ElementType.METHOD, ElementType.FIELD})

3.5 Ví dụ đầy đủ

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Test {
    String name() default "";
    int timeout() default 1000;
    String[] tags() default {};
}

3.6 Sử dụng

public class UserService {

    @Test(name = "create user", timeout = 5000, tags = {"unit", "fast"})
    public void createUser() {
        // ...
    }

    @Test
    public void deleteUser() {
        // ...
    }
}

4. REFLECTION

Reflection cho phép kiểm tra và thao tác với class, method, field tại runtime.

4.1 Lấy Class object

// Cách 1: Class.forName()
Class<?> clazz = Class.forName("com.example.User");

// Cách 2: .class
Class<User> userClass = User.class;

// Cách 3: getClass()
User user = new User();
Class<?> clazz = user.getClass();

4.2 Thông tin Class

Class<?> clazz = User.class;

String name = clazz.getName();              // com.example.User
String simpleName = clazz.getSimpleName();  // User
Package pkg = clazz.getPackage();
Class<?> superClass = clazz.getSuperclass();
Class<?>[] interfaces = clazz.getInterfaces();
int modifiers = clazz.getModifiers();
boolean isPublic = Modifier.isPublic(modifiers);
boolean isAbstract = Modifier.isAbstract(modifiers);

4.3 Constructor

Class<?> clazz = User.class;

// Lấy constructor
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Constructor<?>[] constructors = clazz.getConstructors();
Constructor<?>[] allConstructors = clazz.getDeclaredConstructors();

// Tạo instance
Object user = constructor.newInstance("An", 25);

4.4 Field

Class<?> clazz = User.class;

// Lấy field
Field nameField = clazz.getDeclaredField("name");
Field[] fields = clazz.getDeclaredFields();

// Truy cập private field
nameField.setAccessible(true);
nameField.set(user, "Bình");
String name = (String) nameField.get(user);

4.5 Method

Class<?> clazz = User.class;

// Lấy method
Method method = clazz.getDeclaredMethod("setName", String.class);
Method[] methods = clazz.getDeclaredMethods();

// Gọi method
method.setAccessible(true);
method.invoke(user, "Châu");

// Gọi static method
Method staticMethod = clazz.getDeclaredMethod("createDefault");
staticMethod.setAccessible(true);
Object result = staticMethod.invoke(null);

5. ANNOTATION + REFLECTION

// Định nghĩa annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
    String name() default "";
}

// Class sử dụng
public class UserService {
    @Test(name = "test create")
    public void testCreate() { }

    @Test
    public void testDelete() { }
}

// Framework đọc annotation
public class TestRunner {
    public static void runTests(Class<?> clazz) throws Exception {
        Object instance = clazz.getDeclaredConstructor().newInstance();

        for (Method method : clazz.getDeclaredMethods()) {
            if (method.isAnnotationPresent(Test.class)) {
                Test annotation = method.getAnnotation(Test.class);
                System.out.println("Running: " + annotation.name());
                method.invoke(instance);
            }
        }
    }
}

6. ỨNG DỤNG THỰC TẾ

6.1 Dependency Injection (như Spring)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Inject {}

public class UserService {
    @Inject
    private UserRepository userRepository;
}

// Framework tự động inject
public class Container {
    public static <T> T create(Class<T> clazz) throws Exception {
        T instance = clazz.getDeclaredConstructor().newInstance();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Inject.class)) {
                field.setAccessible(true);
                field.set(instance, create(field.getType()));
            }
        }
        return instance;
    }
}

6.2 ORM (như JPA/Hibernate)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
    String name() default "";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Entity {
    String table() default "";
}

@Entity(table = "users")
public class User {
    @Column(name = "user_name")
    private String name;
}

7. HẠN CHẾ CỦA REFLECTION

  • Chậm hơn gọi trực tiếp
  • Phá vỡ encapsulation (truy cập private)
  • Khó debug
  • Không type-safe tại compile time

💡 Chỉ dùng Reflection khi thật sự cần (Framework, Library).


8. BÀI TẬP THỰC HÀNH

Bài 1: Annotation Validator

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NotNull {}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Min {
    int value();
}

public class Validator {
    public static List<String> validate(Object obj) throws IllegalAccessException {
        List<String> errors = new ArrayList<>();
        for (Field field : obj.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            Object value = field.get(obj);

            if (field.isAnnotationPresent(NotNull.class) && value == null) {
                errors.add(field.getName() + " không được null");
            }

            if (field.isAnnotationPresent(Min.class) && value instanceof Number) {
                int min = field.getAnnotation(Min.class).value();
                if (((Number) value).intValue() < min) {
                    errors.add(field.getName() + " phải >= " + min);
                }
            }
        }
        return errors;
    }
}

Bài 2: Auto Mapper

public static <T> T map(Object source, Class<T> targetClass) throws Exception {
    T target = targetClass.getDeclaredConstructor().newInstance();
    for (Field sourceField : source.getClass().getDeclaredFields()) {
        try {
            Field targetField = targetClass.getDeclaredField(sourceField.getName());
            sourceField.setAccessible(true);
            targetField.setAccessible(true);
            targetField.set(target, sourceField.get(source));
        } catch (NoSuchFieldException ignored) {
        }
    }
    return target;
}

Bài 3: JSON-like toString

public static String toJson(Object obj) throws IllegalAccessException {
    StringBuilder sb = new StringBuilder("{");
    Field[] fields = obj.getClass().getDeclaredFields();
    for (int i = 0; i < fields.length; i++) {
        fields[i].setAccessible(true);
        if (i > 0) sb.append(",");
        sb.append("\"").append(fields[i].getName()).append("\":");
        Object value = fields[i].get(obj);
        sb.append(value instanceof String ? "\"" + value + "\"" : value);
    }
    sb.append("}");
    return sb.toString();
}

9. TÓM TẮT

Khái niệm Mô tả
Annotation Metadata cho code
@Retention Thời điểm tồn tại
@Target Phạm vi áp dụng
Reflection Truy cập class tại runtime
Class.forName Lấy Class object
getDeclaredField Lấy field (kể cả private)
setAccessible(true) Bỏ qua access modifier
invoke Gọi method

Bài tiếp theo: 2.10 Enum & Nested class


🧭 Điều Hướng Series

⬅️ Bài trước: BÀI 2.8: I/O & FILE

📋 Lộ trình tổng quan: Xem Toàn Bộ Series

➡️ Bài tiếp theo: BÀI 2.10: ENUM & NESTED CLASS


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí