Bất kỳ cách nào để gọi một phương thức riêng tư?


146

Tôi có một lớp sử dụng XML và sự phản chiếu để trả lại Objects cho một lớp khác.

Thông thường các đối tượng này là các trường con của một đối tượng bên ngoài, nhưng đôi khi nó là thứ tôi muốn tạo ra khi đang bay. Tôi đã thử một cái gì đó như thế này nhưng vô ích. Tôi tin rằng vì Java sẽ không cho phép bạn truy cập privatecác phương thức để phản ánh.

Element node = outerNode.item(0);
String methodName = node.getAttribute("method");
String objectName = node.getAttribute("object");

if ("SomeObject".equals(objectName))
    object = someObject;
else
    object = this;

method = object.getClass().getMethod(methodName, (Class[]) null);

Nếu phương thức được cung cấp là private, nó thất bại với a NoSuchMethodException. Tôi có thể giải quyết nó bằng cách tạo phương thức public, hoặc tạo một lớp khác để lấy nó từ đó.

Tóm lại, tôi chỉ tự hỏi liệu có cách nào để truy cập một privatephương thức thông qua sự phản chiếu.

Câu trả lời:


303

Bạn có thể gọi phương thức riêng với sự phản chiếu. Sửa đổi bit cuối cùng của mã được đăng:

Method method = object.getClass().getDeclaredMethod(methodName);
method.setAccessible(true);
Object r = method.invoke(object);

Có một vài cảnh báo. Đầu tiên, getDeclaredMethodsẽ chỉ tìm phương thức được khai báo trong hiện tại Class, không được kế thừa từ các siêu kiểu. Vì vậy, đi qua hệ thống phân cấp lớp cụ thể nếu cần thiết. Thứ hai, a SecurityManagercó thể ngăn chặn việc sử dụng setAccessiblephương pháp. Vì vậy, nó có thể cần phải chạy như một PrivilegedAction(sử dụng AccessControllerhoặc Subject).


2
Khi tôi đã làm điều này trong quá khứ, tôi cũng đã gọi phương thức.setAccessible (false) sau khi gọi phương thức, nhưng tôi không biết liệu điều này có cần thiết hay không.
shsteimer

16
Không, khi bạn đặt khả năng truy cập, nó chỉ áp dụng cho trường hợp đó. Miễn là bạn không để đối tượng Phương thức cụ thể đó thoát khỏi sự kiểm soát của mình, điều đó an toàn.
erickson

6
Vì vậy, điểm có phương thức riêng là gì nếu chúng có thể được gọi từ bên ngoài lớp?
Peter Ajtai

2
Ngoài ra, hãy đảm bảo bạn gọi getDeclaredMethod()thay vì chỉ getMethod()- điều này sẽ không hoạt động đối với các phương thức riêng tư.
Ercksen

1
@PeterAjtai Xin lỗi vì phản hồi muộn, nhưng hãy nghĩ về nó theo cách này: Hầu hết mọi người ngày nay khóa cửa của họ, mặc dù họ biết rằng khóa có thể bị phá vỡ hoặc phá vỡ hoàn toàn. Tại sao? Bởi vì nó giúp giữ cho những người trung thực hầu hết trung thực. Bạn có thể nghĩ privatetruy cập đóng một vai trò tương tự.
erickson

34

Sử dụng getDeclaredMethod()để có được một đối tượng Phương thức riêng tư và sau đó sử dụng method.setAccessible()để cho phép thực sự gọi nó.


Trong ví dụ của riêng tôi ( stackoverflow.com/a/15612040/257233 ) Tôi nhận được java.lang.StackOverflowErrornếu tôi không gọi setAccessible(true).
Robert Mark Bram

25

Nếu phương thức chấp nhận kiểu dữ liệu không nguyên thủy thì phương thức sau có thể được sử dụng để gọi một phương thức riêng của bất kỳ lớp nào:

