我在第三方JAR中有一个设计很差的类,我需要访问它的一个私有字段。例如, 为什么我需要选择私人领域是必要的?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

如何使用反射来获取stuffIWant的值?


当前回答

您可以使用jOOR来实现这一点。

class Foo {
    private final String value = "ABC";
}
class Bar {
    private final Foo foo = new Foo();
    public String value() {
        return org.joor.Reflect
            .on(this.foo)
            .field("value")
            .get();
    }
}
class BarTest {
    @Test
    void accessPrivateField() {
        Assertions.assertEquals(new Bar().value(), "ABC");
    }
}

其他回答

Java 9引入了变量句柄。您可以使用它们访问类的私有字段。

示例代码如下所示:

var lookup = MethodHandles.lookup();
var handle = MethodHandles
    .privateLookupIn(IWasDesignedPoorly.class, lookup)
    .findVarHandle(IWasDesignedPoorly.class, "stuffIWant", Hashtable.class);
var value = handle.get(obj);

使用Lookup和VarHandle对象作为静态final字段也是可取的。

还有一个没有提到的选项:使用Groovy。Groovy允许您访问私有实例变量,这是该语言设计的一个副作用。无论是否有字段的getter,都可以使用

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()

您可以使用jOOR来实现这一点。

class Foo {
    private final String value = "ABC";
}
class Bar {
    private final Foo foo = new Foo();
    public String value() {
        return org.joor.Reflect
            .on(this.foo)
            .field("value")
            .get();
    }
}
class BarTest {
    @Test
    void accessPrivateField() {
        Assertions.assertEquals(new Bar().value(), "ABC");
    }
}

使用Soot Java Optimization框架直接修改字节码。 http://www.sable.mcgill.ca/soot/

Soot完全是用Java编写的,并且适用于新的Java版本。

反射不是解决问题的唯一方法(它是访问类/组件的私有功能/行为)

另一种解决方案是从.jar中提取类,使用(比如)Jode或Jad反编译它,更改字段(或添加一个访问器),然后根据原始的.jar重新编译它。然后在类路径中将新的.class放在.jar的前面,或者将其重新插入到.jar中。(jar工具允许你提取和重新插入到一个现有的。jar)

如下所述,这解决了访问/更改私有状态的更广泛问题,而不是简单地访问/更改字段。

当然,这要求.jar不能被签名。