Par hasard aujourd'hui, j'ai soudain eu envie de jeter un œil au proxy dynamique du JDK, car je le connaissais un peu avant, et je voulais juste tester son utilisation. J'ai rapidement écrit ces interfaces et classes :
Classe d'interface : UserService.java
Copiez le code comme suit :
paquet com.yixi.proxy;
interface publique UserService {
public int save();
mise à jour publique vide (identifiant int);
}
Classe d'implémentation : UserServiceImpl.java
Copiez le code comme suit :
paquet com.yixi.proxy;
classe publique UserServiceImpl implémente UserService {
@Outrepasser
public int save() {
System.out.println("enregistrement utilisateur....");
renvoyer 1 ;
}
@Outrepasser
mise à jour publique nulle (identifiant int) {
System.out.println("mettre à jour un utilisateur " + identifiant);
}
}
Ensuite, j'ai écrit anxieusement le InvocationHandler que je voulais : la fonction de celui-ci est très simple, c'est d'enregistrer l'heure de début et l'heure de fin de l'exécution de la méthode.
TimeInvocationHandler.java
Copiez le code comme suit :
paquet com.yixi.proxy;
importer java.lang.reflect.InvocationHandler ;
importer java.lang.reflect.Method ;
classe publique TimeInvocationHandler implémente InvocationHandler {
@Outrepasser
objet public invoque (proxy d'objet, méthode méthode, arguments Object[])
lance Jetable {
System.out.println("startTime : " +System.currentTimeMillis());
Objet obj = method.invoke(proxy, args);
System.out.println("endTime : " +System.currentTimeMillis());
retourner obj ;
}
}
Une fois tous les préparatifs terminés, il est bien sûr temps de commencer à rédiger les tests !
Test.java
Copiez le code comme suit :
paquet com.yixi.proxy;
importer java.lang.reflect.Proxy ;
Test de classe publique {
public static void main(String[] args) { 9 TimeInvocationHandler timeHandler = new TimeInvocationHandler();
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.update(2);
u.save();
}
}
Cela s'est déroulé joyeusement, mais cela ne vous a donné aucun visage. Le résultat a été une exception qui a rempli l'écran :
Copiez le code comme suit :
heure de début : 1352877835040
heure de début : 1352877835040
heure de début : 1352877835040
Exception dans le fil de discussion "principal" java.lang.reflect.UndeclaredThrowableException
à $ Proxy0.update (source inconnue)
sur com.yixi.proxy.Test.main(Test.java:11)
Causé par : java.lang.reflect.InvocationTargetException
à sun.reflect.NativeMethodAccessorImpl.invoke0 (méthode native)
à sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
à sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
sur java.lang.reflect.Method.invoke(Method.java:597)
sur com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12)
... 2 de plus
L'exception com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12) indique clairement le problème à la ligne 12 de TimeInvocationHandle : c'est-à-dire
Copiez le code comme suit :
objet public invoque (proxy d'objet, méthode méthode, arguments Object[])
lance Jetable {
System.out.println("startTime : " +System.currentTimeMillis());
Objet obj = method.invoke(proxy, args);
System.out.println("endTime : " +System.currentTimeMillis());
retourner obj ;
}
Il n'y a rien de mal avec la méthode ! Parce que la méthode Invoke() semble fournir tous les paramètres requis par method.invoke(Object, Object[]), nous l'utiliserons naturellement. Si vous pensez vraiment de cette façon, alors vous avez trompé le JDK. un piège. Voyons d'abord la bonne façon de l'écrire. Au cas où certains étudiants ne seraient pas d'humeur à lire ce qui suit, donnez-moi au moins la bonne solution :
Modifier TimeInvocationHandler.java
Copiez le code comme suit :
paquet com.yixi.proxy;
importer java.lang.reflect.InvocationHandler ;
importer java.lang.reflect.Method ;
classe publique TimeInvocationHandler implémente InvocationHandler {
Objet privé o ;
public TimeInvocationHandler(Objet o){
ceci.o = o;
}
@Outrepasser
objet public invoque (proxy d'objet, méthode méthode, arguments Object[])
lance Jetable {
System.out.println("startTime : " +System.currentTimeMillis());
Objet obj = method.invoke(o, args);
System.out.println("endTime : " +System.currentTimeMillis());
retourner obj ;
}
}
Modifier Test.java
Copiez le code comme suit :
paquet com.yixi.proxy;
importer java.lang.reflect.Proxy ;
Test de classe publique {
public static void main (String[] arguments) {
TimeInvocationHandler timeHandler = new TimeInvocationHandler(new UserServiceImpl());
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.update(2);
u.save();
}
}
Voici maintenant le résultat correct :
Copiez le code comme suit :
heure de début : 1352879531334
mettre à jour un utilisateur 2
heure de fin : 1352879531334
heure de début : 1352879531334
sauvegarde de l'utilisateur....
heure de fin : 1352879531335
Si vous voulez moins de code, vous pouvez écrire directement une classe anonyme :
paquet com.yixi.proxy;
importer java.lang.reflect.InvocationHandler ;
importer java.lang.reflect.Method ;
importer java.lang.reflect.Proxy ;
Test de classe publique {
public static void main (String[] arguments) {
final UserServiceImpl usi = new UserServiceImpl();
UserService u = (UserService) Proxy.newProxyInstance(
avec.getClass().getClassLoader(),
avec.getClass().getInterfaces(),
nouveau InvocationHandler() {
@Outrepasser
objet public invoque (proxy d'objet, méthode méthode, arguments Object[])
lance Jetable {
System.out.println("startTime : " +System.currentTimeMillis());
Objet obj = method.invoke(usi, args);
System.out.println("endTime : " +System.currentTimeMillis());
retourner obj ;
}
});
u.update(2);
u.save();
}
}
Puisque le premier paramètre de method.invoke(target,args); est l'objet cible, pourquoi la méthode Invoke de invocationHandler a-t-elle besoin d'un paramètre proxy Object ? Regardons en bas !
Pour la méthode d’invocation la plus importante (à mon avis), jetons un œil à ce que dit le JDK :
Copiez le code comme suit :
invoquer
Invocation d'objet (proxy d'objet,
Méthode méthode,
Objet[] arguments)
lance des appels de méthode Throwable handles sur l’instance proxy et renvoie le résultat. Cette méthode est appelée sur le gestionnaire d'appel lorsque la méthode est appelée sur l'instance de proxy à laquelle elle est associée.
paramètre:
proxy - l'instance proxy sur laquelle la méthode est appelée
method - une instance de méthode correspondant à la méthode d'interface appelée sur l'instance proxy. La classe déclarante d'un objet Method sera l'interface dans laquelle la méthode est déclarée, qui peut être une superinterface de l'interface proxy dont la classe proxy hérite de la méthode.
args - un tableau d'objets contenant les valeurs d'argument transmises dans l'appel de méthode sur l'instance proxy, ou null si la méthode d'interface ne prend aucun argument. Les paramètres des types de base sont encapsulés dans des instances de la classe wrapper de base appropriée (telle que java.lang.Integer ou java.lang.Boolean).
proxy - l'instance proxy sur laquelle la méthode est appelée ? Que signifie cette phrase ? par intérim? La méthode est-elle la méthode proxy ? Alors ma méthode pour exécuter le proxy ne devrait-elle pas être Object obj = method.invoke(proxy, args);? Je ne me suis pas retourné à ce moment-là, je suis allé au groupe de discussion et je suis allé sur Google mais je n'ai trouvé aucune inspiration. J'ai pensé que je ferais mieux de regarder le code source et peut-être que je pourrais voir quelque chose !
Ouvrez le code source de la classe Proxy et découvrez ce qu'est un constructeur :
Copiez le code comme suit :
protégéInvocationHandler h;
Proxy protégé (InvocationHandler h) {
ceci.h = h;
}
Utilisez InvocationHandler comme paramètre du constructeur du proxy.... Alors à quoi sert-il InvocationHandler ? Existe-t-il un lien avec la méthode Invocation() dans InvocationHandler ?
Ma première pensée est que Proxy appellera l'instruction suivante en interne :
Copiez le code comme suit :
h.invoke(this,methodName,args);
Parce que vous devez appeler la méthode Invoke pour exécuter la méthode correspondante.
Jetons un coup d'oeil à cela d'abord
Vous trouverez ici quelque chose qui semble logique : lorsque u.update(2), àProxy appellera handler.invoke(proxyClass,update,2) à, ce qui signifie proxyClass.update(2);
Lorsque u.save(); àProxy appellera handler.invoke(proxyClass,save,null) àc'est-à-dire proxyClass.save();
Lorsque Test.java est remplacé par ceci :
Copiez le code comme suit :
Test de classe publique {
public static void main (String[] arguments) {
final UserServiceImpl usi = new UserServiceImpl();
UserService u = (UserService) Proxy.newProxyInstance(
avec.getClass().getClassLoader(),
avec.getClass().getInterfaces(),
nouveau InvocationHandler() {
@Outrepasser
objet public invoque (proxy d'objet, méthode méthode, arguments Object[])
lance Jetable {
renvoie null ;
}
});
u.update(2);
u.save();
}
}
Notez que la méthode de la classe anonyme renvoie actuellement null Si vous l'exécutez, vous trouverez :
Copiez le code comme suit :
Exception dans le thread "principal" java.lang.NullPointerException
à $ Proxy0.save (source inconnue)
sur com.yixi.proxy.Test.main(Test.java:17)
Il y a un pointeur nul à la ligne 17, c'est-à-dire que la méthode u.save() a ici un élément nul. Se pourrait-il que u soit vide ? Cela ne devrait pas être le cas. Si u est nul, alors u.update(2) signalera une exception de pointeur nul. Lorsque je commente la ligne 17, l'exception disparaît, indiquant que u.update() peut s'exécuter normalement. Alors pourquoi est-ce ?
En fait, c'est pourquoi la méthode d'invocation renvoie null :
Faites attention aux deux méthodes de la classe UserService :
Copiez le code comme suit :
interface publique UserService {
public int save();
mise à jour publique vide (identifiant int);
}
La méthode Save() renvoie un type int et la méthode update renvoie un type void ; selon l'hypothèse ci-dessus, handler.invoke() implémente proxyClass.update(2);, et la méthode de retour dans la méthode d'invocation correspond au retour. valeur de la méthode proxy,
Ainsi, lorsque la méthode d'invocation renvoie null, la méthode de mise à jour de l'agent reçoit une valeur de retour null, et elle renvoie à l'origine void, donc aucune exception n'est signalée et la sauvegarde de l'agent doit renvoyer une valeur de type int. Ce que nous retournons ici est toujours nul. et la JVM ne peut pas convertir null en type int, donc une exception est signalée. Cette explication peut être expliquée clairement, et elle peut également prouver relativement la supposition précédente.
Le premier paramètre proxy dans la méthode d'invocation d'InvocationHandler semble être simplement destiné à permettre à la classe Proxy de transmettre la référence de l'objet proxy proxyClass lors de l'appel de la méthode avec la référence de son propre objet InvocationHandler pour terminer les tâches que proxyClass doit accomplir. .
Le talent littéraire n’est pas bon ! La capacité est limitée ! J'espère que vous pourrez me corriger...