简体   繁体   English

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

[英]How to read the value of a private field from a different class in Java?

I have a poorly designed class in a 3rd-party JAR and I need to access one of its private fields.我在第 3 方JAR中有一个设计不佳的类,我需要访问它的一个私有字段。 For example, why should I need to choose private field is it necessary?例如,为什么我需要选择私有字段是否有必要?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

How can I use reflection to get the value of stuffIWant ?如何使用反射来获取stuffIWant的值?

In order to access private fields, you need to get them from the class's declared fields and then make them accessible:为了访问私有字段,您需要从类的声明字段中获取它们,然后使其可访问:

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

EDIT : as has been commented by aperkins , both accessing the field, setting it as accessible and retrieving the value can throw Exception s, although the only checked exceptions you need to be mindful of are commented above.编辑:正如aperkins所评论的那样,访问该字段,将其设置为可访问并检索该值都可以抛出Exception s,尽管您需要注意的唯一检查异常在上面进行了评论。

The NoSuchFieldException would be thrown if you asked for a field by a name which did not correspond to a declared field.如果您请求的字段名称与声明的字段不对应,则会引发NoSuchFieldException

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

The IllegalAccessException would be thrown if the field was not accessible (for example, if it is private and has not been made accessible via missing out the f.setAccessible(true) line.如果该字段不可访问(例如,如果它是私有的并且由于错过f.setAccessible(true)行而无法访问),则会抛出IllegalAccessException

The RuntimeException s which may be thrown are either SecurityException s (if the JVM's SecurityManager will not allow you to change a field's accessibility), or IllegalArgumentException s, if you try and access the field on an object not of the field's class's type:可能抛出的RuntimeExceptionSecurityException (如果 JVM 的SecurityManager不允许您更改字段的可访问性)或IllegalArgumentException ,如果您尝试访问不是字段类类型的对象上的字段:

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

尝试来自 apache commons- FieldUtils的 FieldUtils:

FieldUtils.readField(object, fieldName, true);

Reflection isn't the only way to resolve your issue (which is to access the private functionality/behaviour of a class/component)反射不是解决问题的唯一方法(即访问类/组件的私有功能/行为)

An alternative solution is to extract the class from the .jar, decompile it using (say) Jode or Jad , change the field (or add an accessor), and recompile it against the original .jar.另一种解决方案是从 .jar 中提取类,使用(比如) JodeJad对其进行反编译,更改字段(或添加访问器),然后针对原始 .jar 重新编译它。 Then put the new .class ahead of the .jar in the classpath, or reinsert it in the .jar .然后将新的 .class 放在类路径中的.jar之前,或者将其重新插入到.jar中。 (the jar utility allows you to extract and reinsert to an existing .jar) (jar 实用程序允许您提取并重新插入现有的 .jar)

As noted below, this resolves the wider issue of accessing/changing private state rather than simply accessing/changing a field.如下所述,这解决了访问/更改私有状态而不是简单地访问/更改字段的更广泛问题。

This requires the .jar not to be signed, of course.当然,这要求.jar不被签名。

One other option that hasn't been mentioned yet: use Groovy .另一个尚未提及的选项:使用Groovy Groovy allows you to access private instance variables as a side effect of the design of the language. Groovy 允许您访问私有实例变量作为语言设计的副作用。 Whether or not you have a getter for the field, you can just use无论您是否有该领域的吸气剂,您都可以使用

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

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 :使用Java 中的反射,您可以将一个类的所有private/public字段和方法访问到另一个类。但是根据Oracle文档中的缺陷部分,他们建议:

"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反射1.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反射2.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.希望它会有所帮助。

As oxbow_lakes mentions, you can use reflection to get around the access restrictions (assuming your SecurityManager will let you).正如 oxbow_lakes 提到的,您可以使用反射来绕过访问限制(假设您的 SecurityManager 会允许您)。

That said, if this class is so badly designed that it makes you resort to such hackery, maybe you should look for an alternative.也就是说,如果这个类的设计如此糟糕以至于它让你诉诸这种骇客,也许你应该寻找一个替代品。 Sure this little hack might be saving you a few hours now, but how much will it cost you down the road?当然,这个小技巧现在可能会为您节省几个小时,但它会花费您多少呢?

Use the Soot Java Optimization framework to directly modify the bytecode.使用 Soot Java 优化框架直接修改字节码。 http://www.sable.mcgill.ca/soot/ http://www.sable.mcgill.ca/soot/

Soot is completely written in Java and works with new Java versions. Soot 完全用 Java 编写,并且适用于新的 Java 版本。

If using Spring:如果使用弹簧:

In a testing context , ReflectionTestUtils provides some handy tools that can help out here with minimal effort.测试环境中ReflectionTestUtils提供了一些方便的工具,可以帮助您轻松完成任务。 It's described as being "for use in unit and integration testing scenarios" .它被描述为“用于单元和集成测试场景”

In a non-testing context , there is also a similar class named ReflectionUtils but this is described as "Only intended for internal use" - see this answer for a good interpretation of what this means.非测试上下文中,还有一个名为ReflectionUtils的类似类,但这被描述为“仅供内部使用” - 请参阅此答案以更好地解释这意味着什么。

To address the example in the original post:要解决原始帖子中的示例:

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

You need to do the following:您需要执行以下操作:

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

Java 9 introduced Variable Handles . Java 9 引入了变量句柄 You can access a private field of a class using them.您可以使用它们访问类的私有字段。

The code for your example will look like following:您的示例代码如下所示:

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

It is also advisable to use Lookup and VarHandle objects as static final fields.还建议使用LookupVarHandle对象作为static final字段。

You can use jOOR for that.您可以为此使用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");
    }
}

