Я пытаюсь получить экземпляр невидимого класса, частного класса пакета AKA, используя отражение. Мне было интересно, есть ли способ переключить модификаторы, чтобы сделать его общедоступным, а затем получить доступ к нему с помощью Class.forName
. Когда я пытаюсь это сделать, это останавливает меня, говоря, что я не могу этого сделать. К сожалению, нет метода setAccesible
Class
.
Доступ к невидимым классам с отражением
Ответ 1
вложенный класс - класс, определенный в другом классе (включая статические и нестатические классы)
внутренний класс - нестатический вложенный класс (экземпляр внутреннего класса нужен экземпляр внешнего класса для существования)
не вложенные (верхний уровень) классы
На основании вашего вопроса мы знаем, что конструктор, к которому вы хотите получить доступ, не является общедоступным. Таким образом, ваш класс может выглядеть следующим образом ( класс в каком - то пакете отличается от нашего) 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
поле this$0
, больше информации: что это означает, если переменная имеет имя "this $ 0" в IntelliJ IDEA, тогда как отладка Java?)
Поэтому, создавая экземпляр класса Inner
с Constructor#newInstance()
вам нужно передать ссылку на первый аргумент в экземпляр класса Outer
(для моделирования outer.new Inner()
поведения 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
качестве первого аргумента. И нам не нужно передавать экземпляр внешнего класса в качестве первого аргумента. Другими словами, код будет таким же, как для не-вложенного (верхнего уровня) класса (возможно, кроме факта, что вам нужно будет добавить синтаксис $Nested
в Class.forName()
).
Ответ 2
Class.forName
должен работать. Если класс находится в списке иерархии пакетов в свойстве безопасности "package.access"
, вам необходимо выполнить операцию с соответствующей привилегией (обычно это все разрешения или нет менеджера безопасности).
Если вы пытаетесь использовать Class.newInstance
, не делайте этого. Class.newInstance
плохо обрабатывает исключения. Вместо этого создайте Constructor
и вызовите newInstance
. Трудно понять, с чем вы столкнулись, без следа исключений.
Как и прежде, большинство, но не все использование рефлексии - плохие идеи.
Ответ 3
Недавно мы выпустили библиотеку, которая помогает получить доступ к закрытым полям, методам и внутренним классам через отражение: 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
будет мгновенно сгенерирован.
Ответ 4
У меня было требование скопировать значение поля из старой версии объекта, если в последней версии значение 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"));
}
Ответ 5
Вы можете использовать @Jailbreak от Manifold для прямого, типобезопасного отражения 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
.
Узнайте больше о коллектор.