我正在尝试使用反射来获取不可见类,AKA包私有类的实例。我想知道是否有办法切换修饰符使其公开,然后使用Class.forName
访问它。当我尝试这样做时,它阻止我说我不能这样做。遗憾的是,setAccesible
类没有Class
方法。
答案 0 :(得分:54)
嵌套类 - 在其他类中定义的类(包括静态类和非静态类)
内部类 - 非静态嵌套类(内部类的实例需要存在的外部类的实例)
根据您的问题,我们知道您要访问的构造函数不是公共的。所以你的类可能看起来像这样(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$Nested
表示Nested
类在Outer
类中声明。嵌套类与方法非常相似,它们可以访问其外部类的所有成员(包括私有类)。
但我们需要记住,存在的内部类的实例需要其外部类的实例。通常我们通过以下方式创建它们:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
因此,您可以看到Inner类的每个实例都有关于其外部类的一些信息(对该外部实例的引用存储在this$0
字段中,更多信息:What does it mean if a variable has the name "this$0" in IntelliJ IDEA while debugging Java?)
因此,在使用Inner
创建Constructor#newInstance()
类的实例时,您需要将Outer
类的实例作为第一个参数引用传递(以模拟outer.new Inner()
行为)。
这是一个例子。
in package1
package package1;
public class Outer {
class Inner{
Inner(){
System.out.println("non-public constructor of inner class");
}
}
}
in package2
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
作为第一个参数的构造函数。而且我们不需要将外部类的实例作为第一个参数传递。换句话说,代码将与非嵌套(顶级)类相同(可能除了需要在$Nested
中添加Class.forName()
语法之外的事实。)
答案 1 :(得分:3)
Class.forName
应该有效。如果该类位于"package.access"
安全属性中的包层次结构列表中,那么您将需要使用适当的权限(通常是所有权限;或者没有安全管理器)执行操作。
如果您尝试使用Class.newInstance
,请不要。 Class.newInstance
处理异常的情况很糟糕。而是获得Constructor
并在其上调用newInstance
。没有异常跟踪,很难看出你遇到了什么问题。
与以往一样,大多数但并非所有反射都是坏主意。
答案 2 :(得分:0)
我们最近发布了一个库,通过反射帮助很多人访问私有字段,方法和内部类: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
类。
答案 3 :(得分:0)
如果最新版本的值为null,我需要从旧版本的对象复制字段的值。我们有这两个选项。
核心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"));
}
答案 4 :(得分:0)
您可以使用Manifold's @Jailbreak 进行直接的类型安全的Java反射:
Foo foo = new @Jailbreak Foo();
public class Foo {
Foo() {...}
private void yodog() {...}
}
在这里@Jailbreak
使编译器能够像公共类型一样安全地解析构造函数,而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
层次结构中的所有成员。
类似地,您可以将 jailbreak()扩展方法用于一次性使用:
foo.jailbreak().yodog();
通过jailbreak()
方法,您可以访问Foo
层次结构中的任何成员。
了解有关Manifold的更多信息。
答案 5 :(得分: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);
}