web-dev-qa-db-fra.com

Accéder aux classes non visibles avec réflexion

J'essaie d'obtenir une instance d'une classe non visible, classe privée du paquetage AKA, à l'aide de la réflexion. Je me demandais s'il y avait un moyen de changer les modificateurs pour le rendre public, puis d'y accéder en utilisant Class.forName. Quand j'essaie maintenant, ça m'arrête de dire que je ne peux pas le faire. Malheureusement, il n'y a pas de méthode setAccesible de la classe Class.

37
Josh Sobel

nested class - classe définie dans une autre classe (inclut les classes statiques et non statiques)
classe interne - classe imbriquée non statique (l'instance de la classe interne a besoin que l'instance de la classe externe existe)

classes non imbriquées (niveau supérieur)

Sur la base de votre question, nous savons que le constructeur auquel vous souhaitez accéder n’est pas public. Donc, votre classe peut ressembler à ceci (la classe A est dans un paquet différent du nôtre)

package package1;

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

Pour créer une instance de cette classe, nous devons accéder au constructeur que nous voulons invoquer et la rendre accessible. Quand c'est fait, nous pouvons utiliser Constructor#newInstance(arguments) pour créer une instance.

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

classes imbriquées et internes

Si vous souhaitez accéder à une classe imbriquée (statique et non statique) avec Class.forName, vous devez utiliser la syntaxe:

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

Outer$Nested indique que la classe Nested est déclarée dans la classe Outer. Les classes imbriquées sont très similaires aux méthodes, elles ont accès à tous les membres de sa classe externe (y compris les classes privées). 

Mais nous devons nous rappeler que l'instance de la classe interne d'exister requiert l'instance de sa classe externe. Normalement nous les créons via:

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

ainsi, chaque instance de la classe Inner contient des informations sur sa classe externe (la référence à cette instance externe est stockée dans le champ this$0, plus d'infos: Qu'est-ce que cela signifie si une variable porte le nom "this $ 0" dans IntelliJ IDEA lors du débogage de Java? )

Ainsi, lors de la création d'une instance de Inner class avec Constructor#newInstance(), vous devez passer en tant que premier argument de référence à l'instance de Outer class (pour simuler le comportement de outer.new Inner()).

Voici un exemple.

dans package1

package package1;

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

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

    }
}

classes imbriquées statiques

Les instances de classes imbriquées statiques ne nécessitent pas l'instance de la classe Outer (car elles sont statiques). Donc, dans leur cas, nous n'avons pas besoin de rechercher le constructeur avec Outer.class comme premier argument. Et nous n'avons pas besoin de passer l'instance de la classe externe comme premier argument. En d'autres termes, le code sera le même que pour la classe non imbriquée (niveau supérieur) (peut-être sauf le fait que vous auriez besoin d'ajouter la syntaxe $Nested dans Class.forName()).

49
Pshemo

Class.forName devrait fonctionner. Si la classe se trouve dans une liste de hiérarchies de packages dans la propriété de sécurité "package.access", vous devez exécuter l'opération avec le privilège approprié (généralement toutes les autorisations; ou ne pas disposer d'un gestionnaire de sécurité).

Si vous essayez d'utiliser Class.newInstance, ne le faites pas. Class.newInstance gère mal les exceptions. Au lieu de cela, obtenez une Constructor et appelez newInstance à ce sujet. Il est difficile de voir avec quoi vous rencontrez des problèmes sans trace d'exception.

Comme toujours, la plupart des utilisations de la réflexion, mais pas toutes, sont de mauvaises idées.

3
Tom Hawtin - tackline

J'avais l'obligation de copier la valeur du champ de l'ancienne version de l'objet si la valeur est null dans la dernière version. Nous avons eu ces 2 options. 

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

Utilisation de 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"));
}
0
Quest_for_java

Vous pouvez utiliser Manifold@Jailbreak pour une réflexion Java directe et sécurisée au type:

Foo foo = new @Jailbreak Foo();

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

    private void yodog() {...}
}

Ici, @Jailbreak permet au compilateur de résoudre le constructeur de manière sécurisée comme s'il était public, tandis que Manifold génère un code de réflexion efficace pour vous sous le capot.

De plus, vous pouvez utiliser @Jailbreak pour accéder aux classes non visibles et les construire:

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

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

Pour l'accès caché aux classes, la grammaire d'annotation de Java exige que la classe soit annotée indépendamment de son package.

Plus généralement, vous pouvez utiliser @Jailbreak pour tout type de réflexion:

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

@Jailbreak déverrouille la variable locale foo du compilateur pour un accès direct à tous les membres de la hiérarchie Foo.

De même, vous pouvez utiliser la méthode d'extension jailbreak () pour une utilisation unique:

foo.jailbreak().yodog();

Avec la méthode jailbreak(), vous pouvez accéder à n’importe quel membre de la hiérarchie de Foo.

En savoir plus sur Collecteur .

0
Scott McKinney

Nous avons récemment publié une bibliothèque qui aide beaucoup à accéder à des champs privés, des méthodes et des classes internes par réflexion: BoundBox

Pour un cours comme 

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

Il fournit une syntaxe comme celle-ci: 

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

La seule chose à faire pour créer la classe BoundBox consiste à écrire @BoundBox(boundClass=Outer.class). La classe BoundBoxOfOuter sera générée instantanément.

0
Snicolas