Alternatives à java.lang.reflect.Proxy pour créer des proxies de classes abstraites (plutôt que des interfaces)

89

Selon la documentation :

[ java.lang.reflect.] Proxyfournit des méthodes statiques pour créer des classes et des instances proxy dynamiques, et c'est aussi la superclasse de toutes les classes proxy dynamiques créées par ces méthodes.

La newProxyMethodméthode (chargée de générer les proxies dynamiques) a la signature suivante:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
                             throws IllegalArgumentException

Malheureusement, cela empêche de générer un proxy dynamique qui étend une classe abstraite spécifique (plutôt que d' implémenter des interfaces spécifiques). Cela a du sens, étant donné que java.lang.reflect.Proxyc'est "la superclasse de tous les proxys dynamiques", empêchant ainsi une autre classe d'être la superclasse.

Par conséquent, existe-t-il des alternatives à java.lang.reflect.Proxycela qui peuvent générer des proxys dynamiques qui héritent d'une classe abstraite spécifique, redirigeant tous les appels vers les méthodes abstraites vers le gestionnaire d'invocation?

Par exemple, supposons que j'ai une classe abstraite Dog:

public abstract class Dog {

    public void bark() {
        System.out.println("Woof!");
    }

    public abstract void fetch();

}

Y a-t-il un cours qui me permet de faire ce qui suit?

Dog dog = SomeOtherProxy.newProxyInstance(classLoader, Dog.class, h);

dog.fetch(); // Will be handled by the invocation handler
dog.bark();  // Will NOT be handled by the invocation handler
Adam Paynter
la source

Réponses:

123

Cela peut être fait en utilisant Javassist (voir ProxyFactory) ou CGLIB .

Exemple d'Adam utilisant Javassist:

J'ai (Adam Paynter) écrit ce code en utilisant Javassist:

ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(Dog.class);
factory.setFilter(
    new MethodFilter() {
        @Override
        public boolean isHandled(Method method) {
            return Modifier.isAbstract(method.getModifiers());
        }
    }
);

MethodHandler handler = new MethodHandler() {
    @Override
    public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
        System.out.println("Handling " + thisMethod + " via the method handler");
        return null;
    }
};

Dog dog = (Dog) factory.create(new Class<?>[0], new Object[0], handler);
dog.bark();
dog.fetch();

Qui produit cette sortie:

Trame!
Gestion du résumé public void mock.Dog.fetch () via le gestionnaire de méthode
axtavt
la source
10
+1: Exactement ce dont j'ai besoin! Je modifierai votre réponse avec mon exemple de code.
Adam Paynter
proxyFactory.setHandler()est obsolète. Veuillez utiliser proxy.setHandler.
AlikElzin-kilaka
@axtavt est l'objet "Dog" une implémentation ou une interface dans le code ci-dessus?
stackoverflow
-7

Ce que vous pouvez faire dans un tel cas est d'avoir un gestionnaire de proxy qui redirigera les appels vers les méthodes existantes de votre classe abstraite.

Vous devrez bien sûr le coder, mais c'est assez simple. Pour créer votre proxy, vous devrez lui donner un fichier InvocationHandler. Vous n'aurez alors plus qu'à vérifier le type de méthode dans la invoke(..)méthode de votre gestionnaire d'appel. Mais attention: vous devrez vérifier le type de méthode par rapport à l'objet sous-jacent associé à votre gestionnaire, et non par rapport au type déclaré de votre classe abstraite.

Si je prends comme exemple votre classe dog, la méthode d'invocation de votre gestionnaire d'invocation peut ressembler à ceci (avec une sous-classe dog associée existante appelée .. eh bien ... dog)

public void invoke(Object proxy, Method method, Object[] args) {
    if(!Modifier.isAbstract(method.getModifiers())) {
        method.invoke(dog, args); // with the correct exception handling
    } else {
        // what can we do with abstract methods ?
    }
}

Cependant, il y a quelque chose qui m'interroge: j'ai parlé d'un dogobjet. Mais, comme la classe Dog est abstraite, vous ne pouvez pas créer d'instances, vous avez donc des sous-classes existantes. En outre, comme le révèle une inspection rigoureuse du code source du proxy, vous pouvez découvrir (à Proxy.java:362) qu'il n'est pas possible de créer un proxy pour un objet de classe qui ne représente pas une interface).

Donc, en dehors de la réalité , ce que vous voulez faire est parfaitement possible.

Riduidel
la source
1
Veuillez patienter pendant que j'essaie de comprendre votre réponse ... Dans mon cas particulier, je veux que la classe proxy (générée à l'exécution) soit la sous-classe de Dog(par exemple, je n'écris pas explicitement une Poodleclasse qui implémente fetch()). Par conséquent, il n'y a pas de dogvariable sur laquelle invoquer les méthodes ... Désolé si je suis confus, je devrai y réfléchir un peu plus.
Adam Paynter
1
@Adam - vous ne pouvez pas créer dynamiquement des sous-classes au moment de l'exécution sans une manipulation de bytecode (CGLib, je pense, fait quelque chose comme ça). La réponse courte est que les proxys dynamiques prennent en charge les interfaces, mais pas les classes abstraites, car les deux sont des concepts très très différents. Il est presque impossible de penser à un moyen de proxy dynamiquement des classes abstraites d'une manière saine.
Andrzej Doyle
1
@Andrzej: Je comprends que ce que je demande nécessite une manipulation de bytecode (en fait, j'ai déjà écrit une solution à mon problème en utilisant ASM). Je comprends également que les proxys dynamiques de Java ne prennent en charge que les interfaces. Ma question n'était peut-être pas tout à fait claire - je demande s'il y a une autre classe (c'est-à-dire autre chose que java.lang.reflect.Proxy) disponible qui fait ce dont j'ai besoin.
Adam Paynter le
2
Eh bien, pour faire de longues choses courtes ... non (du moins dans les classes Java standard). En utilisant la manipulation de bytecode, le ciel est la limite!
Riduidel
9
J'ai voté contre parce que ce n'est pas vraiment une réponse à la question. OP a déclaré qu'il voulait proxy une classe, pas une interface, et est conscient que ce n'est pas possible avec java.lang.reflect.Proxy. Vous répétez simplement ce fait et ne proposez aucune autre solution.
jcsahnwaldt Réintègre Monica