+5

Tìm hiểu và sử dụng Java Refelection Api

Java Refelection là gì?

Java Reflection là một tính năng trong Java (có thể gọi là API hay thư viện) cho phép bạn truy cập và thao tác với thông tin của các đối tượng (như tên lớp, các trường dữ liệu, các phương thức) và chỉnh sửa các trường dữ liệu (kể cả các trường private) trong quá trình runtime.

Bạn có thể áp dụng Java Reflection trong những trường hợp không biết đối tượng cần xử lý là gì (như tên lớp, package, các trường dữ liệu, các phương thức…).

Ví dụ, nếu bạn muốn viết một hàm sao chép hai đối tượng khác nhau, bạn cần kiểm tra xem hai đối tượng có cùng kiểu hay không, có những trường dữ liệu nào và sao chép giá trị của từng trường dữ liệu. Ngoài ra, đối với các trường dữ liệu và phương thức có modifier là private, bạn không thể truy cập từ bên ngoài lớp. Trong những trường hợp cần thiết phải gọi hoặc truy cập các trường dữ liệu, phương thức private từ bên ngoài lớp, Java Reflection là một giải pháp hiệu quả.

Hôm nay mình sẽ viết 3 class để sử dụng tính năng này

com.j4fun.plugins.RefUtils

package com.j4fun.plugins;

import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class RefUtils {
    private Class<?> class_;

    private Method[] methods;

    private Map<String, List<Map<String, String>>> mapMethod;

    public RefUtils(Class<?> class_) {
        this.class_ = class_;
        this.methods = class_.getDeclaredMethods();
        fetchAllMethods();
    }

    public RefUtils(String className) throws ClassNotFoundException {
        this.class_ = Class.forName(className);
        this.methods = class_.getDeclaredMethods();
        fetchAllMethods();
    }

    private void fetchAllMethods() {
        mapMethod = new HashMap<>();
        if (class_ != null) {
            for (Method method : methods) {
                List<Map<String, String>> paramsList = new ArrayList<>();
                Parameter[] parameters = method.getParameters();
                for (Parameter parameter : parameters) {
                    Map<String, String> paramInfo = new HashMap<>();
                    paramInfo.put("name", parameter.getName());
                    paramInfo.put("type", parameter.getType().getName());
                    paramsList.add(paramInfo);
                }
                mapMethod.put(method.getName(), paramsList);
            }
        }
    }

    public boolean hasMethod(String methodName) {
        return mapMethod.containsKey(methodName);
    }

    public Object invokeMethod(String methodName, Object instance, Object... params) throws Exception {
        if (hasMethod(methodName)) {
            Method method = getMethodByNameAndParams(methodName, params);
            if (method.getReturnType().equals(Void.TYPE)) {
                method.invoke(instance, params);
                System.out.println("Method " + methodName + " executed successfully.");
                return null;
            } else {
                return method.invoke(instance, params);
            }
        } else {
            throw new NoSuchMethodException("Method " + methodName + " not found in class " + class_.getName());
        }
    }

    public Method getMethodByNameAndParams(String methodName, Object[] params) throws NoSuchMethodException {
        for (Method method : methods) {
            if (method.getName().equals(methodName) && matchParameterTypes(method.getParameterTypes(), params)) {
                return method;
            }
        }
        throw new NoSuchMethodException("Method " + methodName + " with specified parameters not found in class " + class_.getName());
    }

    private boolean matchParameterTypes(Class<?>[] paramTypes, Object[] params) {
        if (paramTypes.length != params.length) {
            return false;
        }
        for (int i = 0; i < paramTypes.length; i++) {
            if (params[i] == null) {
                continue;
            }
            if (paramTypes[i].isPrimitive()) {
                if (!getWrapperClass(paramTypes[i]).equals(params[i].getClass())) {
                    return false;
                }
            } else if (!paramTypes[i].isAssignableFrom(params[i].getClass())) {
                return false;
            }
        }
        return true;
    }

    private Class<?> getWrapperClass(Class<?> primitiveClass) {
        if (primitiveClass.equals(int.class)) {
            return Integer.class;
        } else if (primitiveClass.equals(long.class)) {
            return Long.class;
        } else if (primitiveClass.equals(double.class)) {
            return Double.class;
        } else if (primitiveClass.equals(float.class)) {
            return Float.class;
        } else if (primitiveClass.equals(boolean.class)) {
            return Boolean.class;
        } else if (primitiveClass.equals(char.class)) {
            return Character.class;
        } else if (primitiveClass.equals(byte.class)) {
            return Byte.class;
        } else if (primitiveClass.equals(short.class)) {
            return Short.class;
        }
        throw new IllegalArgumentException("Unsupported primitive type: " + primitiveClass.getName());
    }

    public Map<String, List<Map<String, String>>> getMethodDetails(String methodName) {
        if (hasMethod(methodName)) {
            Map<String, List<Map<String, String>>> methodDetails = new HashMap<>();
            methodDetails.put(methodName, mapMethod.get(methodName));
            return methodDetails;
        } else {
            return null;
        }
    }

    public Map<String, List<Map<String, String>>> getAllMethods() {
        return mapMethod;
    }
}