public static Object genericInvokeMethod(Object obj, String methodName,
            Object... params) {
        int paramCount = params.length;
        Method method;
        Object requiredObj = null;
        Class<?>[] classArray = new Class<?>[paramCount];
        for (int i = 0; i < paramCount; i++) {
            classArray[i] = params[i].getClass();
        }
        try {
            method = obj.getClass().getDeclaredMethod(methodName, classArray);
            method.setAccessible(true);
            requiredObj = method.invoke(obj, params);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        return requiredObj;
    }

Các tham số được chấp nhận là obj, methodName và các tham số. Ví dụ

public class Test {
private String concatString(String a, String b) {
    return (a+b);
}
}

Phương thức concatString có thể được gọi là

Test t = new Test();
    String str = (String) genericInvokeMethod(t, "concatString", "Hello", "Mr.x");

7
Tại sao paramCount cần thiết? Bạn không thể sử dụng params.length ?
Saad Malik

7

bạn có thể làm điều này bằng ReflectionTestUtils of Spring ( org.springframework.test.util.ReflectionTestUtils )

ReflectionTestUtils.invokeMethod(instantiatedObject,"methodName",argument);

Ví dụ: nếu bạn có một lớp với phương thức riêng square(int x)

Calculator calculator = new Calculator();
ReflectionTestUtils.invokeMethod(calculator,"square",10);

Ngoài ra còn có một ReflectionUtils hạn chế hơn trong mùa xuân cốt lõi.
Thunderforge

3

Hãy để tôi cung cấp mã hoàn chỉnh để thực hiện các phương thức được bảo vệ thông qua sự phản chiếu. Nó hỗ trợ bất kỳ loại thông số nào, bao gồm cả tổng quát, thông số tự động và giá trị null

@SuppressWarnings("unchecked")
public static <T> T executeSuperMethod(Object instance, String methodName, Object... params) throws Exception {
    return executeMethod(instance.getClass().getSuperclass(), instance, methodName, params);
}

public static <T> T executeMethod(Object instance, String methodName, Object... params) throws Exception {
    return executeMethod(instance.getClass(), instance, methodName, params);
}

@SuppressWarnings("unchecked")
public static <T> T executeMethod(Class clazz, Object instance, String methodName, Object... params) throws Exception {

    Method[] allMethods = clazz.getDeclaredMethods();

    if (allMethods != null && allMethods.length > 0) {

        Class[] paramClasses = Arrays.stream(params).map(p -> p != null ? p.getClass() : null).toArray(Class[]::new);

        for (Method method : allMethods) {
            String currentMethodName = method.getName();
            if (!currentMethodName.equals(methodName)) {
                continue;
            }
            Type[] pTypes = method.getParameterTypes();
            if (pTypes.length == paramClasses.length) {
                boolean goodMethod = true;
                int i = 0;
                for (Type pType : pTypes) {
                    if (!ClassUtils.isAssignable(paramClasses[i++], (Class<?>) pType)) {
                        goodMethod = false;
                        break;
                    }
                }
                if (goodMethod) {
                    method.setAccessible(true);
                    return (T) method.invoke(instance, params);
                }
            }
        }

        throw new MethodNotFoundException("There are no methods found with name " + methodName + " and params " +
            Arrays.toString(paramClasses));
    }

    throw new MethodNotFoundException("There are no methods found with name " + methodName);
}

Phương thức sử dụng apache ClassUtils để kiểm tra tính tương thích của các thông số tự động


Hoàn toàn không có điểm nào trong việc trả lời một câu hỏi 9 năm tuổi với hơn 90000 lượt xem và một câu trả lời được chấp nhận. Trả lời câu hỏi chưa được trả lời thay thế.
Anuraag Baishya

0

Thêm một biến thể nữa là sử dụng thư viện JOOR rất mạnh mẽ https://github.com/jOOQ/jOOR

MyObject myObject = new MyObject()
on(myObject).get("privateField");  

Nó cho phép sửa đổi bất kỳ trường nào như hằng số tĩnh cuối cùng và gọi các phương thức được bảo vệ yne mà không chỉ định lớp cụ thể trong hierarhy kế thừa

<!-- https://mvnrepository.com/artifact/org.jooq/joor-java-8 -->
<dependency>
     <groupId>org.jooq</groupId>
     <artifactId>joor-java-8</artifactId>
     <version>0.9.7</version>
</dependency>

0

Bạn có thể sử dụng @Jailbreak của Manifold để phản ánh Java trực tiếp, an toàn kiểu:

@Jailbreak Foo foo = new Foo();
foo.callMe();

public class Foo {
    private void callMe();
}

@Jailbreakmở khóa foobiến cục bộ trong trình biên dịch để truy cập trực tiếp vào tất cả các thành viên trong Foohệ thống phân cấp.

Tương tự, bạn có thể sử dụng phương thức mở rộng jailbreak () để sử dụng một lần:

foo.jailbreak().callMe();

Thông qua jailbreak()phương pháp bạn có thể truy cập bất kỳ thành viên nào trong Foohệ thống phân cấp.

Trong cả hai trường hợp, trình biên dịch sẽ giải quyết cuộc gọi phương thức cho bạn gõ một cách an toàn, như thể là một phương thức công khai, trong khi Manifold tạo mã phản chiếu hiệu quả cho bạn dưới mui xe.

Ngoài ra, nếu loại không được biết tĩnh, bạn có thể sử dụng Nhập cấu trúc để xác định giao diện mà loại có thể đáp ứng mà không phải khai báo việc thực hiện. Chiến lược này duy trì an toàn loại và tránh các vấn đề về hiệu suất và nhận dạng liên quan đến sự phản ánh và mã proxy.

Khám phá thêm về Manifold .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.