Java Reflection là gì?
Reflection thể hiện 1 sự phản chiếu khác của class. Java Reflection là một bộ API quan trọng của Java giúp người dùng lấy được thông tin class/super class, duyệt field, method, thay đổi dữ liệu của field hoặc thực thi lời gọi method mà chỉ cần biết tên và tham số dưới dạng String
Ứng dụng của Java Reflection
Java Reflection được ứng dụng trong rất nhiều hệ thống lớn nhỏ, các component mang tính tổng quát cần sử dụng chung cho nhiều class khác nhau. Ví dụ điển hình và thường gặp nhất của Java Reflection là việc gọi hàm từ xa với Webservice Soap, toàn bộ class xử lý nghiệp vụ được xây dựng trên server, soap client chỉ cần 1 bản xml mô tả class dưới dạng text kèm theo url soap server là có thể thực thi được các nghiệp vụ đó từ xa. Mình sẽ ví dụ cụ thể hơn bằng các đoạn sample code dưới.
Object test
package net.tunghuynh.reflect; import java.util.Date; /** * net.tunghuynh.reflect.Person * at 25/01/2019 11:13 AM */ public class Person { public String name; protected String address; private Date birthday; //getter & setter public void sayHello(String str1, String str2){ System.out.println(this.name + ", " + str1 + " " + str2); } public String toString(){ return (name==null?"":name) + " " + (address==null?"":address) + " " + (birthday==null?"":birthday.toString()); } }
Lấy thông tin class
public static void getClassInfo(Class clazz) { System.out.println(String.format("Class: %s", clazz.getName())); System.out.println(String.format("Package: %s", clazz.getPackageName())); System.out.println(String.format("Modifier public: %s", Modifier.isPublic(clazz.getModifiers()))); }
Lấy thông tin field
public static void getFields(Class clazz) { Field[] fs = clazz.getDeclaredFields(); for (int i = 0; i < fs.length; i++) { System.out.print(fs[i].getType()); System.out.println(" " + fs[i].getName()); } }
Lấy thông tin method
public static void getMethods(Class clazz) { Method[] ms = clazz.getDeclaredMethods(); for (int i = 0; i < ms.length; i++) { System.out.print(ms[i].getName()); Parameter[] ps = ms[i].getParameters(); for (int j = 0; j < ps.length; j++) { System.out.print(" " + ps[j].getParameterizedType()); System.out.print(" " + ps[j].getName()); } System.out.println(); } }
Main test
public static void main(String[] args) throws Exception { Class clazz = Class.forName("net.tunghuynh.reflect.Person"); getClassInfo(clazz); getFields(clazz); getMethods(clazz); }
Kết quả khi chạy chương trình
Class: net.tunghuynh.reflect.Person Package: net.tunghuynh.reflect Modifier public: true class java.lang.String name class java.lang.String address class java.util.Date birthday toString getName setName class java.lang.String arg0 getAddress sayHello class java.lang.String arg0 class java.lang.String arg1 setBirthday class java.util.Date arg0 setAddress class java.lang.String arg0 getBirthday
Ở các sample trên có thể thấy, chỉ cần có tên của class dưới dạng String “net.tunghuynh.reflect.Person” là có thể lấy được thông tin của class cũng như danh sách field và method đã khai báo trong class.
Tạo Object mới và khởi tạo giá trị cho field
public static void main(String[] args) throws Exception { Class clazz = Class.forName("net.tunghuynh.reflect.Person"); Object obj = createObject(clazz, new String[]{"name", "address", "birthday"}, new Object[]{"TungHuynh", "Address", new Date()}); System.out.println(obj); } public static Object createObject(Class clazz, String[] fields, Object[] values) throws Exception { Object obj = clazz.newInstance(); for (int i = 0; i < fields.length; i++) { Field field = clazz.getDeclaredField(fields[i]); field.setAccessible(true); field.set(obj, values[i]); } return obj; }
Kết quả
TungHuynh Address Mon Jan 28 10:56:18 ICT 2019
Ta có thể kiểm chứng lại instance của object mới tạo bằng cách debug xem có đúng là Person hay không
Sample trên cho thấy, ta có thể tạo 1 object mới từ tên class và gán giá trị cho các thuộc tính trong object chỉ với tên field. Nhưng có 1 vấn đề ở đây, với các field có modifier không phải là public thì không thể gán giá trị trực tiếp được mà cần phải cấp quyền truy cập vào các field này bằng lệnh field.setAccessible(true);
Gọi một method trong class bằng Java Reflection
public static void main(String[] args) throws Exception { Class clazz = Class.forName("net.tunghuynh.reflect.Person"); Object obj = createObject(clazz, new String[]{"name", "address", "birthday"}, new Object[]{"TungHuynh", "Address", new Date()}); invokeMethod(obj, "sayHello", new Class[]{String.class, String.class}, new Object[]{"Hello", "World"}); } public static Object invokeMethod(Object obj, String methodName, Class[] paramTypes, Object[] paramValues) throws Exception { Method method = obj.getClass().getMethod(methodName, paramTypes); return method.invoke(obj, paramValues); }
Kết quả
TungHuynh, Hello World
Để invoke được 1 method thì ta không cần tên của parameter nhưng buộc phải biết số lượng và thứ tự datatype của các parameter, bởi vì trong java hỗ trợ overloading, các method có thể giống tên nhau nhưng khác số lượng và thứ tự parameter.
Với các sample trên, ta cũng hoàn toàn có thể thay sử dụng Class clazz = Person.class; để thay cho Class clazz = Class.forName(“net.tunghuynh.reflect.Person”); mà không ảnh hưởng gì đến logic.
Để tiện cho việc sử dụng, mình thường tạo 1 class Util để get và set giá trị cho 1 field bất kỳ trong object khi cần thiết.
Với class này, có thể truyền trực tiếp object để xử lý và gán giá trị cho field bằng tên không cần gọi getter setter
import java.lang.reflect.Field; public class ReflectUtils { public static boolean set(Object object, String fieldName, Object fieldValue) { Class<?> clazz = object.getClass(); if (clazz != null) { try { Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); field.set(object, fieldValue); return true; } catch (NoSuchFieldException e) { clazz = clazz.getSuperclass(); } catch (Exception e) { throw new IllegalStateException(e); } } return false; } public static Object get(Object object, String fieldName) { Class<?> clazz = object.getClass(); if (clazz != null) { try { Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); return field.get(object); } catch (NoSuchFieldException e) { clazz = clazz.getSuperclass(); } catch (Exception e) { throw new IllegalStateException(e); } } return null; } }
Sử dụng
public static void main(String[] args) throws Exception { Person person = new Person(); //Cách thông thường person.setName("Tung Huynh"); person.setBirthday(new Date()); //Dùng Reflection ReflectUtils.set(person,"name", "Tung Huynh"); ReflectUtils.set(person,"birthday", new Date()); }
Ở bài sau, mình sẽ đề cập đến vấn đề nâng cao hơn của Java Reflection, đó là Reflect với Annotation