532

我在第 3 方有一个设计不佳的课程,JAR我需要访问它的一个私有字段。例如,为什么我需要选择私有字段是否有必要?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

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

4

15 回答 15

719

为了访问私有字段,您需要从类的声明字段中获取它们,然后使其可访问:

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

编辑:正如aperkins所评论的那样,访问该字段,将其设置为可访问并检索该值都可以 throw Exceptions,尽管您需要注意的唯一检查异常在上面进行了评论。

NoSuchFieldException如果您要求一个名称与声明的字段不对应的字段,则会抛出该字段。

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

如果该字段不可访问(例如,如果它是私有的并且由于错过了该行IllegalAccessException而无法访问),则会抛出。f.setAccessible(true)

RuntimeException可能抛出的 s 是s SecurityException(如果 JVMSecurityManager不允许您更改字段的可访问性),或者IllegalArgumentException是 s,如果您尝试访问不是字段类类型的对象上的字段:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
于 2009-07-28T19:22:12.453 回答
182

FieldUtils从 apache commons-lang3尝试:

FieldUtils.readField(object, fieldName, true);
于 2014-04-25T07:40:48.510 回答
28

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

另一种解决方案是从 .jar 中提取类,使用(例如)JodeJad对其进行反编译,更改字段(或添加访问器),然后针对原始 .jar 重新编译它。然后将新的 .class 放在.jar类路径中的前面,或者将其重新插入到.jar. (jar 实用程序允许您提取并重新插入现有的 .jar)

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

当然,这需要.jar不签名。

于 2009-07-28T21:16:37.883 回答
18

另一个尚未提及的选项:使用Groovy。作为语言设计的副作用,Groovy 允许您访问私有实例变量。无论您是否有该领域的吸气剂,您都可以使用

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
于 2010-01-19T14:48:16.423 回答
10

Using the Reflection in Java you can access all the private/public fields and methods of one class to another .But as per the Oracle documentation in the section drawbacks they recommended that :

"Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform"

here is following code snapts to demonstrate basic concepts of Reflection

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

}

Hope it will help.

于 2014-09-24T06:54:12.597 回答
5

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

也就是说,如果这个类的设计如此糟糕以至于它让你诉诸这种骇客,也许你应该寻找一个替代品。当然,这个小技巧现在可能会为您节省几个小时,但它会花费您多少呢?

于 2009-07-28T19:36:58.430 回答
4

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

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

于 2010-01-19T14:42:51.860 回答
4

如果使用弹簧:

测试环境中ReflectionTestUtils提供了一些方便的工具,可以帮助您轻松完成任务。它被描述为“用于单元和集成测试场景”

非测试上下文中,还有一个名为ReflectionUtils的类似类,但这被描述为“仅供内部使用” - 请参阅此答案以更好地解释这意味着什么。

要解决原始帖子中的示例:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
于 2017-10-23T13:25:20.667 回答
3

您需要执行以下操作:

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);
}
于 2017-02-13T09:08:11.500 回答
2

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字段。

于 2021-12-01T15:25:37.637 回答
2

您可以为此使用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");
    }
}
于 2020-07-01T22:22:29.183 回答
1

您可以使用Manifold 的 @JailBreak进行直接的、类型安全的 Java 反射:

@JailBreak Foo foo = new Foo();
foo.stuffIWant = "123;

public class Foo {
    private String stuffIWant;
}

@JailBreak解锁foo编译器中的局部变量,以便直接访问Foo的层次结构中的所有成员。

同样,您可以将jailbreak()扩展方法用于一次性使用:

foo.jailbreak().stuffIWant = "123";

通过该jailbreak()方法,您可以访问Foo层次结构中的任何成员。

在这两种情况下,编译器都会安全地为您解析字段访问,就像一个公共字段一样,而 Manifold 会在后台为您生成高效的反射代码。

了解有关歧管的更多信息。

于 2018-12-09T04:22:33.967 回答
1

使用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());

在内部,这依赖于反射。

于 2019-11-27T12:39:53.563 回答
1

关于反射的附加说明:我观察到在一些特殊情况下,当不同包中存在多个具有相同名称的类时,最佳答案中使用的反射可能无法从对象中选择正确的类。因此,如果您知道对象的 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);

(这是对我不起作用的示例类)

于 2015-09-09T07:59:42.433 回答
-2

尝试解决该案例的问题,您要设置/获取数据的类是您自己的类之一。

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

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

当然,现在您必须在设置字段值之前找出java.lang.reflect.Fieldfor 。sString

我确实在通用的 ResultSet-to-and-from-model-mapper 中使用了这种技术。

于 2019-11-25T15:05:29.767 回答