ProgrammationArchitecte Java

Qu'est-ce qu'un proxy, les types de proxy en Java et comment cela permet-il de réaliser un comportement dynamique des objets ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question :

Les objets proxy et le patron Proxy ont été introduits en Java pour créer des objets substituts, capables de contrôler l'accès à un objet réel, de modifier son comportement ou d'injecter des fonctionnalités transversales (sécurité, journalisation, métriques). Depuis Java 1.3, la prise en charge des proxies dynamiques standard a été introduite dans le paquet java.lang.reflect, suivie plus tard des bibliothèques populaires CGLIB et ByteBuddy.

Problème :

Il n'est pas toujours possible ou pratique de modifier directement la logique d'une classe existante. Souvent, il est nécessaire d'ajouter de manière transparente un comportement (par exemple, la journalisation, la mise en cache, les transactions) sans changer le code source de l'objet, ce qui n'est pas réalisable avec l'héritage standard.

Solution :

Les objets proxy peuvent être mis en œuvre statiquement (par sous-classe manuelle) et dynamiquement (via la réflexion ou le bytecode). Le proxy dynamique permet de créer à la volée des objets qui implémentent l'interface requise et délèguent les appels à l'objet réel à l'intérieur de la méthode invoke(), offrant ainsi une flexibilité pour l'injection de comportements externes.

Exemple de code :

import java.lang.reflect.*; interface Service { void doWork(); } class RealService implements Service { public void doWork() { System.out.println("Doing work!"); } } class LoggingInvocationHandler implements InvocationHandler { private final Object target; public LoggingInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Calling: " + method.getName()); return method.invoke(target, args); } } Service real = new RealService(); Service proxy = (Service) Proxy.newProxyInstance( real.getClass().getClassLoader(), new Class[] {Service.class}, new LoggingInvocationHandler(real)); proxy.doWork(); // Appel enregistré

Caractéristiques clés :

  • Les proxies statiques nécessitent un code prédéfini pour chaque fonction.
  • Les proxies dynamiques peuvent être créés à la volée à l'exécution pour n'importe quelle interface.
  • Pour les classes sans interfaces, des bibliothèques tierces sont nécessaires (par exemple, CGLIB).

Questions piégées.

Le Proxy dynamique standard en Java peut-il fonctionner avec des classes qui n'implémentent pas d'interfaces ?

Non. Le Proxy standard en Java ne fonctionne qu'avec des interfaces. Pour les classes sans interfaces (par exemple, si vous souhaitez proxy un classe normale), des outils tiers sont nécessaires (par exemple, CGLIB, ByteBuddy).

Peut-on créer un proxy pour une interface avec des méthodes privées ?

Non. Les interfaces en Java ne peuvent pas contenir de méthodes privées à proxy. Le proxy dynamique implémente les méthodes publiques de l'interface. Depuis Java 9, des méthodes par défaut privées ont été ajoutées, mais elles ne sont pas accessibles via le proxy.

Quelle est la différence entre InvocationHandler et MethodInterceptor (CGLIB) ?

InvocationHandler est une interface standard JDK utilisée pour les proxies dynamiques. Elle reçoit les appels de méthodes de l'interface. MethodInterceptor est une interface CGLIB qui permet d'intercepter les appels de méthodes dans n'importe quelle classe via l'héritage dynamique. La différence réside dans l'applicabilité et le niveau : JDK ne fonctionne qu'avec des interfaces, CGLIB fonctionne avec n'importe quelle classe.

Erreurs courantes et antipatterns

  • Erreur de type : tentative d'utiliser Proxy pour une classe ne réalisant pas d'interface.
  • Appels cycliques implicites entraînant un StackOverflow.
  • Proxification de classes non destinées à cela ou tentative de proxy des classes finales.

Exemples de la vie réelle

Cas négatif

Un ingénieur répète manuellement le code de journalisation dans chaque méthode de tous les services. En ajoutant de nouvelles méthodes, il duplique constamment la logique, oubliant souvent d'ajouter les appels nécessaires.

Avantages :

  • Transparent, facile à comprendre le déroulement.

Inconvénients :

  • Grande perte de temps, beaucoup de code répétitif, difficile à maintenir.

Cas positif

AOP est intégré au projet via un proxy dynamique : la journalisation et le contrôle des transactions sont effectués via des wrappers-substituts, implémentés de manière centralisée.

Avantages :

  • Plus rapide à introduire et à changer toute fonctionnalité transversale, pas de duplication.

Inconvénients :

  • Nécessité de comprendre comment fonctionne le proxy, complexités potentielles dans le débogage.