如何从Java中不同的类读取私有字段的值?

我在第三方JAR中有一个设计很差的类,我需要访问它的私人字段之一。例如, 为什么我需要选择私人领域是必要的?< / p >
class IWasDesignedPoorly {
private Hashtable stuffIWant;
}


IWasDesignedPoorly obj = ...;

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

356202 次浏览

为了访问私有字段,你需要从类的宣布字段中获取它们,然后使它们可访问:

Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException

编辑:正如aperkins所注释的那样,访问字段,将其设置为可访问和检索值都可以抛出Exceptions,尽管你需要注意的只有检查异常是上面注释的。

如果您请求的字段名称与已声明的字段不对应,则会抛出NoSuchFieldException

obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException

如果字段不可访问,则抛出IllegalAccessException(例如,如果它是私有的,并且没有通过错过f.setAccessible(true)行而使其可访问)。

可能抛出的# eyz0要么是SecurityExceptions(如果JVM的SecurityManager不允许你改变字段的可访问性),要么是IllegalArgumentExceptions,如果你尝试访问一个不属于字段类类型的对象上的字段:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type

正如oxbow_lakes提到的,您可以使用反射来绕过访问限制(假设您的SecurityManager允许您)。

也就是说,如果这门课设计得如此糟糕,以至于你不得不求助于这样的伎俩,也许你应该寻找另一种选择。当然,这个小技巧现在可能会为你节省几个小时,但它会让你付出多少代价呢?

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

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

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

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

使用Soot Java Optimization框架直接修改字节码。 # EYZ0 < / p >

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

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

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

尝试FieldUtils从Apache commons-lang3:

FieldUtils.readField(object, fieldName, true);

附注:在我看来,反思是邪恶的

使用Java中的反射你可以访问一个类的所有private/public字段和方法到另一个类。但是根据缺点节中的甲骨文 文档,他们建议:

由于反射允许代码执行在非反射代码中不合法的操作,例如访问私有字段和方法,因此使用反射可能导致意想不到的副作用,这可能导致代码功能失调,并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为。”

下面是演示反射的基本概念的代码片段

Reflection1.java

public class Reflection1{


private int i = 10;


public void methoda()
{


System.out.println("method1");
}
public void methodb()
{


System.out.println("method2");
}
public void methodc()
{


System.out.println("method3");
}


}

Reflection2.java

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;




public class Reflection2{


public static void main(String ar[]) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
Method[] mthd = Reflection1.class.getMethods(); // for axis the methods


Field[] fld = Reflection1.class.getDeclaredFields();  // for axis the fields


// Loop for get all the methods in class
for(Method mthd1:mthd)
{


System.out.println("method :"+mthd1.getName());
System.out.println("parametes :"+mthd1.getReturnType());
}


// Loop for get all the Field in class
for(Field fld1:fld)
{
fld1.setAccessible(true);
System.out.println("field :"+fld1.getName());
System.out.println("type :"+fld1.getType());
System.out.println("value :"+fld1.getInt(new Reflaction1()));
}
}


}

希望能有所帮助。

关于反射的另一个注意事项:我在一些特殊情况下观察到,当不同的包中存在几个同名的类时,顶部答案中使用的反射可能无法从对象中选择正确的类。所以如果你知道对象的package.class是什么,那么最好像下面这样访问它的私有字段值:

org.deeplearning4j.nn.layers.BaseOutputLayer ll = (org.deeplearning4j.nn.layers.BaseOutputLayer) model.getLayer(0);
Field f = Class.forName("org.deeplearning4j.nn.layers.BaseOutputLayer").getDeclaredField("solver");
f.setAccessible(true);
Solver s = (Solver) f.get(ll);

(这是一个不适合我的例子)

您需要完成以下操作:

private static Field getField(Class<?> cls, String fieldName) {
for (Class<?> c = cls; c != null; c = c.getSuperclass()) {
try {
final Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (final NoSuchFieldException e) {
// Try parent
} catch (Exception e) {
throw new IllegalArgumentException(
"Cannot access field " + cls.getName() + "." + fieldName, e);
}
}
throw new IllegalArgumentException(
"Cannot find field " + cls.getName() + "." + fieldName);
}

如果使用Spring:

测试环境中,ReflectionTestUtils提供了一些方便的工具,可以以最小的工作量帮助解决这个问题。它被描述为“用于单元和集成测试场景”;

non-testing上下文中,也有一个类似的类,名为ReflectionUtils,但它被描述为“仅供内部使用”; -请参阅这个答案,以更好地解释这是什么意思。

要解决原文中的例子:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");

尝试绕过这种情况下的问题,您想要设置/获取数据的类是您自己的类之一。

只需创建一个public setter(Field f, Object value)public Object getter(Field f)。您甚至可以在这些成员函数中自己进行一些安全检查。例如,对于setter:

class myClassName {
private String aString;


public set(Field field, Object value) {
// (A) do some checkings here  for security


// (B) set the value
field.set(this, value);
}
}

当然,现在你必须在设置字段值之前找出sStringjava.lang.reflect.Field

我确实在一个通用的resultset -to-and-from-model- mapping器中使用了这种技术。

使用XrayInterface工具非常简单。只需定义缺失的getter /setter,例如:

interface BetterDesigned {
Hashtable getStuffIWant(); //is mapped by convention to stuffIWant
}

用x射线照射你设计糟糕的项目:

IWasDesignedPoorly obj = new IWasDesignedPoorly();
BetterDesigned better = ...;
System.out.println(better.getStuffIWant());

在内部,这依赖于反思。

你可以使用< em > jOOR < / em >

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

使用LookupVarHandle对象作为static final字段也是可取的。