web-dev-qa-db-fra.com

Délégation automatique de toutes les méthodes d'une classe Java

Disons que j'ai une classe avec beaucoup de méthodes publiques:

public class MyClass {

    public void method1() {}
    public void method2() {}
    (...)
    public void methodN() {}

}

Maintenant, j'aimerais créer une classe wrapper qui déléguerait toutes les méthodes à une instance encapsulée (delegate):

public class WrapperClass extends MyClass  {
    private final MyClass delegate;

    public WrapperClass(MyClass delegate) {
        this.delagate = delegate;
    }

    public void method1() { delegate.method1(); }
    public void method2() { delegate.method2(); }
    (...)
    public void methodN() { delegate.methodN(); }

}

Maintenant, si MyClass a beaucoup de méthodes, il me faudrait redéfinir chacune d’elles, ce qui correspond plus ou moins au même code qui ne fait que "déléguer". Je me demandais s’il était possible de faire de la magie pour appeler automatiquement une méthode en Java (la classe Wrapper aurait donc besoin de dire: "Hé si vous appelez une méthode sur moi, allez simplement à l’objet delegate et appelez cette méthode le il).

BTW: Je ne peux pas utiliser l'héritage parce que le délégué n'est pas sous mon contrôle. Je viens d'obtenir son instance d'ailleurs (un autre cas serait si MyClass était final). 

REMARQUE: je ne souhaite pas la génération IDE. Je sais que je peux le faire avec l’aide d’IntelliJ/Eclipse, mais je suis curieux de savoir si cela peut être fait en code.

Des suggestions comment réaliser quelque chose comme ceci? (NOTE: je serais probablement capable de le faire dans certains langages de script tels que php où je pourrais utiliser des fonctions magiques php pour intercepter l'appel).

19
walkeros

Peut-être que la dynamique Proxy de Java peut vous aider. Cela ne fonctionne que si vous utilisez par conséquent des interfaces. Dans ce cas, je vais appeler l'interface MyInterface et configurer une implémentation par défaut:

public class MyClass implements MyInterface {

    @Override
    public void method1() {
        System.out.println("foo1");
    }

    @Override
    public void method2() {
        System.out.println("foo2");
    }

    @Override
    public void methodN() {
        System.out.println("fooN");
    }

    public static void main(String[] args) {
        MyClass wrapped = new MyClass();
        wrapped.method1();
        wrapped.method2();
        MyInterface wrapper = WrapperClass.wrap(wrapped);
        wrapper.method1();
        wrapper.method2();
    }

}

L'implémentation de la classe wrapper ressemblerait à ceci:

public class WrapperClass extends MyClass implements MyInterface, InvocationHandler {

    private final MyClass delegate;

    public WrapperClass(MyClass delegate) {
        this.delegate = delegate;
    }

    public static MyInterface wrap(MyClass wrapped) {
        return (MyInterface) Proxy.newProxyInstance(MyClass.class.getClassLoader(), new Class[] { MyInterface.class }, new WrapperClass(wrapped));
    }

    //you may skip this definition, it is only for demonstration
    public void method1() {
        System.out.println("bar");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Method m = findMethod(this.getClass(), method);
        if (m != null) {
            return m.invoke(this, args);
        }
        m = findMethod(delegate.getClass(), method);
        if (m != null) {
            return m.invoke(delegate, args);
        }
        return null;
    }

    private Method findMethod(Class<?> clazz, Method method) throws Throwable {
        try {
            return clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

}

Notez que cette classe:

  • étend MyClass, pour hériter d'une implémentation par défaut (toute autre solution conviendrait)
  • implémente Invocationhandler, pour permettre au mandataire de faire la réflexion
  • implémenter optionnellement MyInterface (pour satisfaire le modèle de décorateur)

Cette solution vous permet de remplacer des méthodes spéciales, mais de déléguer toutes les autres. Cela fonctionnera même avec les sous-classes de la classe Wrapper.

Notez que la méthode findMethod ne capture pas encore les cas spéciaux. 

20
CoronA

Cette question a déjà 6 mois et la réponse merveilleuse de @CoronA a été acceptée et acceptée par @walkeros, mais j'ai pensé ajouter quelque chose ici, car je pense que cela peut être poussé d'un pas supplémentaire.

Comme discuté avec @CoronA dans les commentaires de sa réponse, au lieu de devoir créer et gérer une longue liste de méthodes MyClass dans WrapperClass (c'est-à-dire public void methodN() { delegate.methodN(); }), la solution de proxy dynamique le transfère vers l'interface. Le problème est que vous devez toujours créer et gérer une longue liste de signatures pour les méthodes MyClass dans l'interface, ce qui est peut-être un peu plus simple mais ne résout pas complètement le problème. C'est particulièrement le cas si vous n'avez pas accès à MyClass afin de connaître toutes les méthodes.

Selon Trois approches pour décorer votre code }, 

Pour les classes plus longues, un programmeur doit choisir le moindre de deux maux: implémenter de nombreuses méthodes wrapper et conserver le type d'objet décoré ou maintenir une implémentation simple de décorateur et sacrifier en conservant le type d'objet décoré.

C'est peut-être une limitation attendue du motif de décorateur.

@ Mark-Bramnik, cependant, donne un solution fascinante utilisant CGLIB sur interposition sur des méthodes de classe Java (sans interfaces) . J'ai pu combiner cela avec la solution de @ CoronaA afin de créer un wrapper capable de remplacer des méthodes individuelles, puis de passer le reste à l'objet encapsulé sans que ne nécessite une interface.

Voici MyClass.

public class MyClass {

    public void method1() { System.out.println("This is method 1 - " + this); } 
    public void method2() { System.out.println("This is method 2 - " + this); } 
    public void method3() { System.out.println("This is method 3 - " + this); } 
    public void methodN() { System.out.println("This is method N - " + this); }

}

Voici WrapperClass qui remplace uniquement method2(). Comme vous le verrez ci-dessous, les méthodes non substituées ne sont en fait pas transmises au délégué, ce qui peut poser problème.

public class WrapperClass extends MyClass {

    private MyClass delagate;

    public WrapperClass(MyClass delegate) { this.delagate = delegate; }

    @Override
    public void method2() {
        System.out.println("This is overridden method 2 - " + delagate);
    }

}

Voici MyInterceptor qui étend MyClass. Il utilise la solution proxy utilisant CGLIB comme décrit par @ Mark-Bramnik. Il utilise également la méthode de @ CononA pour déterminer s'il convient d'envoyer la méthode au wrapper (si elle est surchargée) ou à l'objet encapsulé (si ce n'est pas le cas).

import Java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MyInterceptor extends MyClass implements MethodInterceptor {

    private Object realObj;

    public MyInterceptor(Object obj) { this.realObj = obj; }

    @Override
    public void method2() {
        System.out.println("This is overridden method 2 - " + realObj);
    }

    @Override
    public Object intercept(Object arg0, Method method, Object[] objects,
            MethodProxy methodProxy) throws Throwable {
        Method m = findMethod(this.getClass(), method);
        if (m != null) { return m.invoke(this, objects); }
        Object res = method.invoke(realObj, objects);
        return res;
    }

    private Method findMethod(Class<?> clazz, Method method) throws Throwable {
        try {
            return clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

}

Voici Main et les résultats que vous obtenez si vous l'exécutez.

import net.sf.cglib.proxy.Enhancer;

public class Main {

    private static MyClass unwrapped;
    private static WrapperClass wrapped;
    private static MyClass proxified;

    public static void main(String[] args) {
        unwrapped = new MyClass();
        System.out.println(">>> Methods from the unwrapped object:");
        unwrapped.method1();
        unwrapped.method2();
        unwrapped.method3();
        wrapped = new WrapperClass(unwrapped);
        System.out.println(">>> Methods from the wrapped object:");
        wrapped.method1();
        wrapped.method2();
        wrapped.method3();
        proxified = createProxy(unwrapped);
        System.out.println(">>> Methods from the proxy object:");
        proxified.method1();
        proxified.method2();
        proxified.method3();
    }

    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T obj) {
        Enhancer e = new Enhancer();
        e.setSuperclass(obj.getClass());
        e.setCallback(new MyInterceptor(obj));
        T proxifiedObj = (T) e.create();
        return proxifiedObj;
    }

}

>>> Methods from the unwrapped object:
This is method 1 - MyClass@e26db62
This is method 2 - MyClass@e26db62
This is method 3 - MyClass@e26db62

>>> Methods from the wrapped object:
This is method 1 - WrapperClass@7b7035c6
This is overridden method 2 - MyClass@e26db62
This is method 3 - WrapperClass@7b7035c6

>>> Methods from the proxy object:
This is method 1 - MyClass@e26db62
This is overridden method 2 - MyClass@e26db62
This is method 3 - MyClass@e26db62

Comme vous pouvez le constater, lorsque vous exécutez les méthodes sur wrapped, vous obtenez le wrapper pour les méthodes qui ne sont pas remplacées (c'est-à-dire method1() et method3()). Lorsque vous exécutez les méthodes sur proxified, toutefois, toutes les méthodes sont exécutées sur l'objet enveloppé sans la peine d'avoir à les déléguer toutes dans WrapperClass ou à placer toutes les signatures de méthode dans une interface. Merci à @CoronA et à @ Mark-Bramnik pour ce qui semble être une solution plutôt cool à ce problème.

5
Mark Cramer

Passez à Groovy :-) 

@CompileStatic
public class WrapperClass extends MyClass  {
    @Delegate private final MyClass delegate;

    public WrapperClass(MyClass delegate) {
        this.delagate = delegate;
    }

    //Done. That's it.

}

http://mrhaki.blogspot.com/2009/08/groovy-goodness-delegate-to-simplify.html

4
Snekse

Vérifiez l'annotation @Delegation du framework Lombok: https://projectlombok.org/features/Delegate.html

2
Vanessa Schissato

Vous n'êtes pas obligé de faire cela - votre classe Wrapper est une sous-classe de la classe d'origine. Elle hérite donc de all de ses méthodes accessibles au public - et si vous ne les implémentez pas, la méthode d'origine sera appelé.

Vous ne devriez pas avoir extends Myclass avec un objet privé MyClass - c'est vraiment très redondant, et je ne peux pas penser à un modèle de conception où il est correct de le faire. Votre WrapperClass est a MyClass et vous pouvez donc utiliser ses propres champs et méthodes au lieu d'appeler delegate.

EDIT: Dans le cas où MyClass étant final, vous contournez la déclaration willfull pour ne pas permettre le sous-classement par "simuler" l'héritage; Je ne vois personne qui veuille faire cela, à part vous, qui contrôle WrapperClass; mais, puisque vous contrôlez WrapperClass, ne pas emballer tout ce dont vous n’avez pas besoin est vraiment plus qu’une option - c’est la bonne chose à faire, car votre objet n’est pas a MyClass et ne doit se comporter comme un dans les cas que vous avez considérés mentalement.

EDITvous venez de changer votre question pour qu'elle veuille dire quelque chose de complètement différent en supprimant la superclasse MyClass de votre WrapperClass; c'est un peu dommage, car cela invalide toutes les réponses données jusqu'à présent. Vous auriez dû ouvrir une autre question.

1
Marcus Müller

Définissez une méthode dans WrapperClass c'est-à-dire delegate() qui renvoie l'instance de MyClass

OR

Vous pouvez utiliser la réflexion pour le faire, mais l'appelant doit transmettre le nom de la méthode en tant qu'argument à une méthode exposée. Et il y aura des complications concernant les arguments de méthode/méthodes surchargées, etc.

BTW: Je ne peux pas utiliser l'héritage parce que le délégué n'est pas sous mon contrôle. Je viens d'obtenir son instance d'ailleurs (un autre cas serait si MyClass était final)

Le code que vous avez posté a public class WrapperClass extends MyClass 

En fait, votre implémentation actuelle de WrapperClass est en fait un décorateur au-dessus de MyClass. 

0
Nitin Dandriyal

Les crédits vont à CoronA pour avoir souligné les classes Proxy et InvocationHandler. J'ai élaboré une classe utilitaire plus réutilisable basée sur sa solution, utilisant des génériques:

public class DelegationUtils {

    public static <I> I wrap(Class<I> iface, I wrapped) {
        return wrapInternally(iface, wrapped, new SimpleDecorator(wrapped));
    }

    private static <I> I wrapInternally (Class<I> iface, I wrapped, InvocationHandler handler) {
        return (I) Proxy.newProxyInstance(wrapped.getClass().getClassLoader(), new Class[] { iface }, handler);
    }

    private static class SimpleDecorator<T> implements InvocationHandler {

        private final T delegate;

        private SimpleDecorator(T delegate) {
            this.delegate = delegate;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Method m = findMethod(delegate.getClass(), method);
            if (m == null) {
                throw new NullPointerException("Found no method " + method + " in delegate: " + delegate);
            }
            return m.invoke(delegate, args);
        }
    }    

    private static Method findMethod(Class<?> clazz, Method method) throws Throwable {
        try {
            return clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            return null;
        }
    }
}

Essaye-le:

public class Test {

    public  interface Test {
        public void sayHello ();
    }

    public static class TestImpl implements Test {
        @Override
        public void sayHello() {
            System.out.println("HELLO!");
        }
    }

    public static void main(String[] args) {
        Test proxy = DelegationUtils.wrap(Test.class, new TestImpl());
        proxy.sayHello();
    }
}

Je voulais créer une classe de délégation automatique qui exécute les méthodes du délégataire sur l'EDT. Avec cette classe, vous créez simplement une nouvelle méthode utilitaire qui utilisera un EDTDecorator, dans lequel l'implémentation encapsulera m.invoke dans un SwingUtilities.invokeLater.

Toutefois, si je réfléchis à cela, je souhaiterais peut-être reconsidérer la possibilité de créer un proxy non basé sur Reflection par interface que j'ai - il pourrait être plus propre, plus rapide et plus compréhensible. Mais c'est possible.

0
Timmos

Permettez-moi de redéfinir le problème pour un cas particulier. Je veux remplacer la méthode de fermeture de l'interface ResultSet dans jdbc. Mon objectif est de fermer l’instruction préparée dans la méthode d’ensemble des résultats. Je ne pouvais pas accéder à la classe (DelegatingResultSet) implémentée dans l'interface ResultSet. Il existe de nombreuses méthodes dans l’interface ResultSet et il est préférable de les remplacer une par une et l’appel de la méthode correspondante à partir de l’objet ResultSet est une solution. Pour une solution dynamique, j'ai utilisé Dynamic ProxyClasses ( https://docs.Oracle.com/javase/1.5.0/docs/guide/reflection/proxy.html ).

    // New ResultSet implementation
    public class MyResultSet implements InvocationHandler {
        ResultSet rs;
        PreparedStatement ps;
        private Method closeMethod;

        public MyResultSet(ResultSet rs, PreparedStatement ps) {
            super();
            this.rs = rs;
            this.ps = ps;
            try {
                closeMethod = ResultSet.class.getMethod("close",null);
            } catch (NoSuchMethodException | SecurityException e) {
                e.printStackTrace();
            }
        }

        public void close() {
            try {
                rs.close();
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }

        public static Object newInstance(ResultSet rs, PreparedStatement ps) {
            return Java.lang.reflect.Proxy.newProxyInstance(rs.getClass().getClassLoader(), rs.getClass().getInterfaces(),
                    new MyResultSet(rs,ps));
        }

        public Object invoke(Object proxy, Method m, Object[] args) 
throws Throwable {
            Object result = null;
            try {
                Class declaringClass = m.getDeclaringClass();

                if (m.getName().compareTo("close")==0) {
                        close();
                } else {
                    result = m.invoke(rs, args);
                }
            } catch (InvocationTargetException e) {
                throw e.getTargetException();
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage());

            } finally {
            }
            return result;
        }
    }

// Comment l'appeler:

ResultSet prs = (ResultSet) MyResultSet.newInstance(rs,ps);
0
erde