Contenu
Introduction
API de proxy dynamique
Sérialisation
Exemples
Introduction
Une classe de proxy dynamique est une classe qui implémente une liste d’interfaces spécifiées à l’exécution de telle sorte qu’une invocation de méthode via l’une des interfaces d’une instance de la classe sera codée et envoyée à un autre objet via une interface uniforme. Ainsi, une classe proxy dynamique peut être utilisée pour créer un objet proxy sécurisé pour une liste d’interfaces sans nécessiter la génération préalable de la classe proxy, par exemple avec des outils de compilation.Les invocations de méthode sur une instance d’une classe de proxy dynamique sontappariées à une seule méthode dans le gestionnaire invocationhandler de l’instance, et elles sont codées avec un objet java.lang.reflect.Method
identifiant la méthode qui a été invoquée et un tableau de type Object
contenant les arguments.
Les classes de proxy dynamiques sont utiles à une application ou à une bibliothèque qui doit fournir une répartition réfléchissante de type sûr des invocations sur des objets qui présentent des API d’interface. Par exemple, une application peut utiliser une classe proxy dynamique pour créer un objet qui implémente plusieurs interfaces d’écoute d’événements arbitraires – interfaces qui s’étendent java.util.EventListener
– pour traiter une variété d’événements de différents types de manière uniforme, par exemple en enregistrant tous ces événements dans un fichier.
API de classe de proxy dynamique
Une classe de proxy dynamique (simplement appelée proxyclass ci-dessous) est une classe qui implémente une liste d’interfaces spécifiées lors de l’exécution lorsque la classe est créée.
Une interface proxy est une interface telle qu’elle est implémentée par une classe proxy.
Une instance proxy est une instance d’une proxyclass.
Création d’une classe Proxy
Les classes Proxy, ainsi que leurs instances, sont créées à l’aide des méthodes statiques de la classe java.lang.refléter.Proxy.
La méthode Proxy.getProxyClass
renvoie l’objet java.lang.Class
pour une classe proxy donnée à un chargeur de classe et à un tableau d’interfaces. La classe proxy sera définie dans le chargeur de classe spécifié et implémentera toutes les interfaces fournies. Si une classe proxy pour la même permutation d’interfaces a déjà été définie dans le chargeur de classe, la classe proxy existante sera renvoyée; sinon, une classe proxy pour ces interfaces sera générée dynamiquement et définie dans le chargeur de classe.
Il existe plusieurs restrictions sur les paramètres qui peuvent être passés à Proxy.getProxyClass
:
- Tous les objets
Class
du tableauinterfaces
doivent représenter des interfaces, des notclasses ou des types primitifs. - Aucun élément du tableau
interfaces
ne peut faire référence à des objetsClass
identiques. - Tous les types d’interface doivent être visibles par leur nom via le chargeur de classe spécifié. En d’autres termes, pour le chargeur de classe
cl
et chaque interfacei
, l’expression suivante doit être vraie:Class.forName(i.getName(), false, cl) == i
- Toutes les interfaces non publiques doivent être dans le même package ; sinon, il ne serait pas possible pour la classe proxy d’implémenter toutes les interfaces, quel que soit le package dans lequel elle est définie.
- Pour tout ensemble de méthodes membres des interfaces spécifiées qui ont la même signature:
- Si le type de retour de l’une des méthodes est un type primitif orvoid, toutes les méthodes doivent avoir le même type de retour.
- Sinon, l’une des méthodes doit avoir un type de retour qui peut être attribué à tous les types de retour des autres méthodes.
- La classe proxy résultante ne doit pas dépasser les limites imposées surclasses par la machine virtuelle. Par exemple, la machine virtuelle peut limiter le nombre d’interfaces qu’une classe peut implémenter à 65535 ; dans ce cas, la taille du tableau
interfaces
ne doit pas dépasser 65535.
Si l’une de ces restrictions est violée, Proxy.getProxyClass
lancera un IllegalArgumentException
. Si l’argument de tableau interfaces
ou l’un de ses éléments est null
, un NullPointerException
sera détruit.
Notez que l’ordre des interfaces proxy spécifiées est significatif : deux demandes pour une classe proxy avec la même combinaison d’interfaces mais dans un ordre différent se traduiront par deux classes proxy distinctes. Les classes de proxy se distinguent par le nombre de leurs interfaces de proxy afin de fournir un codage d’invocation de méthode déterministe dans les cas où deux ou plusieurs des interfaces de proxy partagent une méthode avec le même nom et la même signature de paramètres; ce raisonnement est décrit plus en détail dans la section ci-dessous intitulée Méthodes dupliquées dans plusieurs interfaces de proxy.
Pour qu’une nouvelle classe de proxy n’ait pas besoin d’être générée chaque fois que Proxy.getProxyClass
est invoqué avec le même chargeur de classe et la même liste d’interfaces, l’implémentation de l’API de classe proxy dynamique doit conserver un cache de classes de proxy générées, saisies par leurs chargeurs et leur liste d’interfaces correspondants.L’implémentation doit faire attention à ne pas se référer aux chargeurs de classes, aux interfaces et aux classes proxy de manière à empêcher les chargeurs de classes et toutes leurs classes d’être collectés le cas échéant.
Propriétés de la classe Proxy
Une classe proxy possède les propriétés suivantes:
- Les classes proxy sont publiques, finales et non abstraites.
- Le nom non qualifié d’une classe proxy n’est pas spécifié. L’espace des noms de classes commençant par la chaîne
"$Proxy"
doit cependant être réservé aux classes proxy. - Une classe proxy s’étend
java.lang.reflect.Proxy
. - Une classe proxy implémente exactement les interfaces spécifiées lors de sa création, dans le même ordre.
- Si une classe proxy implémente une interface non publique, elle sera définie dans le même package que cette interface. Sinon, le package d’une classe proxy n’est pas non plus spécifié. Notez que packagesealing n’empêchera pas une classe proxy d’être définie avec succès dans un package particulier au moment de l’exécution, et ni willclasses déjà définies dans le même chargeur de classe et le même paquet avec des signataires particuliers.
- Étant donné qu’une classe proxy implémente toutes les interfaces spécifiées à sa création, invoquer
getInterfaces
sur son objetClass
renverra un tableau contenant la même liste d’interfaces (dans l’ordre spécifié à sa création), invoquergetMethods
sur son objetClass
renverra un tableau d’objetsMethod
qui incluenttoutes les méthodes dans ces interfaces, et invoquergetMethod
trouvera des méthodes dans les interfaces proxy comme prévu. - La méthode
Proxy.isProxyClass
retournera true si une classe proxy est transmise – une classe renvoyée parProxy.getProxyClass
ou la classe d’un objet retournée parProxy.newProxyInstance
– et false sinon. La fiabilité de cette méthode est importante pour pouvoir l’utiliserpour prendre des décisions de sécurité, son implémentation ne doit donc pas simplement tester si la classe en question s’étendjava.lang.reflect.Proxy
. - Le
java.security.ProtectionDomain
d’une proxyclass est le même que celui des classes système chargées par le chargeur bootstrapclass, telles quejava.lang.Object
, car le code d’une classe proxy est généré par du code système de confiance. Ce domaine de protection sera généralement accordéjava.security.AllPermission
.
Création d’une instance de proxy
Chaque classe de proxy a un constructeur public qui prend oneargument, une implémentation de l’interface InvocationHandler
.
Chaque instance de proxy a un objet de gestionnaire d’appel associé, celui qui a été transmis à son constructeur. Plutôt que d’avoir recours à l’API de réflexion pour accéder au constructeur public, un exemple de proxy peut également être créé en appelant la méthode Proxy.newProxyInstance
, qui combine les actions d’appel Proxy.getProxyClass
avec l’appel du constructeur avec un gestionnaire d’invocation.Proxy.newProxyInstance
lance IllegalArgumentException
pour les mêmes raisons que Proxy.getProxyClass
.
Propriétés d’instance de proxy
Une instance de proxy possède les propriétés suivantes:
- Étant donné une instance de proxy
proxy
et l’une des interfaces implémentées par sa classe de proxyFoo
, l’expression suivante retournera true:proxy instanceof Foo
et l’opération de conversion suivante réussira (plutôt que de lancer una
ClassCastException
):(Foo) proxy
- La méthode static
Proxy.getInvocationHandler
retournera le gestionnaire d’invocation associé au proxy instancepassed comme argument. Si l’objet passé àProxy.getInvocationHandler
n’est pas une instance proxy, alors unIllegalArgumentException
sera lancé. - Une invocation de méthode d’interface sur une instance proxy sera codée et envoyée à la méthode
invoke
du gestionnaire d’invocation comme décrit ci-dessous.L’instance de proxy elle-même sera passée en tant que premier argument de
invoke
, qui est de typeObject
.Le deuxième argument passé à
invoke
sera l’instancejava.lang.reflect.Method
correspondant à la méthode d’interface invoquée sur l’instance proxy. La classe déclarante de l’objetMethod
sera l’interface dans laquelle themethod a été déclarée, qui peut être une superinterface de l’interface proxy par laquelle la classe proxy hérite de la méthode.Le troisième argument passé à
invoke
sera un tableau d’objets contenant les valeurs des arguments passés dans l’invocation de la méthode sur l’instance proxy. Les arguments des primitivetypes sont encapsulés dans une instance de la classe primitivewrapper appropriée, telle quejava.lang.Integer
oujava.lang.Boolean
. La mise en œuvre de la méthodeinvoke
est libre de modifier le contenu de ce tableau.La valeur renvoyée par la méthode
invoke
deviendra la valeur de retour de l’appel de méthode sur l’instance de proxy. Si la valeur de retour déclarée de la méthode d’interface est un primitivetype, la valeur renvoyée parinvoke
doit être une instance de la classe wrapper primitive correspondante ; sinon, il doit s’agir d’un type assignable au type de retour déclaré. Si la valeur renvoyée parinvoke
vautnull
et que le type de retour de la méthode d’interface est primitif, alors unNullPointerException
sera lancé par la methodinvocation sur l’instance de proxy. Si la valeur renvoyée parinvoke
n’est pas compatible avec le type de retour déclaré de la méthode comme décrit ci-dessus, unClassCastException
sera émis par le proxy.Si une exception est levée par la méthode
invoke
, elle le sera également par l’invocation de la méthode sur l’instance de proxy.Le type de l’exception doit être assignable à l’un des types d’exception déclarés dans la signature de la méthode d’interface ou aux types d’exception non contrôlésjava.lang.RuntimeException
oujava.lang.Error
. Si une exception vérifiée est levée parinvoke
qui n’est assignable à aucun des types d’exception déclarés dans la clausethrows
de l’interfacemethod, alors uneUndeclaredThrowableException
sera levée par l’invocation de la méthode sur l’instance proxy. LeUndeclaredThrowableException
sera construit avecl’exception qui a été levée par la méthodeinvoke
. - Une invocation des méthodes
hashCode
,equals
outoString
déclarées dansjava.lang.Object
sur une instance proxy sera encodée et envoyée à la méthodeinvoke
du gestionnaire d’invocation de la même manière que les invocations de méthode d’interface sont codées et distribuées, comme décrit ci-dessus. La classe déclarante de l’objetMethod
passé àinvoke
serajava.lang.Object
. D’autres méthodes publiques d’une instance proxyinstance héritée dejava.lang.Object
ne sont pas overridées par une classe proxy, donc les invocations de ces méthodes se comportent comme elles le font pour les instances dejava.lang.Object
.
Méthodes dupliquées dans des interfaces Multiproxy
Lorsque deux interfaces ou plus d’une classe proxy contiennent une méthodeavec le même nom et la même signature de paramètre, l’ordre des interfaces de la classe proxy devient significatif. Lorsqu’une telle méthode de duplication est invoquée sur une instance de proxy, l’objet Method
transmis au gestionnaire d’invocation ne sera pas nécessairement celui dont la classe déclarante peut être assignée à partir du type de référence de l’interface via laquelle la méthode du proxy a été invoquée. Cette limitation existe car l’implémentation de la méthode correspondante dans la classe proxy générée ne peut pas déterminer par quelle interface elle a été invoquée. Par conséquent, lorsqu’une méthode dupliquée est appelée sur une instance de proxy, l’objet Method
de la méthode dans l’interface principale qui contient la méthode (directement ou héritée via une superinterface) dans la liste des interfaces de la classe proxy est transmis à la méthode invoke
du gestionnaire d’invocation, quel que soit le type de référence à travers lequel l’invocation de la méthode s’est produite.
Si une interface proxy contient une méthode avec le même nom et la même signature de paramètre que les méthodes hashCode
, equals
ou toString
de java.lang.Object
, lorsqu’une telle méthode est invoquée sur une instance aproxy, l’objet Method
passé au gestionnaire d’invocation aura java.lang.Object
comme classe de déclaration. En d’autres termes, les méthodes publiques non finales de java.lang.Object
précèdent logiquement toutes les interfaces proxy pour déterminer quel objet Method
transmettre au gestionnaire d’invocation.
Notez également que lorsqu’une méthode dupliquée est envoyée à un gestionnaire d’invocation, la méthode invoke
ne peut lancer que des types d’exception vérifiés qui peuvent être assignés à l’un des types d’exception de la clause throws
de la méthode dans toutes les interfaces proxy via lesquelles elle peut être invoquée. Si la méthode invoke
lève une exception vérifiée qui n’est assignable à aucun des types d’exception déclarés par la méthode dans l’une des interfaces proxy via lesquelles elle peut être invoquée, alors anunchecked UndeclaredThrowableException
sera lancé lors de l’appel sur l’instance proxy. Cette restriction signifie que tous les types d’exception renvoyés en invoquant getExceptionTypes
sur l’objet Method
passé à la méthode invoke
ne peuvent pas nécessairement être lancés avec succès par la méthode invoke
.
Sérialisation
Depuis que java.lang.reflect.Proxy
implémente java.io.Serializable
, les instances de proxy peuvent êtresérialisées, comme décrit dans cette section. Si une instance proxy contient un gestionnaire d’appel qui n’est pas assignable à java.io.Serializable
, cependant, un java.io.NotSerializableException
sera lancé si une telle instance est écrite dans un java.io.ObjectOutputStream
. Notez que pour les proxyclasses, l’implémentation de java.io.Externalizable
a le même effet en ce qui concerne la sérialisation que l’implémentation de java.io.Serializable
: les méthodes writeExternal
et readExternal
de l’interface Externalizable
ne seront jamais invoquées sur une instance aproxy (ou un gestionnaire d’invocation) dans le cadre de son processus de sérialisation. Comme pour tous les objets Class
, l’objet Class
d’une classe proxy est toujours sérialisable.
Une classe proxy n’a pas de champs sérialisables et un serialVersionUID
de 0L
. En d’autres termes, lorsque l’objet Class
d’une classe proxy est passé à la méthode statique lookup
de java.io.ObjectStreamClass
, l’instance ObjectStreamClass
renvoyée aura les propriétés suivantes:
- Invoquer sa méthode
getSerialVersionUID
retournera0L
. - Invoquer sa méthode
getFields
renverra un tableau de longueur nulle. - Invoquer sa méthode
getField
avec n’importe quel argumentString
renverranull
.
Le protocole stream pour la sérialisation d’objets prend en charge un code type nommé TC_PROXYCLASSDESC
, qui est un symbole de terminaison dans la grammaire du format stream; son type et sa valeur sont définis par le champ constant suivant dans l’interface java.io.ObjectStreamConstants
:
final static byte TC_PROXYCLASSDESC = (byte)0x7D;
La grammaire comprend également les deux règles suivantes, la première étant une extension alternative de la newClassDescrule d’origine :
newClassDesc :
TC_PROXYCLASSDESC
newHandle proxyClassDescInfo
proxyClassDescInfo:
(int)<count>
proxyInterfaceName classAnnotationsuperClassDesc
Nom de l’interface PROXY:(utf)
Lorsqu’un ObjectOutputStream
sérialise le classdescriptor pour une classe qui est une classe proxy, tel que déterminé en contournant son objet Class
à la méthode Proxy.isProxyClass
, il utilise le code de type TC_PROXYCLASSDESC
au lieu de TC_CLASSDESC
, en suivant les règles ci-dessus. Dans l’extension de proxyClassDescInfo, la séquence des éléments Proxyinterfacename sont les noms de toutes les interfaces implémentées par la classe proxy, dans l’ordre dans lequel elles sont renvoyées en appelant la méthode getInterfaces
sur l’objet Class
. Les éléments classAnnotation Etsuperclassdesc ont la même signification que dans la règle Classdescinfo. Pour une classe proxy, superClassDescis est le descripteur de classe pour sa superclasse, java.lang.reflect.Proxy
; inclure ce descripteur permet l’évolution de la représentation sérialisée de la classe Proxy
pour les instances proxy.
Pour les classes non proxy, ObjectOutputStream
appelle sa méthode protected annotateClass
pour autoriser les sous-classes à inscrire des données personnalisées dans le flux pour une classe particulière. Pour les proxyclasses, au lieu de annotateClass
, la méthode suivante dans java.io.ObjectOutputStream
est appelée avec l’objet Class
pour la classe proxy:
protected void annotateProxyClass(Class cl) throws IOException;
L’implémentation par défaut de annotateProxyClass
dans ObjectOutputStream
ne fait rien.
Lorsqu’un ObjectInputStream
rencontre le code de type TC_PROXYCLASSDESC
, il désérialise le classdescriptor pour une classe proxy du flux, formaté comme décrit ci-dessus. Au lieu d’appeler sa méthode resolveClass
pour résoudre l’objet Class
pour le classdescriptor, la méthode suivante dans java.io.ObjectInputStream
est appelée:
protected Class resolveProxyClass(String interfaces) throws IOException, ClassNotFoundException;
La liste des noms d’interface qui ont été désérialisés dans le descripteur proxyclass est passée en tant qu’argument interfaces
à resolveProxyClass
.
L’implémentation par défaut de resolveProxyClass
dans ObjectInputStream
renvoie les résultats de l’appel de Proxy.getProxyClass
avec la liste des objets Class
pour les interfaces nommées dans le paramètre interfaces
. L’objet Class
utilisé pour chaque nom d’interface i
est la valeur réajustée en appelant
Class.forName(i, false, loader)
oùloader
est le premier chargeur de classe non nul dans la pile d’exécution, ounull
si aucun chargeur de classe non nul ne se trouve sur la pile. C’est le même choix de chargeur de classe effectué par le comportement par défaut de la méthoderesolveClass
. Cette même valeur deloader
est également le chargeur de classe passé àProxy.getProxyClass
. SiProxy.getProxyClass
lance unIllegalArgumentException
,resolveClass
lancera unClassNotFoundException
contenant leIllegalArgumentException
.
Comme une classe de proxy n’a jamais ses propres champs sérialisables, theclassdata dans la représentation de flux d’une instance de proxy se compose entièrement des données d’instance de sa superclasse, java.lang.reflect.Proxy
. Proxy
a un champ sérialisable, h
, qui contient l’invocationhandler pour l’instance proxy.
Exemples
Voici un exemple simple qui imprime un message avant et après une invocation de méthode sur un objet qui implémente une liste arbitraire d’interfaces:
public interface Foo { Object bar(Object obj) throws BazException;}public class FooImpl implements Foo { Object bar(Object obj) throws BazException { // ... }}public class DebugProxy implements java.lang.reflect.InvocationHandler { private Object obj; public static Object newInstance(Object obj) { return java.lang.reflect.Proxy.newProxyInstance( obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new DebugProxy(obj)); } private DebugProxy(Object obj) { this.obj = obj; } public Object invoke(Object proxy, Method m, Object args) throws Throwable { Object result; try { System.out.println("before method " + m.getName()); result = m.invoke(obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } catch (Exception e) { throw new RuntimeException("unexpected invocation exception: " + e.getMessage()); } finally { System.out.println("after method " + m.getName()); } return result; }}
Pour construire une DebugProxy
pour une implémentation de l’interface Foo
et appeler l’une de ses méthodes:
Foo foo = (Foo) DebugProxy.newInstance(new FooImpl()); foo.bar(null);
Voici un exemple de classe de gestionnaire d’appel d’utilitaire qui fournit un comportement de proxy par défaut pour les méthodes héritées de java.lang.Object
et implémente la délégation de certaines invocations de méthode proxy à des objets distincts en fonction de l’interface de la méthode invoquée:
import java.lang.reflect.*;public class Delegator implements InvocationHandler { // preloaded Method objects for the methods in java.lang.Object private static Method hashCodeMethod; private static Method equalsMethod; private static Method toStringMethod; static { try { hashCodeMethod = Object.class.getMethod("hashCode", null); equalsMethod = Object.class.getMethod("equals", new Class { Object.class }); toStringMethod = Object.class.getMethod("toString", null); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } private Class interfaces; private Object delegates; public Delegator(Class interfaces, Object delegates) { this.interfaces = (Class) interfaces.clone(); this.delegates = (Object) delegates.clone(); } public Object invoke(Object proxy, Method m, Object args) throws Throwable { Class declaringClass = m.getDeclaringClass(); if (declaringClass == Object.class) { if (m.equals(hashCodeMethod)) { return proxyHashCode(proxy); } else if (m.equals(equalsMethod)) { return proxyEquals(proxy, args); } else if (m.equals(toStringMethod)) { return proxyToString(proxy); } else { throw new InternalError( "unexpected Object method dispatched: " + m); } } else { for (int i = 0; i < interfaces.length; i++) { if (declaringClass.isAssignableFrom(interfaces)) { try { return m.invoke(delegates, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } } return invokeNotDelegated(proxy, m, args); } } protected Object invokeNotDelegated(Object proxy, Method m, Object args) throws Throwable { throw new InternalError("unexpected method dispatched: " + m); } protected Integer proxyHashCode(Object proxy) { return new Integer(System.identityHashCode(proxy)); } protected Boolean proxyEquals(Object proxy, Object other) { return (proxy == other ? Boolean.TRUE : Boolean.FALSE); } protected String proxyToString(Object proxy) { return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode()); }}
Les sous-classes de Delegator
peuvent remplacer invokeNotDelegated
pour implémenter le comportement des invocations proxymethod à ne pas déléguer directement à d’autres objets, et elles peuvent remplacer proxyHashCode
, proxyEquals
et proxyToString
pour remplacer le comportement par défaut des méthodes dont le proxy hérite java.lang.Object
.
Pour construire une Delegator
pour une implémentation de l’interface Foo
:
Class proxyInterfaces = new Class { Foo.class }; Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), proxyInterfaces, new Delegator(proxyInterfaces, new Object { new FooImpl() }));
Notez que l’implémentation de la classe Delegator
donnée ci-dessus est destinée à être plus illustrative qu’optimisée; par exemple, au lieu de mettre en cache et de comparer les objets Method
pour les méthodes hashCode
, equals
et toString
, il pourrait simplement les faire correspondre par leurs noms de chaîne, car aucun de ces noms de méthode n’est surchargé dans java.lang.Object
.