com.j4fun.plugins.InvokeMethod

package com.j4fun.plugins;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class InvokeMethod {
    public static Object execute(String classNameMethod, Map<String, String> params) throws Exception {
        String[] parts = classNameMethod.split("/");
        if (parts.length != 2) {
            throw new IllegalArgumentException("Invalid classNameMethod format. Expected format: className/methodName");
        }
        String className = parts[0];
        String methodName = parts[1];
        Class<?> clazz = Class.forName(className);
        RefUtils refUtils = new RefUtils(clazz);
        if (!refUtils.hasMethod(methodName)) {
            throw new NoSuchMethodException("Method " + methodName + " not found in class " + className);
        }
        // Chuyển đổi Map sang Object[]
        Object[] paramValues = convertParams(methodName, params, refUtils);

        Method method = refUtils.getMethodByNameAndParams(methodName, paramValues);
        Object instance = clazz.getDeclaredConstructor().newInstance();

        return refUtils.invokeMethod(methodName, instance, paramValues);
    }

    private static Object[] convertParams(String methodName, Map<String, String> params, RefUtils refUtils) throws Exception {
        List<Map<String, String>> methodParams = refUtils.getMethodDetails(methodName).get(methodName);
        System.out.println(methodParams);
        Object[] paramValues = new Object[methodParams.size()];

        for (int i = 0; i < methodParams.size(); i++) {
            Map<String, String> paramInfo = methodParams.get(i);
            String paramName = paramInfo.get("name");
            String paramType = paramInfo.get("type");
            String paramValue = params.get(paramName);

            paramValues[i] = convertParam(paramValue, paramType);
        }

        return paramValues;
    }

    private static Object convertParam(String value, String type) {
        if (value == null) {
            // return null; // or throw an exception if null values are not acceptable
            throw new IllegalArgumentException("Null values are not acceptable ");
        }

        switch (type) {
            case "java.lang.String":
                return value;
            case "int":
            case "java.lang.Integer":
                return Integer.parseInt(value);
            case "long":
            case "java.lang.Long":
                return Long.parseLong(value);
            case "double":
            case "java.lang.Double":
                return Double.parseDouble(value);
            case "float":
            case "java.lang.Float":
                return Float.parseFloat(value);
            case "boolean":
            case "java.lang.Boolean":
                return Boolean.parseBoolean(value);
            case "char":
            case "java.lang.Character":
                return value.charAt(0);
            default:
                throw new IllegalArgumentException("Unsupported parameter type: [" + type + "] with value: [" + value+"]");
        }
    }
}

com.j4fun.plugins.MyClass

package com.j4fun.plugins;

import java.util.HashMap;
import java.util.Map;

public class MyClass {

    public void sayHello() {
        System.out.println("Hello, world!");
    }

    public String concatenate(String str1, String str2) {
        return str1 + str2;
    }

