如何调用私有方法?

177

我有一个类,使用XML和反射返回Object给另一个类。

通常这些对象是外部对象的子字段,但有时它是我想要动态生成的东西。我尝试过类似这样的方法,但没有成功。我相信这是因为Java不允许您访问反射中的private方法。

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);
如果提供的方法是private,它会失败并抛出一个NoSuchMethodException。我可以通过将该方法更改为public或创建另一个类来继承它来解决此问题。
简而言之,我想知道是否有办法通过反射访问私有方法。
6个回答

341
你可以使用反射来调用私有方法。修改发布的代码的最后一部分:
Method method = object.getClass().getDeclaredMethod(methodName);
method.setAccessible(true);
Object r = method.invoke(object);

需要注意几点。首先,getDeclaredMethod 只能找到当前 Class 中声明的方法,而不能找到从父类继承而来的方法。因此,如果必要的话,需要遍历具体类层次结构。其次,一个 SecurityManager 可以防止使用 setAccessible 方法。因此,可能需要作为 PrivilegedAction 运行(使用 AccessControllerSubject)。


2
在过去,当我这样做时,我在调用方法后还调用了 method.setAccessible(false),但我不知道是否必要。 - shsteimer
17
不,当您设置可访问性时,它仅适用于该实例。只要您不让该特定的“Method”对象脱离您的控制,就是安全的。 - erickson
8
如果私有方法可以从类外部调用,那么有私有方法的意义是什么?请问。 - Peter Ajtai
4
请确保调用 getDeclaredMethod() 而不是仅仅使用 getMethod(),后者无法用于私有方法。 - Ercksen
8
抱歉回复晚了,但可以这样想:大多数人现在都锁门,即使他们知道锁可以很容易地被破解或完全规避。为什么呢?因为它有助于让大多数诚实的人保持诚实。你可以将“private”访问视为发挥类似作用的一种方式。 - erickson
显示剩余2条评论

37

使用getDeclaredMethod()方法获取私有方法的Method对象,然后使用method.setAccessible()方法允许调用该方法。


在我的示例中(https://dev59.com/aGUp5IYBdhLWcg3wAjxF#15612040),如果我不调用“setAccessible(true)”,我会得到一个“java.lang.StackOverflowError”。 - Robert Mark Bram

31
如果方法接受非原始数据类型,则可以使用以下方法调用任何类的私有方法:
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;
    }

接受的参数包括 obj、methodName 和参数。例如:
public class Test {
private String concatString(String a, String b) {
    return (a+b);
}
}

方法 concatString 可以这样调用:

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

7
为什么需要使用 _paramCount_?不能直接使用 params.length 吗? - Saad Malik

18

您可以使用Spring的ReflectionTestUtils (org.springframework.test.util.ReflectionTestUtils)来实现此操作。

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

示例:如果你有一个带有私有方法square(int x)的类

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

1
在核心Spring中也有一个更为有限的ReflectionUtils。 - Thunderforge

3

让我提供完整的代码,通过反射执行受保护的方法。它支持任何类型的参数,包括泛型、自动装箱参数和空值。

@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);
}

该方法使用Apache ClassUtils来检查自动装箱参数的兼容性。


1
回答一个已经有90000次浏览和被采纳答案的9年老问题毫无意义,不如回答那些还未得到解答的问题。 - Anuraag Baishya

1

还有一种变体是使用非常强大的JOOR库https://github.com/jOOQ/jOOR

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

它允许修改任何字段,如final static常量,并调用受保护的方法,而无需在继承层次结构中指定具体的类。
<!-- 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>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接