Kỹ Thuật Reflection Trong Java: Nguyên Lý Và Thực Hành

Cơ chế Reflection đóng vai trò then chốt trong hệ sinh thái Java, cung cấp khả năng kiểm tra và thao tác với metadata của lớp trong thời gian chạy (runtime). Thông qua cơ chế này, lập trình viên có thể truy xuất thông tin chi tiết về lớp (tên, phương thức, trường dữ liệu, constructor) cũng như thực thi các hành động như khởi tạo đối tượng, gọi hàm hoặc thay đổi giá trị thuộc tính mà không cần biết trước cấu trúc code lúc biên dịch.

1. Các thành phần API chính

Hệ thống reflection của Java được xây dựng chủ yếu dựa trên gói java.lang.reflect kết hợp với lớp Class. Dưới đây là bảng mô tả các thành phần cốt lõi:

Thành phần Chức năng chính
Class Đại diện cho dữ liệu meta của một lớp cụ thể
Constructor Đại diện cho phương thức khởi tạo của lớp
Method Đại diện cho các phương thức hành vi
Field Đại diện cho các biến thành viên
Modifier Hỗ trợ kiểm tra các mức truy cập (public, private...)

2. Ba phương thức thu thập đối tượng Class

Để bắt đầu sử dụng reflection, bước đầu tiên là lấy được đối tượng Class đại diện cho lớp mục tiêu. Có ba cách phổ biến để thực hiện việc này:

// Cách 1: Sử dụng toán tử .class
Class<?> typeInfo = Double.class;

// Cách 2: Gọi phương thức getClass() từ đối tượng
Double numberObj = 10.5;
Class<?> classData = numberObj.getClass();

// Cách 3: Sử dụng Class.forName() với tên đầy đủ
Class<?> loadedClass = Class.forName("java.lang.Integer");

3. Các thao tác thường gặp với Reflection

3.1. Khởi tạo đối tượng

Việc tạo实例 (instance) có thể thực hiện qua constructor mặc định hoặc constructor có tham số. Lưu ý rằng phương thức newInstance() trực tiếp trên Class đã bị deprecated từ Java 9.

// Lấy đối tượng Class
Class<?> clazz = Class.forName("com.demo.Worker");

// Cách khuyến nghị: Lấy Constructor và gọi newInstance
Constructor<?> constructor = clazz.getConstructor();
Object workerObj = constructor.newInstance();

// Khởi tạo với tham số
Constructor<?> paramConstructor = clazz.getConstructor(String.class, int.class);
Object workerWithParams = paramConstructor.newInstance("ID001", 5);

3.2. Thực thi phương thức

Để gọi một method, cần xác định tên và danh sách tham số đầu vào. Đối với method tĩnh (static), đối tượng truyền vào khi invoke có thể là null.

// Định nghĩa method cần gọi
Method action = clazz.getMethod("processTask", String.class);

// Thực thi trên đối tượng cụ thể
Object returnVal = action.invoke(workerObj, "TaskA");

// Thực thi trên method tĩnh
Object staticVal = action.invoke(null, "StaticTask");

3.3. Truy xuất và thay đổi trường dữ liệu

Reflection cho phép đọc ghi cả các trường private nếu được cấp quyền truy cập đặc biệt.

// Lấy trường dữ liệu
Field secretField = clazz.getDeclaredField("securityCode");
secretField.setAccessible(true); // Bỏ qua kiểm tra truy cập

// Đọc giá trị
Object currentCode = secretField.get(workerObj);

// Ghi giá trị mới
secretField.set(workerObj, "NEW_CODE_99");

3.4. Trích xuất thông tin lớp

Có thể lấy được nhiều thông tin meta khác nhau như tên lớp, modifier, lớp cha hoặc các interface đã implement.

// Tên lớp
String fullName = clazz.getName();
String shortName = clazz.getSimpleName();

// Kiểm tra modifier
int mods = clazz.getModifiers();
boolean isPublic = Modifier.isPublic(mods);

// Quan hệ thừa kế
Class<?> parentClass = clazz.getSuperclass();
Class<?>[] implementedInterfaces = clazz.getInterfaces();

4. Ứng dụng nâng cao

4.1. Làm việc với mảng

Lớp Array trong gói reflect hỗ trợ tạo và thao tác với mảng động.

// Tạo đối tượng Class cho mảng Integer
Class<?> arrayType = Class.forName("[Ljava.lang.Integer;");
Object intArray = Array.newInstance(Integer.class, 5);

// Gán và lấy giá trị
Array.set(intArray, 0, 100);
Integer firstVal = (Integer) Array.get(intArray, 0);

4.2. Phân tích Generic

Reflection có thể đọc thông tin về kiểu generic của trường dữ liệu, tuy nhiên không thể tạo instance với generic type động trực tiếp.

Field listField = clazz.getDeclaredField("dataList");
Type genericType = listField.getGenericType();

if (genericType instanceof ParameterizedType) {
    Type[] typeArgs = ((ParameterizedType) genericType).getActualTypeArguments();
    System.out.println("Kiểu tham số: " + typeArgs[0]);
}

4.3. Kết hợp với Dynamic Proxy

Reflection là nền tảng để xây dựng các proxy động, cho phép chèn logic xử lý trước/sau khi gọi method.

ServiceHandler proxy = (ServiceHandler) Proxy.newProxyInstance(
    ServiceHandler.class.getClassLoader(),
    new Class<?>[] { ServiceHandler.class },
    (Object p, Method m, Object[] args) -> {
        System.out.println("Before: " + m.getName());
        Object res = m.invoke(targetObj, args);
        System.out.println("After execution");
        return res;
    }
);

5. Vấn đề hiệu năng và bảo mật

Việc sử dụng reflection thường chậm hơn so với gọi method trực tiếp từ 10 đến 100 lần do quá trình phân giải diễn ra tại runtime. Để tối ưu, nên lưu cache các đối tượng Method hoặc Field thay vì tìm kiếm lại nhiều lần. Các giải pháp thay thế như MethodHandle (Java 7+) hoặc VarHandle (Java 9+) có thể mang lại hiệu suất tốt hơn.

Về mặt bảo mật, việc truy cập thành phần private yêu cầu quyền setAccessible(true), có thể bị chặn bởi SecurityManager. Ngoài ra, kể từ Java 9, hệ thống module (JPMS) hạn chế reflection vào các package không được export.

6. Ví dụ minh họa hoàn chỉnh

import java.lang.reflect.*;

public class ReflectionTest {
    public static void main(String[] args) throws Exception {
        // 1. Load class
        Class<?> clazz = Class.forName("com.demo.Employee");

        // 2. Khởi tạo đối tượng
        Constructor<?> cons = clazz.getConstructor(String.class, int.class);
        Object emp = cons.newInstance("NV001", 3000);

        // 3. Gọi method
        Method showInfo = clazz.getMethod("displayInfo");
        showInfo.invoke(emp);

        // 4. Sửa trường private
        Field salaryField = clazz.getDeclaredField("salary");
        salaryField.setAccessible(true);
        System.out.println("Old salary: " + salaryField.get(emp));
        salaryField.set(emp, 5000);

        // 5. In thông tin class
        System.out.println("Class Name: " + clazz.getName());
        System.out.println("Parent: " + clazz.getSuperclass());
    }
}

class Employee {
    private String empId;
    private int salary;

    public Employee(String empId, int salary) {
        this.empId = empId;
        this.salary = salary;
    }

    public void displayInfo() {
        System.out.println("Employee ID: " + empId);
    }
}

7. Bảng tổng hợp các phương thức quan trọng

Chức năng Phương thức API
Lấy đối tượng Class Class.forName(), .class, getClass()
Tạo instance constructor.newInstance()
Gọi method clazz.getMethod(), method.invoke()
Thao tác trường getDeclaredField(), setAccessible(), get/set()
Thông tin meta getName(), getModifiers(), getSuperclass()

Thẻ: java-reflection runtime-introspection dynamic-proxy java-lang-reflect

Đăng vào ngày 30 tháng 6 lúc 05:00