我有一个设计不良的第三方JAR
中的类,我需要访问它的一个私有字段。例如,为什么需要选择私有字段,这是必要的吗?
class IWasDesignedPoorly {
private Hashtable stuffIWant;
}
IWasDesignedPoorly obj = ...;
我该如何使用反射来获取stuffIWant
的值?
我有一个设计不良的第三方JAR
中的类,我需要访问它的一个私有字段。例如,为什么需要选择私有字段,这是必要的吗?
class IWasDesignedPoorly {
private Hashtable stuffIWant;
}
IWasDesignedPoorly obj = ...;
我该如何使用反射来获取stuffIWant
的值?
Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException
编辑:正如aperkins所评论的那样,访问该字段、将其设置为可访问并检索值都可能抛出Exception
,尽管你需要注意的唯一已检查异常在上面有注释。
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
尝试使用Apache commons-lang3中的FieldUtils
:
FieldUtils.readField(object, fieldName, true);
附言:我个人认为,反射是邪恶的。
反射不是解决您的问题的唯一方法(即访问类/组件的私有功能/行为)。
另一种解决方案是从.jar文件中提取类,使用(比如)Jode或Jad对其进行反编译,更改字段(或添加访问器),并针对原始的 .jar文件重新编译它。然后将新的.class文件放在classpath的前面,或重新插入到.jar文件中。(jar实用程序允许您从现有的.jar文件中提取和重新插入)
如下所述,这解决了访问/更改私有状态而不仅仅是访问/更改字段的广泛问题。
当然,这需要.jar文件没有签名。
还有一种未被提及的选项:使用Groovy。Groovy允许您访问私有实例变量,这是语言设计的副作用。无论是否有该字段的getter,您都可以直接使用。
def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
使用Java中的反射,可以访问一个类的所有private/public
字段和方法,并在另一个类中使用。但是根据Oracle 文档中的缺点部分建议:
"由于反射允许代码执行非反射代码中不合法的操作,例如访问私有字段和方法,因此使用反射可能会导致意外的副作用,这可能使代码失效并破坏可移植性。反射代码打破了抽象层,并且因此可能会随着平台升级而改变行为"
以下代码片段演示了反射的基本概念
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()));
}
}
}
希望它能够有所帮助。
Java 9引入了变量句柄(Variable Handles)。您可以使用它们访问类的私有字段。
示例代码如下所示:
var lookup = MethodHandles.lookup();
var handle = MethodHandles
.privateLookupIn(IWasDesignedPoorly.class, lookup)
.findVarHandle(IWasDesignedPoorly.class, "stuffIWant", Hashtable.class);
var value = handle.get(obj);
建议将 Lookup
和 VarHandle
对象作为 static final
字段使用。
如果使用Spring:
在测试环境中,ReflectionTestUtils提供了一些方便的工具,可以在最小的努力下帮助解决问题。它被描述为"用于单元和集成测试场景"。
在非测试环境中,也有一个类似的类名为ReflectionUtils,但这被描述为"仅供内部使用" - 请参见this answer以获得对此含义的良好解释。
针对原始帖子中的示例进行如下处理:
Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(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);
}
getDeclaredField()
就找不到它 -- 你必须通过遍历父类层次结构并在每个类上调用getDeclaredField()
来查找匹配项(之后可以调用setAccessible(true)
),或者直到达到Object
。 - Luke Hutchison