我在第三方JAR中有一个设计很差的类,我需要访问它的一个私有字段。例如, 为什么我需要选择私人领域是必要的?
class IWasDesignedPoorly {
private Hashtable stuffIWant;
}
IWasDesignedPoorly obj = ...;
如何使用反射来获取stuffIWant的值?
我在第三方JAR中有一个设计很差的类,我需要访问它的一个私有字段。例如, 为什么我需要选择私人领域是必要的?
class IWasDesignedPoorly {
private Hashtable stuffIWant;
}
IWasDesignedPoorly obj = ...;
如何使用反射来获取stuffIWant的值?
当前回答
您需要完成以下操作:
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);
}
其他回答
使用Soot Java Optimization框架直接修改字节码。 http://www.sable.mcgill.ca/soot/
Soot完全是用Java编写的,并且适用于新的Java版本。
反射不是解决问题的唯一方法(它是访问类/组件的私有功能/行为)
另一种解决方案是从.jar中提取类,使用(比如)Jode或Jad反编译它,更改字段(或添加一个访问器),然后根据原始的.jar重新编译它。然后在类路径中将新的.class放在.jar的前面,或者将其重新插入到.jar中。(jar工具允许你提取和重新插入到一个现有的。jar)
如下所述,这解决了访问/更改私有状态的更广泛问题,而不是简单地访问/更改字段。
当然,这要求.jar不能被签名。
使用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());
在内部,这依赖于反思。
为了访问私有字段,你需要从类声明的字段中获取它们,然后使它们可访问:
Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException
EDIT:正如aperkins注释的那样,访问字段、将其设置为可访问和检索值都可以抛出异常,尽管您需要注意的唯一检查异常是上面注释的。
如果您请求的字段名称与声明的字段不对应,则会抛出NoSuchFieldException。
obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException
如果字段不可访问(例如,如果它是私有的,并且没有通过遗漏f.setAccessible(true)行进行访问,则抛出IllegalAccessException异常。
可能抛出的runtimeexception是securityexception(如果JVM的SecurityManager不允许你改变字段的可访问性),或者illegalargumentexception,如果你试图访问一个不属于字段类类型的对象上的字段:
f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
您可以使用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");
}
}