Proxys dynamiques en Java

Proxys dynamiques en Java

1. introduction

Cet article concerneJava’s dynamic proxies - qui est l'un des principaux mécanismes de proxy disponibles dans la langue.

En termes simples, les mandataires sont des fronts ou des wrappers qui transmettent l’invocation de fonctions via leurs propres installations (généralement sur des méthodes réelles), ce qui ajoute potentiellement certaines fonctionnalités.

Les proxys dynamiques permettent à une seule classe avec une seule méthode de desservir plusieurs appels de méthodes à des classes arbitraires avec un nombre arbitraire de méthodes. Un proxy dynamique peut être considéré comme une sorte deFacade, mais qui peut prétendre être une implémentation de n'importe quelle interface. Sous le couvert,it routes all method invocations to a single handler - la méthodeinvoke().

Bien qu'il ne s'agisse pas d'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 kit JDK standard. Par conséquent, aucune dépendance supplémentaire n'est requise.

2. Gestionnaire d'appel

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 dejava.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 classejava.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éthodeput() appelée est imprimé dans le fichier journal.

4. Gestionnaire d'appels via les expressions Lambda

PuisqueInvocationHandler est une interface fonctionnelle, il est possible de définir le gestionnaire en ligne en utilisant 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 lanceUnsupportedOperationException 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 nécessaire à l'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 minutage et d'invocation réflexive:

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 proxy une carte et une séquence de caractères (String).

Les invocations 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 rapide didacticiel, nous avons examiné les proxys dynamiques de Java ainsi que certaines de ses utilisations possibles.

Comme toujours, le code dans les exemples peut être trouvéover on GitHub.