有办法调用私有方法吗?

我有一个类,它使用 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来解决这个问题,或者创建另一个类来派生它。

长话短说,我只是想知道是否有一种通过反射访问 private方法的方法。

169474 次浏览

使用 getDeclaredMethod()获取私有 Method 对象,然后使用 method.setAccessible()允许实际调用它。

您可以使用反射调用私有方法:

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

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

如果该方法接受非原始数据类型,那么可以使用以下方法调用任何类的私有方法:

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");

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

@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 检查自动装箱参数的兼容性

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

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

它允许修改诸如 final 静态常量之类的任何字段,并调用 yne protected 方法,而不需要在继承层次结构中指定具体的类

<!-- 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>

您可以使用 Spring 的 RefectionTestUtils (Test.util.RefectionTestUtils)完成此操作

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

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

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