Giải thích chi tiết cơ chế reflection trong Java
Reflection là gì?
Nếu bạn từng tìm hiểu nguyên lý bên trong của framework hoặc tự viết framework, chắc hẳn bạn không còn xa lạ với khái niệm reflection.
Reflection được gọi là linh hồn của framework chủ yếu vì nó cho phép chúng ta phân tích class và thực thi các method trong class tại runtime.
Thông qua reflection, bạn có thể lấy thông tin về field, method, constructor và các thành phần khác của class, đồng thời gọi hoặc đọc/ghi chúng khi access control và ranh giới module cho phép. Phạm vi của các API khác nhau cũng khác nhau. Ví dụ, getMethods() trả về các method public có thể truy cập, còn getDeclaredMethods() trả về các method do class hiện tại khai báo nhưng không bao gồm method kế thừa.
Bạn biết các trường hợp ứng dụng của reflection không?
Trong công việc hằng ngày, phần lớn thời gian bạn viết business code nên ít khi gặp trường hợp phải trực tiếp sử dụng cơ chế reflection.
Tuy nhiên, điều đó không có nghĩa reflection không hữu ích. Ngược lại, chính nhờ reflection mà bạn có thể sử dụng nhiều framework một cách dễ dàng. Các framework như Spring/Spring Boot, MyBatis và nhiều framework khác đều sử dụng rộng rãi cơ chế reflection.
Các framework này cũng sử dụng rộng rãi dynamic proxy, còn việc triển khai dynamic proxy lại phụ thuộc vào reflection.
Ví dụ dưới đây là code mẫu triển khai dynamic proxy bằng JDK, trong đó sử dụng lớp Method trong reflection để gọi method được chỉ định.
public class DebugInvocationHandler implements InvocationHandler {
/**
* Đối tượng thực trong proxy class
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after method " + method.getName());
return result;
}
}Ngoài ra, một công cụ mạnh trong Java là annotation cũng được triển khai bằng reflection.
Tại sao khi sử dụng Spring, chỉ một annotation @Component đã có thể khai báo một class là Spring Bean? Tại sao bạn có thể đọc được giá trị trong file cấu hình thông qua annotation @Value? Rốt cuộc chúng hoạt động như thế nào?
Tất cả là nhờ bạn có thể phân tích class dựa trên reflection, sau đó lấy annotation trên class/field/method/tham số của method. Sau khi lấy được annotation, bạn có thể xử lý tiếp.
Bàn về ưu và nhược điểm của cơ chế reflection
Ưu điểm: Giúp code của chúng ta linh hoạt hơn, đồng thời tạo thuận lợi cho việc cung cấp các chức năng dùng ngay cho nhiều framework.
Nhược điểm: Cho phép phân tích và thao tác với class tại runtime, nhưng điều này cũng làm phát sinh vấn đề an toàn. Ví dụ, có thể bỏ qua kiểm tra an toàn của tham số generic (việc kiểm tra an toàn của tham số generic diễn ra tại thời điểm biên dịch). Ngoài ra, performance của reflection cũng thấp hơn đôi chút, nhưng với framework thì ảnh hưởng thực tế không đáng kể. Bài đọc liên quan: Java Reflection: Why is it so slow?
Thực hành reflection
Bốn cách lấy đối tượng Class
Nếu muốn lấy động những thông tin này, chúng ta cần dựa vào đối tượng Class. Đối tượng Class cung cấp cho chương trình đang chạy thông tin về method, variable và các thành phần khác của một class. Java cung cấp bốn cách lấy đối tượng Class:
1. Khi biết class cụ thể, có thể sử dụng:
Class alunbarClass = TargetObject.class;Cách này phù hợp với trường hợp đã biết type cụ thể tại thời điểm biên dịch. Việc lấy class literal không kích hoạt quá trình khởi tạo class.
2. Truyền đường dẫn đầy đủ của class vào Class.forName() để lấy:
Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");3. Lấy thông qua object instance instance.getClass():
TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();4. Dùng class loader truyền đường dẫn của class vào xxxClassLoader.loadClass() để lấy:
ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");Lấy đối tượng Class thông qua class loader sẽ không thực hiện khởi tạo, tức là không thực hiện một loạt bước, trong đó có khởi tạo. Static code block và static object sẽ không được thực thi.
Một số thao tác cơ bản với reflection
- Tạo class
TargetObjectmà chúng ta muốn thao tác bằng reflection.
package cn.javaguide;
public class TargetObject {
private String value;
public TargetObject() {
value = "JavaGuide";
}
public void publicMethod(String s) {
System.out.println("I love " + s);
}
private void privateMethod() {
System.out.println("value is " + value);
}
}- Sử dụng reflection để thao tác với method và field của class này.
package cn.javaguide;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
/**
* Lấy Class object của class TargetObject và tạo instance của class TargetObject
*/
Class<?> targetClass = Class.forName("cn.javaguide.TargetObject");
TargetObject targetObject = (TargetObject) targetClass.getDeclaredConstructor().newInstance();
/**
* Lấy tất cả method được định nghĩa trong class TargetObject
*/
Method[] methods = targetClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
/**
* Lấy và gọi method được chỉ định
*/
Method publicMethod = targetClass.getDeclaredMethod("publicMethod",
String.class);
publicMethod.invoke(targetObject, "JavaGuide");
/**
* Lấy field được chỉ định và sửa field
*/
Field field = targetClass.getDeclaredField("value");
// Tắt kiểm tra quyền truy cập để sửa field trong class
field.setAccessible(true);
field.set(targetObject, "JavaGuide");
/**
* Gọi private method
*/
Method privateMethod = targetClass.getDeclaredMethod("privateMethod");
// Tắt kiểm tra quyền truy cập để gọi private method
privateMethod.setAccessible(true);
privateMethod.invoke(targetObject);
}
}Nội dung output:
publicMethod
privateMethod
I love JavaGuide
value is JavaGuideLưu ý: Có độc giả cho biết code trên khi chạy sẽ ném exception ClassNotFoundException. Nguyên nhân cụ thể là bạn chưa thay tên package trong đoạn code dưới đây bằng package chứa TargetObject do bạn tạo.
Bạn có thể tham khảo bài viết này: https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html.
Class<?> targetClass = Class.forName("cn.javaguide.TargetObject");Lời cuối
Nếu nội dung hữu ích với bạn, hãy tiện tay tặng JavaGuide một Star miễn phí để ủng hộ: GitHub | Gitee.
JavaGuide đã được duy trì gần bảy năm, tích lũy 6100+ commit, với sự chung tay hoàn thiện của 620+ contributor. Star, phản hồi và PR của bạn đều là động lực để dự án tiếp tục cập nhật.
Nếu bạn đang chuẩn bị phỏng vấn backend / phát triển ứng dụng AI, có thể tham khảo Knowledge Planet của tôi, bao gồm các project thực tế về backend và AI, tối ưu CV, hỏi đáp 1-1 và tài liệu về các trọng điểm thường gặp, đã được duy trì liên tục sáu năm.
