Invoking a Private Method in Java – 在Java中调用一个私有方法

最后修改: 2021年 3月 4日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

While methods are made private in Java to prevent them from being called from outside the owning class, we may still need to invoke them for some reason.

虽然方法在Java中被定为private,以防止它们从拥有的类之外被调用,但我们仍然可能因为某些原因需要调用它们。

To achieve this, we need to work around Java’s access controls. This may help us reach a corner of a library or allow us to test some code that should normally remain private.

为了达到这个目的,我们需要绕过Java的访问控制。这可能会帮助我们到达一个库的某个角落,或者允许我们测试一些通常应该保持隐私的代码。

In this short tutorial, we’ll look at how we can verify the functionality of a method regardless of its visibility. We’ll consider two different approaches: the Java Reflection API and Spring’s ReflectionTestUtils.

在这个简短的教程中,我们将研究如何验证一个方法的功能,而不考虑其可见性。我们将考虑两种不同的方法:Java Reflection API 和 Spring 的ReflectionTestUtils

2. Visibility Out of Our Control

2.不受我们控制的可见性

For our example, let’s use a utility class LongArrayUtil that operates on long arrays. Our class has two indexOf methods:

在我们的例子中,让我们使用一个实用类LongArrayUtil,对long数组进行操作。我们的类有两个indexOf方法。

public static int indexOf(long[] array, long target) {
    return indexOf(array, target, 0, array.length);
}

private static int indexOf(long[] array, long target, int start, int end) {
    for (int i = start; i < end; i++) {
        if (array[i] == target) {
            return i;
        }
    }
    return -1;
}

Let’s assume that the visibility of these methods cannot be changed, and yet we want to call the private indexOf method.

让我们假设这些方法的可见性不能被改变,然而我们想调用私有的indexOf方法。

3. Java Reflection API

3.Java反思API

3.1. Finding the Method with Reflection

3.1.用反射法寻找方法

While the compiler prevents us from calling a function that is not visible to our class, we can invoke functions via reflection. First, we need to access the Method object that describes the function we want to call:

虽然编译器阻止我们调用一个对我们的类不可见的函数,但我们可以通过反射调用函数。首先,我们需要访问描述我们想要调用的函数的Method对象。

Method indexOfMethod = LongArrayUtil.class.getDeclaredMethod(
  "indexOf", long[].class, long.class, int.class, int.class);

We have to use getDeclaredMethod in order to access non-private methods. We call it on the type that has the function, in this case, LongArrayUtil, and we pass in the types of the parameters to identify the correct method.

我们必须使用getDeclaredMethod来访问非私有方法。我们在拥有该函数的类型上调用它,在本例中是LongArrayUtil,然后我们传入参数的类型来识别正确的方法。

The function may fail and throw an exception if the method does not exist.

如果该方法不存在,该函数可能会失败并抛出一个异常。

3.2. Allow the Method to Be Accessed

3.2.允许该方法被访问

Now we need to elevate the method’s visibility temporarily:

现在我们需要暂时提升该方法的可见性。

indexOfMethod.setAccessible(true);

This change will last until the JVM stops, or the accessible property is set back to false.

这一变化将持续到JVM停止,或者accessible属性被设置为false.

3.3. Invoke the Method with Reflection

3.3.用反思调用方法

Finally, we call invoke on the Method object:

最后,我们Method对象调用invoke

int value = (int) indexOfMethod.invoke(
  LongArrayUtil.class, someLongArray, 2L, 0, someLongArray.length);

We have now successfully accessed a private method.

现在我们已经成功访问了一个私有方法。

The first argument to invoke is the target object, and the remaining arguments need to match our method’s signature. As in this case, our method is static, and the target object is the parent class – LongArrayUtil. For calling instance methods, we’d pass the object whose method we’re calling.

invoke的第一个参数是目标对象,其余参数需要与我们方法的签名相匹配。在这个例子中,我们的方法是static,而目标对象是父类–LongArrayUtil。对于调用实例方法,我们会传递我们要调用的方法的对象。

We should also note that invoke returns Object, which is null for void functions, and which needs casting to the right type in order to use it.

我们还应该注意,invoke返回Object,对于void函数来说,它是null,它需要铸造为正确的类型才能使用。

4. Spring ReflectionTestUtils

4.Spring ReflectionTestUtils

Reaching internals of classes is a common problem in testing. Spring’s test library provides some shortcuts to help unit tests reach classes. This often solves problems specific to unit tests, where a test needs to access a private field which Spring might instantiate at runtime.

接触类的内部结构是测试中的一个常见问题。Spring的测试库提供了一些捷径来帮助单元测试到达类。这通常能解决单元测试特有的问题,即测试需要访问一个Spring可能在运行时实例化的私有字段。

First, we need to add the spring-test dependency in our pom.xml:

首先,我们需要在pom.xml中添加spring-test依赖性。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.4</version>
    <scope>test</scope>
</dependency>

Now we can use the invokeMethod function in ReflectionTestUtils, which uses the same algorithm as above, and saves us writing as much code:

现在我们可以使用invokeMethod中的ReflectionTestUtils函数,它使用了与上面相同的算法,并且省去了我们写那么多代码。

int value = ReflectionTestUtils.invokeMethod(
  LongArrayUtil.class, "indexOf", someLongArray, 1L, 1, someLongArray.length);

As this is a test library, we wouldn’t expect to use this outside of the test code.

由于这是一个测试库,我们不会期望在测试代码之外使用它。

5. Considerations

5.考虑因素

Using reflection to bypass function visibility comes with some risks and may not even be possible. We ought to consider:

使用反射来绕过功能的可见性有一些风险,甚至可能不可能。我们应该考虑。

  • Whether the Java Security Manager will allow this in our runtime
  • Whether the function we’re calling, without compile-time checking, will continue to exist for us to call in the future
  • Refactoring our own code to make things more visible and accessible

6. Conclusion

6.结语

In this article, we looked at how to access private methods using the Java Reflection API and using Spring’s ReflectionTestUtils.

在这篇文章中,我们研究了如何使用Java Reflection API和使用Spring的ReflectionTestUtils访问私有方法。

As always, the example code for this article can be found over on GitHub.

一如既往,本文的示例代码可以在GitHub上找到over