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

 

Bình luận