Proxies dynamiques en Java

1. Introduction

Cet article concerne les proxies dynamiques de Java - qui est l'un des principaux mécanismes de proxy disponibles dans le langage.

En termes simples, les proxies sont des fronts ou des wrappers qui passent l'invocation de fonction via leurs propres installations (généralement sur de vraies méthodes) - ajoutant potentiellement des fonctionnalités.

Les proxies dynamiques permettent à une seule classe avec une seule méthode de traiter plusieurs appels de méthode à des classes arbitraires avec un nombre arbitraire de méthodes. Un proxy dynamique peut être considéré comme une sorte de façade , mais qui peut prétendre être une implémentation de n'importe quelle interface. Sous le couvert, il achemine toutes les invocations de méthode vers un seul gestionnaire - la méthode invoke () .

Bien que ce ne soit pas un outil destiné aux tâches de programmation quotidiennes, les proxys dynamiques peuvent être très utiles pour les auteurs de framework. Il peut également être utilisé dans les cas où les implémentations de classes concrètes ne seront pas connues avant l'exécution.

Cette fonctionnalité est intégrée au JDK standard, par conséquent aucune dépendance supplémentaire n'est requise.

2. Gestionnaire d'appels

Construisons un proxy simple qui ne fait rien d'autre que l'impression de la méthode qui a été demandée et renvoie un nombre codé en dur.

Tout d'abord, nous devons créer un sous-type de java.lang.reflect.InvocationHandler :

public class DynamicInvocationHandler implements InvocationHandler { private static Logger LOGGER = LoggerFactory.getLogger( DynamicInvocationHandler.class); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { LOGGER.info("Invoked method: {}", method.getName()); return 42; } }

Ici, nous avons défini un proxy simple qui enregistre la méthode appelée et renvoie 42.

3. Création d'une instance de proxy

Une instance de proxy desservie par le gestionnaire d'invocation que nous venons de définir est créée via un appel de méthode d'usine sur la classe java.lang.reflect.Proxy :

Map proxyInstance = (Map) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class }, new DynamicInvocationHandler());

Une fois que nous avons une instance de proxy, nous pouvons invoquer ses méthodes d'interface comme d'habitude:

proxyInstance.put("hello", "world");

Comme prévu, un message concernant la méthode put () appelée est imprimé dans le fichier journal.

4. Gestionnaire d'appels via les expressions Lambda

Puisque InvocationHandler est une interface fonctionnelle, il est possible de définir le gestionnaire en ligne à l'aide de l'expression lambda:

Map proxyInstance = (Map) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class }, (proxy, method, methodArgs) -> { if (method.getName().equals("get")) { return 42; } else { throw new UnsupportedOperationException( "Unsupported method: " + method.getName()); } });

Ici, nous avons défini un gestionnaire qui renvoie 42 pour toutes les opérations get et lève une exception UnsupportedOperationException pour tout le reste.

Il est invoqué exactement de la même manière:

(int) proxyInstance.get("hello"); // 42 proxyInstance.put("hello", "world"); // exception

5. Exemple de proxy dynamique de synchronisation

Examinons un scénario réel potentiel pour les proxys dynamiques.

Supposons que nous voulions enregistrer le temps d'exécution de nos fonctions. Dans cette mesure, nous définissons d'abord un gestionnaire capable d'encapsuler l'objet «réel», de suivre les informations de synchronisation et d'invoquer par réflexion:

public class TimingDynamicInvocationHandler implements InvocationHandler { private static Logger LOGGER = LoggerFactory.getLogger( TimingDynamicInvocationHandler.class); private final Map methods = new HashMap(); private Object target; public TimingDynamicInvocationHandler(Object target) { this.target = target; for(Method method: target.getClass().getDeclaredMethods()) { this.methods.put(method.getName(), method); } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.nanoTime(); Object result = methods.get(method.getName()).invoke(target, args); long elapsed = System.nanoTime() - start; LOGGER.info("Executing {} finished in {} ns", method.getName(), elapsed); return result; } }

Par la suite, ce proxy peut être utilisé sur différents types d'objets:

Map mapProxyInstance = (Map) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class }, new TimingDynamicInvocationHandler(new HashMap())); mapProxyInstance.put("hello", "world"); CharSequence csProxyInstance = (CharSequence) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { CharSequence.class }, new TimingDynamicInvocationHandler("Hello World")); csProxyInstance.length()

Ici, nous avons mandaté une carte et une séquence de caractères (String).

Les appels des méthodes proxy délégueront à l'objet encapsulé et produiront des instructions de journalisation:

Executing put finished in 19153 ns Executing get finished in 8891 ns Executing charAt finished in 11152 ns Executing length finished in 10087 ns

6. Conclusion

Dans ce tutoriel rapide, nous avons examiné les proxys dynamiques de Java ainsi que certaines de ses utilisations possibles.

Comme toujours, le code des exemples se trouve à l'adresse over sur GitHub.