    public static void main(String[] args) {
        try {
            //Thử với hàm void truyền vào không có tham số
            Object result0 = InvokeMethod.execute("com.j4fun.plugins.MyClass/sayHello", null);
            if (result0 != null) {
                System.out.println("Result: " + result0);
            }
            Map<String, String> map = new HashMap<>();
            map.put("arg0", "Xin chào, ");
            map.put("arg1", "tôi đến từ viblo.asia");
            Object result1 = InvokeMethod.execute("com.j4fun.plugins.MyClass/concatenate", map);
            if (result1 != null) {
                System.out.println("Result: " + result1);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Giải thích code Đoạn mã Java trên sử dụng Reflection để tương tác với các phương thức của một lớp một cách linh hoạt trong thời gian chạy. Dưới đây là giải thích chi tiết về từng phần của mã:

com.j4fun.plugins.RefUtils

Class RefUtils

  • Mục đích: Lớp này cung cấp các phương thức để truy vấn thông tin về các phương thức của một lớp, tìm kiếm phương thức theo tên và tham số, cũng như thực thi các phương thức đó.

  • Các thành phần chính:

    • class_: Đối tượng lớp mà RefUtils đang xử lý.
    • methods: Mảng các phương thức trong lớp.
    • mapMethod: Một Map lưu trữ thông tin chi tiết về từng phương thức và các tham số của nó.
  • Các phương thức quan trọng:

    • fetchAllMethods(): Duyệt qua tất cả các phương thức của lớp và lưu thông tin về từng phương thức vào mapMethod.
    • hasMethod(String methodName): Kiểm tra xem một phương thức có tồn tại trong lớp hay không.
    • invokeMethod(String methodName, Object instance, Object... params): Thực thi một phương thức với các tham số cho trước và trả về kết quả nếu có.
    • getMethodByNameAndParams(String methodName, Object[] params): Tìm kiếm một phương thức dựa trên tên và các tham số.

Các phương thức hỗ trợ

  • matchParameterTypes(Class<?>[] paramTypes, Object[] params): So sánh kiểu dữ liệu của các tham số với kiểu dữ liệu của các tham số của phương thức.
  • getWrapperClass(Class<?> primitiveClass): Chuyển đổi từ kiểu dữ liệu nguyên thủy sang lớp bao (wrapper class).

Một số lưu ý

  • Reflection yêu cầu sử dụng cẩn thận vì nó có thể làm giảm hiệu suất của ứng dụng nếu không được sử dụng đúng cách.
  • Xử lý các ngoại lệ là điều cần thiết khi sử dụng Reflection để đảm bảo ứng dụng ổn định.

com.j4fun.plugins.InvokeMethod

Class InvokeMethod

  • Mục đích: Lớp này cung cấp phương thức execute để thực thi một phương thức của một lớp dựa trên tên phương thức và tham số được đưa vào dưới dạng Map.

  • Các phương thức quan trọng:

    • execute(String classNameMethod, Map<String, String> params): Phân tích và thực thi phương thức của lớp từ tên phương thức và các tham số trong Map.
  • Các phương thức hỗ trợ:

    • convertParams(String methodName, Map<String, String> params, RefUtils refUtils): Chuyển đổi từ Map tham số sang mảng các đối tượng tương ứng với kiểu dữ liệu của các tham số.
    • convertParam(String value, String type): Chuyển đổi giá trị từ dạng chuỗi sang kiểu dữ liệu tương ứng.

com.j4fun.plugins.MyClass

Class MyClass

  • Mục đích: Lớp này là ví dụ về việc sử dụng các phương thức được thực thi bằng Reflection.

  • Các phương thức:

    • sayHello(): In ra chuỗi "Hello, world!".
    • concatenate(String str1, String str2): Ghép hai chuỗi lại với nhau.
  • Chạy ra kết quả image.png

Kết luận

Java Reflection là một công cụ mạnh mẽ cho phép các lập trình viên tương tác với các thành phần của ứng dụng một cách động, tăng tính linh hoạt và tái sử dụng trong mã nguồn. Tuy nhiên, sử dụng Reflection cần thận trọng vì có thể làm giảm hiệu suất và không nên sử dụng nếu có cách thực hiện mà không cần Reflection.

Trong bài viết này, chúng ta đã học cách sử dụng Reflection để tìm kiếm và thực thi các phương thức của một lớp một cách động, thông qua việc xây dựng hai lớp RefUtilsInvokeMethod. Chúng ta cũng đã thấy các ứng dụng thực tế của Reflection trong việc tự động hóa và tăng tính linh hoạt của mã nguồn.

Hy vọng rằng bài viết này sẽ giúp bạn hiểu rõ hơn về Reflection và cách áp dụng nó trong các dự án Java của bạn. Nếu có bất kỳ câu hỏi hay ý kiến nào, đừng ngần ngại để lại comment dưới đây. Chúc bạn thành công và hẹn gặp lại trong các bài viết tiếp theo!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí