61

我正在尝试使用反射获取不可见类的实例,即 AKA 包私有类。我想知道是否有办法切换修饰符以使其公开,然后使用Class.forName. 当我现在尝试这样做时,它会阻止我说我做不到。不幸的是,没有该类setAccesible的方法Class

4

6 回答 6

72

嵌套类- 在其他类中定义的类(包括静态和非静态类)
内部类- 非静态嵌套类(内部类的实例需要外部类的实例存在)

##非嵌套(顶级)类

根据您的问题,我们知道您要访问的构造函数不是公开的。所以你的班级可能看起来像这样(A班级在一些与我们不同的包中)

package package1;

public class A {
    A(){
        System.out.println("this is non-public constructor");
    }
}

要创建此类的实例,我们需要访问要调用的构造函数,并使其可访问。完成后,我们可以使用Constructor#newInstance(arguments)创建实例。

Class<?> c = Class.forName("package1.A");
//full package name --------^^^^^^^^^^
//or simpler without Class.forName:
//Class<package1.A> c = package1.A.class;

//In our case we need to use
Constructor<?> constructor = c.getDeclaredConstructor();
//note: getConstructor() can return only public constructors
//so we needed to search for any Declared constructor

//now we need to make this constructor accessible
constructor.setAccessible(true);//ABRACADABRA!

Object o = constructor.newInstance();

##嵌套和内部类

如果要访问嵌套(静态和非静态)类,Class.forName则需要使用语法:

Class<?> clazz = Class.forName("package1.Outer$Nested");

Outer$NestedNested类是在类中声明的Outer。嵌套类与方法非常相似,它们可以访问其外部类的所有成员(包括私有成员)。

但是我们需要记住,内部类的实例存在需要其外部类的实例。通常我们通过以下方式创建它们:

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();

因此,当您看到内部类的每个实例都有关于其外部类的一些信息时(对该外部实例的引用存储在this$0字段中,更多信息:在调试 Java 时,如果一个变量在 IntelliJ IDEA 中具有名称“this$0”,这意味着什么? ? )

因此,在创建Inner类实例时,Constructor#newInstance()您需要将第一个参数引用作为Outer类实例传递(以模拟outer.new Inner()行为)。

这是一个例子。

在包1

package package1;

public class Outer {
    class Inner{
        Inner(){
            System.out.println("non-public constructor of inner class");
        }
    }
}

在包 2

package package2;

import package1.Outer;
import java.lang.reflect.Constructor;

public class Test {
    public static void main(String[] args) throws Exception {

        Outer outerObject = new Outer();

        Class<?> innerClazz = Class.forName("package1.Outer$Inner");

        // constructor of inner class as first argument need instance of
        // Outer class, so we need to select such constructor
        Constructor<?> constructor = innerClazz.getDeclaredConstructor(Outer.class);

        //we need to make constructor accessible 
        constructor.setAccessible(true);

        //and pass instance of Outer class as first argument
        Object o = constructor.newInstance(outerObject);
        
        System.out.println("we created object of class: "+o.getClass().getName());

    }
}

##静态嵌套类

静态嵌套类的实例不需要 Outer 类的实例(因为它们是静态的)。所以在他们的情况下,我们不需要寻找构造函数Outer.class作为第一个参数。而且我们不需要将外部类的实例作为第一个参数传递。换句话说,代码将与非嵌套(顶级)类的代码相同(也许除了在引用嵌套类时需要添加 use$而不是.在类名中的事实Class.forName()like Class.forName("some.package.Outer$Nested1$NestedNested"))。

于 2013-02-22T02:28:21.787 回答
3

Class.forName应该管用。如果该类位于"package.access"安全属性中的包层次结构列表中,那么您将需要使用适当的权限(通常是所有权限;或者没有安全管理器)执行操作。

如果您尝试使用Class.newInstance,请不要。Class.newInstance处理异常很差。而是得到一个Constructor并调用newInstance它。如果没有异常跟踪,很难看出您遇到了什么问题。

与以往一样,大多数但不是所有反射的使用都是坏主意。

于 2013-02-22T02:15:03.037 回答
1

我们最近发布了一个库,它对通过反射访问私有字段、方法和内部类有很大帮助:BoundBox

对于像这样的课程

public class Outer {
    private static class Inner {
        private int foo() {return 2;}
    }
}

它提供了如下语法:

Outer outer = new Outer();
Object inner = BoundBoxOfOuter.boundBox_new_Inner();
new BoundBoxOfOuter.BoundBoxOfInner(inner).foo();

要创建 BoundBox 类,您唯一要做的就是编写@BoundBox(boundClass=Outer.class)BoundBoxOfOuter该类将立即生成。

于 2013-09-26T05:03:33.370 回答
1

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

Foo foo = new @Jailbreak Foo();

public class Foo {
    Foo() {...}

    private void yodog() {...}
}

这里@Jailbreak使编译器能够像 public 一样安全地解析构造函数,而 Manifold 在后台为您生成高效的反射代码。

此外,您可以@Jailbreak用来访问和构造不可见的类:

com.abc. @Jailbreak Bar bar = new com.abc. @Jailbreak Bar();

package com.abc;
// package-private class
class Bar {
    Bar() {...}
}

对于隐藏类访问,Java 的注解语法要求将类与其包分开注解。

更一般地,您可以@Jailbreak用于任何类型的反射:

@Jailbreak Foo foo = new Foo();
foo.yodog();

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

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

foo.jailbreak().yodog();

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

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

于 2018-12-09T04:16:51.177 回答
0

如果最新版本中的值为空,我需要从旧版本的对象中复制字段的值。我们有这两个选项。

核心Java:

for (Field f : object.getClass().getSuperclass().getDeclaredFields()) {
    f.setAccessible(true);
  System.out.println(f.getName());
  if (f.get(object) == null){
    f.set(object, f.get(oldObject));
  }
}

使用 Spring [org.springframework.beans.BeanWrapper]:

BeanWrapper bw = new BeanWrapperImpl(object);
PropertyDescriptor[] data = bw.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : data) {
  System.out.println(propertyDescriptor.getName());
  Object propertyValue = bw.getPropertyValue(propertyDescriptor.getName());
  if(propertyValue == null )
    bw.setPropertyValue( new PropertyValue(propertyDescriptor.getName(),"newValue"));
}
于 2014-11-26T07:53:02.583 回答
0

不得不在Android上做类似的事情是我想出的:

/**
 * To fix issues with wrong edge-to-edge bottom sheet top padding and status bar icon color…
 * …we need to call [BottomSheetDialog.EdgeToEdgeCallback.setPaddingForPosition] which is a private function from a private class.
 * See: https://github.com/material-components/material-components-android/issues/2165
 */
fun adjustBottomSheet(aDialog : BottomSheetDialog) {
    // Get our private class
    val classEdgeToEdgeCallback = Class.forName("com.google.android.material.bottomsheet.BottomSheetDialog\$EdgeToEdgeCallback")
    // Get our private method
    val methodSetPaddingForPosition: Method = classEdgeToEdgeCallback.getDeclaredMethod("setPaddingForPosition", View::class.java)
    methodSetPaddingForPosition.isAccessible = true
    // Get private field containing our EdgeToEdgeCallback instance
    val fieldEdgeToEdgeCallback = BottomSheetDialog::class.java.getDeclaredField("edgeToEdgeCallback")
    fieldEdgeToEdgeCallback.isAccessible = true
    // Get our bottom sheet view field
    val fieldBottomField = BottomSheetDialog::class.java.getDeclaredField("bottomSheet")
    fieldBottomField.isAccessible = true
    // Eventually call setPaddingForPosition from EdgeToEdgeCallback instance passing bottom sheet view as parameter
    methodSetPaddingForPosition.invoke(fieldEdgeToEdgeCallback.get(aDialog),fieldBottomField.get(aDialog))
}

此外,您可能希望确保 proguard 不会丢弃您尝试访问的方法:

# Needed for now to fix our bottom sheet issue from 
com.google.android.material:material:1.4.0-alpha02
-keep class com.google.android.material.bottomsheet.BottomSheetDialog$EdgeToEdgeCallback {
    private void setPaddingForPosition(android.view.View);
}
于 2021-04-09T09:57:13.167 回答