Just an additional note about reflection: I have observed in some special cases, when several classes with the same name exist in different packages, that reflection as used in the top answer may fail to pick the correct class from the object.关于反射的附加说明:我观察到在一些特殊情况下,当不同包中存在多个具有相同名称的类时,最佳答案中使用的反射可能无法从对象中选择正确的类。 So if you know what is the package.class of the object, then it's better to access its private field values as follows:因此,如果您知道对象的 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);

(This is the example class that was not working for me) (这是对我不起作用的示例类)

You can use Manifold's @JailBreak for direct, type-safe Java reflection:您可以使用Manifold 的@JailBreak进行直接的、类型安全的 Java 反射:

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

public class Foo {
    private String stuffIWant;
}

@JailBreak unlocks the foo local variable in the compiler for direct access to all the members in Foo 's hierarchy. @JailBreak在编译器中解锁foo局部变量,以便直接访问Foo层次结构中的所有成员。

Similarly you can use the jailbreak() extension method for one-off use:同样,您可以使用jailbreak()扩展方法进行一次性使用:

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

Through the jailbreak() method you can access any member in Foo 's hierarchy.通过jailbreak()方法,您可以访问Foo层次结构中的任何成员。

In both cases the compiler resolves the field access for you type-safely, as if a public field, while Manifold generates efficient reflection code for you under the hood.在这两种情况下,编译器都会以类型安全的方式为您解析字段访问,就像公共字段一样,而 Manifold 会在幕后为您生成高效的反射代码。

Discover more about Manifold .了解更多关于歧管的信息

It is quite easy with the tool XrayInterface .使用XrayInterface工具非常容易。 Just define the missing getters/setters, eg只需定义缺少的 getter/setter,例如

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

and xray your poor designed project:并对你设计不佳的项目进行 X 光检查:

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

Internally this relies on reflection.在内部,这依赖于反射。

Try to go around the problem for the case, the calass of which you want to set/get data is one of your own classes.尝试解决该案例的问题,您要设置/获取数据的类是您自己的类之一。

Just create a public setter(Field f, Object value) and public Object getter(Field f) for that.只需为此创建一个public setter(Field f, Object value)public Object getter(Field f) You can even do some securoty check on your own inside theses member functions.您甚至可以在这些成员函数中自己进行一些安全检查。 Eg for the 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);
    }
}

Of course, now you have to find out the java.lang.reflect.Field for sString prior to setting of field value.当然,现在您必须在设置字段值之前找出sStringjava.lang.reflect.Field

I do use this technique in a generic ResultSet-to-and-from-model-mapper.我确实在通用的 ResultSet-to-and-from-model-mapper 中使用了这种技术。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM