1. Serialization qua reflection
Bạn đã biết, serialization là quá trình chuyển đổi một đối tượng thành luồng byte hoặc biểu diễn dạng văn bản. Trong Java có cơ chế serialization chuẩn (Serializable), nhưng thường dùng hơn là các định dạng JSON/XML thông qua thư viện như Jackson và Gson.
Tại sao cần reflection ở đây?
Để serialize một đối tượng, cần biết các trường (field) của nó và giá trị của chúng. Các trường có thể là private, và mã thông thường không truy cập được — còn reflection thì làm được. Vì vậy, các thư viện JSON duyệt động cấu trúc đối tượng, đọc/ghi các trường qua Field và Method.
Ví dụ: serialize đơn giản đối tượng thành chuỗi
Lớp dữ liệu:
public class Person {
private String name;
private int age;
private boolean active;
public Person(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
}
Bộ tuần tự hóa đơn giản bằng reflection:
import java.lang.reflect.Field;
public class SimpleSerializer {
public static String serialize(Object obj) {
StringBuilder sb = new StringBuilder();
Class<?> clazz = obj.getClass();
sb.append(clazz.getSimpleName()).append("{");
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
field.setAccessible(true); // Mở các trường private
try {
sb.append(field.getName()).append("=")
.append(field.get(obj));
} catch (IllegalAccessException e) {
sb.append(field.getName()).append("=<?>");
}
if (i < fields.length - 1) sb.append(", ");
}
sb.append("}");
return sb.toString();
}
}
Cách dùng:
Person p = new Person("Alice", 30, true);
System.out.println(SimpleSerializer.serialize(p));
Person{name=Alice, age=30, active=true}
Hoạt động như thế nào?
- Lấy các trường được khai báo thông qua getDeclaredFields().
- Cho phép truy cập chúng bằng setAccessible(true).
- Đọc tên và giá trị các field rồi tạo chuỗi.
Giới hạn của ví dụ: không xử lý đối tượng lồng nhau, collection, mảng và tham chiếu vòng — chỉ minh họa nguyên lý.
Các thư viện hoàn chỉnh làm thế nào?
Jackson/Gson có thể làm việc với đối tượng lồng nhau và collection, tôn trọng các annotation (@JsonIgnore, @SerializedName), định dạng ngày tháng và nhiều thứ khác — tất cả dựa trên reflection.
Ví dụ với Jackson:
import com.fasterxml.jackson.databind.ObjectMapper;
Person p = new Person("Bob", 25, false);
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(p);
// {"name":"Bob","age":25,"active":false}
2. Dependency Injection (DI) và reflection
Dependency Injection — mẫu thiết kế trong đó các phụ thuộc được “tiêm” từ bên ngoài thay vì tạo ra bên trong lớp. Điều này giúp mã linh hoạt, dễ kiểm thử và mở rộng. Trong Java, các framework như Spring, Guice, Dagger đánh dấu điểm tiêm bằng các annotation như @Autowired, @Inject.
Tại sao ở đây cần reflection?
DI container cần tìm field/constructor, đọc annotation và tạo instance lúc runtime — việc này thực hiện qua API Class/Constructor/Field.
Ví dụ: DI mini bằng reflection
Annotation để tiêm phụ thuộc:
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Inject {
}
Các lớp có phụ thuộc:
public class Service {
public void doWork() {
System.out.println("Service is working!");
}
}
public class Client {
@Inject
private Service service;
public void useService() {
service.doWork();
}
}
Mini-container:
import java.lang.reflect.*;
public class MiniDIContainer {
// Tạo đối tượng từ class và tiêm các phụ thuộc vào các trường có @Inject
public static Object createObject(Class<?> clazz) throws Exception {
Object obj = clazz.getDeclaredConstructor().newInstance();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Inject.class)) {
Object dependency = createObject(field.getType()); // đệ quy
field.setAccessible(true);
field.set(obj, dependency);
}
}
return obj;
}
}
Cách dùng:
public class Main {
public static void main(String[] args) throws Exception {
Client client = (Client) MiniDIContainer.createObject(Client.class);
client.useService(); // Service is working!
}
}
Hoạt động như thế nào? Container tìm các field có @Inject, tạo phụ thuộc theo kiểu của chúng và dùng reflection để đặt chúng vào các trường private.
Quan trọng: đây là sơ đồ đơn giản hóa. DI container thực tế hỗ trợ scope, singleton, cấu hình, proxy, xử lý phụ thuộc vòng, v.v.
3. Proxy động
Proxy — “đối tượng thay thế” chặn lời gọi và bổ sung hành vi: logging, bảo mật, giao dịch, v.v. Trong Java điều này được thực hiện bởi java.lang.reflect.Proxy cùng InvocationHandler. Đây là nền tảng của nhiều khả năng trong Spring AOP, mock trong Mockito, v.v.
Ví dụ: proxy ghi log
public interface HelloService {
void sayHello(String name);
}
public class HelloServiceImpl implements HelloService {
public void sayHello(String name) {
System.out.println("Hello, " + name + "!");
}
}
import java.lang.reflect.*;
public class LoggingProxy {
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target, Class<T> iface) {
return (T) Proxy.newProxyInstance(
iface.getClassLoader(),
new Class<?>[]{iface},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Gọi phương thức: " + method.getName());
return method.invoke(target, args);
}
}
);
}
}
Cách dùng:
HelloService original = new HelloServiceImpl();
HelloService proxy = LoggingProxy.createProxy(original, HelloService.class);
proxy.sayHello("World");
Gọi phương thức: sayHello
Hello, World!
Hoạt động như thế nào? Proxy.newProxyInstance tạo một đối tượng triển khai interface, và mọi lời gọi phương thức được chuyển đến InvocationHandler.invoke, nơi bạn có thể thực thi bất kỳ “mã xuyên suốt” nào trước/sau khi ủy quyền.
4. Tình huống thực tế và hạn chế
Reflection được dùng ở đâu trong thực tế?
- JUnit — tìm các phương thức có @Test và gọi chúng qua reflection.
- Spring — tạo bean, tiêm phụ thuộc, quét annotation, sinh proxy.
- Jackson/Gson — serialize/deserialize, đọc cả các trường private.
- Hibernate — dựng mô hình ORM dựa trên cấu trúc lớp, quản lý field và đối tượng proxy.
- Mockito — tạo mock và chặn lời gọi qua proxy.
Vì sao không phải lúc nào cũng nên dùng reflection?
- Hiệu năng. Nói chung chậm hơn lời gọi thông thường (có thể giảm bằng cache/sinh bytecode).
- Bảo mật. Phá vỡ tính đóng gói; truy cập dữ liệu private.
- Tính module của Java 9+. Có thể xuất hiện InaccessibleObjectException nếu không mở gói/module một cách tường minh.
5. Lỗi thường gặp
Lỗi #1: Bỏ qua checked exception. Các phương thức reflection ném NoSuchFieldException, IllegalAccessException, InvocationTargetException, v.v. Hãy xử lý chúng hoặc bọc trong exception của bạn.
Lỗi #2: setAccessible(true) không phải lúc nào cũng hoạt động. Trên Java 9+ trong ứng dụng module, có thể gặp InaccessibleObjectException. Cần tham số JVM/module (--add-opens) hoặc API public.
Lỗi #3: Phụ thuộc vòng trong DI. Với đệ quy ngây thơ (A phụ thuộc vào B, còn B phụ thuộc A) bạn sẽ gặp StackOverflowError. Container thực tế theo dõi đồ thị phụ thuộc và giải quyết vòng bằng kỹ thuật đặc biệt.
Lỗi #4: Serialization không đầy đủ của đối tượng. Trường tham chiếu được serialize thành ClassName@hash nếu không duyệt đệ quy. Để serialize đúng cần xử lý đối tượng/collection lồng nhau và bảo vệ khỏi vòng lặp.
Lỗi #5: Mất hiệu năng. Các thao tác reflection thường xuyên (trong vòng lặp, trên đường nóng) trở thành nút thắt. Hãy dùng cache Field/Method, sinh bytecode, MethodHandle hoặc annotation processing.
Lỗi #6: Phá vỡ tính đóng gói. Việc thay đổi các trường private qua reflection dẫn đến tính dễ vỡ và bug khó truy vết. Hãy ưu tiên các hợp đồng public.
GO TO FULL VERSION