Durch Zufall wollte ich heute plötzlich einen Blick auf den dynamischen Proxy von JDK werfen, da ich vorher ein wenig darüber wusste und nur seine Verwendung testen wollte. Ich habe schnell diese Schnittstellen und Klassen geschrieben:
Schnittstellenklasse: UserService.java
Kopieren Sie den Codecode wie folgt:
Paket com.yixi.proxy;
öffentliche Schnittstelle UserService {
public int save();
public void update(int id);
}
Implementierungsklasse: UserServiceImpl.java
Kopieren Sie den Codecode wie folgt:
Paket com.yixi.proxy;
Die öffentliche Klasse UserServiceImpl implementiert UserService {
@Override
public int save() {
System.out.println("Benutzer speichern....");
Rückgabe 1;
}
@Override
public void update(int id) {
System.out.println("update a user" + id);
}
}
Dann habe ich gespannt den InvocationHandler geschrieben, den ich wollte: Die Funktion ist sehr einfach, sie besteht darin, die Start- und Endzeit der Methodenausführung aufzuzeichnen.
TimeInvocationHandler.java
Kopieren Sie den Codecode wie folgt:
Paket com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
öffentliche Klasse TimeInvocationHandler implementiert InvocationHandler {
@Override
öffentlicher Objektaufruf (Objekt-Proxy, Methodenmethode, Objekt[]-Argumente)
wirft Throwable {
System.out.println("startTime : " +System.currentTimeMillis());
Objekt obj = method.invoke(proxy, args);
System.out.println("endTime : " +System.currentTimeMillis());
return obj;
}
}
Nachdem alle Vorbereitungen abgeschlossen sind, ist es natürlich Zeit, mit dem Schreiben von Tests zu beginnen!
Test.java
Kopieren Sie den Codecode wie folgt:
Paket com.yixi.proxy;
import java.lang.reflect.Proxy;
öffentlicher Klassentest {
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();
}
}
Es lief fröhlich, aber es gab Ihnen kein Gesicht. Das Ergebnis war eine bildschirmfüllende Ausnahme:
Kopieren Sie den Codecode wie folgt:
Startzeit: 1352877835040
Startzeit: 1352877835040
Startzeit: 1352877835040
Ausnahme im Thread „main“ java.lang.reflect.UndeclaredThrowableException
bei $Proxy0.update (Unbekannte Quelle)
unter com.yixi.proxy.Test.main(Test.java:11)
Verursacht durch: java.lang.reflect.InvocationTargetException
bei sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
bei sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
bei sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
bei java.lang.reflect.Method.invoke(Method.java:597)
unter com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12)
... 2 weitere
Die Ausnahme com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12) macht das Problem in Zeile 12 von TimeInvocationHandle deutlich: Das heißt
Kopieren Sie den Codecode wie folgt:
öffentlicher Objektaufruf (Objekt-Proxy, Methodenmethode, Objekt[]-Argumente)
wirft Throwable {
System.out.println("startTime : " +System.currentTimeMillis());
Objekt obj = method.invoke(proxy, args);
System.out.println("endTime : " +System.currentTimeMillis());
return obj;
}
An der Methode ist nichts auszusetzen! Da die invoke()-Methode scheinbar alle von method.invoke(Object, Object[]) benötigten Parameter bereitstellt, werden wir sie selbstverständlich verwenden. Wenn Sie wirklich so denken, dann haben Sie das JDK getäuscht Schauen wir uns zunächst an, wie man es richtig schreibt. Falls einige Schüler keine Lust haben, Folgendes zu lesen, geben Sie mir zumindest die richtige Lösung:
Ändern Sie TimeInvocationHandler.java
Kopieren Sie den Codecode wie folgt:
Paket com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
öffentliche Klasse TimeInvocationHandler implementiert InvocationHandler {
privates Objekt o;
public TimeInvocationHandler(Object o){
this.o = o;
}
@Override
öffentlicher Objektaufruf (Objekt-Proxy, Methodenmethode, Objekt[]-Argumente)
wirft Throwable {
System.out.println("startTime : " +System.currentTimeMillis());
Objekt obj = method.invoke(o, args);
System.out.println("endTime : " +System.currentTimeMillis());
return obj;
}
}
Ändern Sie Test.java
Kopieren Sie den Codecode wie folgt:
Paket com.yixi.proxy;
import java.lang.reflect.Proxy;
öffentlicher Klassentest {
public static void main(String[] args) {
TimeInvocationHandler timeHandler = new TimeInvocationHandler(new UserServiceImpl());
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.update(2);
u.save();
}
}
Hier nun die korrekte Ausgabe:
Kopieren Sie den Codecode wie folgt:
Startzeit: 1352879531334
Einen Benutzer aktualisieren 2
Endzeit: 1352879531334
Startzeit: 1352879531334
Benutzer speichern....
Endzeit: 1352879531335
Wenn Sie weniger Code benötigen, können Sie direkt eine anonyme Klasse schreiben:
Paket com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
öffentlicher Klassentest {
public static void main(String[] args) {
final UserServiceImpl usi = new UserServiceImpl();
UserService u = (UserService) Proxy.newProxyInstance(
usi.getClass().getClassLoader(),
usi.getClass().getInterfaces(),
neuer InvocationHandler() {
@Override
öffentlicher Objektaufruf (Objekt-Proxy, Methodenmethode, Objekt[]-Argumente)
wirft Throwable {
System.out.println("startTime : " +System.currentTimeMillis());
Objekt obj = method.invoke(usi, args);
System.out.println("endTime : " +System.currentTimeMillis());
return obj;
}
});
u.update(2);
u.save();
}
}
Da der erste Parameter in method.invoke(target,args); das Zielobjekt ist, warum benötigt die Invoke-Methode von invocationHandler einen Object-Proxy-Parameter? Schauen wir nach unten!
Für die wichtigste Aufrufmethode (meiner persönlichen Meinung nach) schauen wir uns an, was das JDK sagt:
Kopieren Sie den Codecode wie folgt:
aufrufen
Objektaufruf (Objekt-Proxy,
Methode Methode,
Object[] args)
throws Throwable verarbeitet Methodenaufrufe auf der Proxy-Instanz und gibt das Ergebnis zurück. Diese Methode wird im Aufrufhandler aufgerufen, wenn die Methode auf der Proxy-Instanz aufgerufen wird, der sie zugeordnet ist.
Parameter:
Proxy – die Proxy-Instanz, auf der die Methode aufgerufen wird
method – eine Methodeninstanz, die der auf der Proxy-Instanz aufgerufenen Schnittstellenmethode entspricht. Die deklarierende Klasse eines Methodenobjekts ist die Schnittstelle, in der die Methode deklariert wird. Dabei kann es sich um eine Superschnittstelle der Proxy-Schnittstelle handeln, von der die Proxy-Klasse die Methode erbt.
args – ein Array von Objekten, das die Argumentwerte enthält, die an den Methodenaufruf auf der Proxy-Instanz übergeben werden, oder null, wenn die Schnittstellenmethode keine Argumente akzeptiert. Parameter von Basistypen werden in Instanzen der entsprechenden Basis-Wrapper-Klasse verpackt (z. B. java.lang.Integer oder java.lang.Boolean).
Proxy – die Proxy-Instanz, auf der die Methode aufgerufen wird? Was bedeutet dieser Satz? Schauspielerei? Methode ist die Proxy-Methode? Sollte meine Methode zum Ausführen des Proxys dann nicht Object obj = method.invoke(proxy, args); sein? Ich habe mich damals nicht umgedreht und bin zu Google gegangen, konnte aber keine Inspiration finden. Ich dachte, ich schaue mir besser den Quellcode an und vielleicht könnte ich etwas sehen!
Öffnen Sie den Quellcode der Proxy-Klasse und finden Sie heraus, was ein Konstruktor ist:
Kopieren Sie den Codecode wie folgt:
protectedInvocationHandler h;
protected Proxy(InvocationHandler h) {
this.h = h;
}
Verwenden Sie InvocationHandler als Parameter des Proxy-Konstruktors ... Wofür wird InvocationHandler dann verwendet? Gibt es eine Verbindung mit der invoke()-Methode in InvocationHandler?
Mein erster Gedanke ist, dass Proxy intern die folgende Anweisung aufruft:
Kopieren Sie den Codecode wie folgt:
h.invoke(this,methodName,args);
Weil Sie die Aufrufmethode aufrufen müssen, um die entsprechende Methode auszuführen.
Werfen wir zunächst einen Blick darauf
Hier finden Sie etwas, das Sinn zu machen scheint: Wenn u.update(2), àProxy handler.invoke(proxyClass,update,2) à aufruft, was ProxyClass.update(2); bedeutet;
Wenn u.save(); àProxy handler.invoke(proxyClass,save,null) àdas heißt, ProxyClass.save();
Wenn Test.java wie folgt geändert wird:
Kopieren Sie den Codecode wie folgt:
öffentlicher Klassentest {
public static void main(String[] args) {
final UserServiceImpl usi = new UserServiceImpl();
UserService u = (UserService) Proxy.newProxyInstance(
usi.getClass().getClassLoader(),
usi.getClass().getInterfaces(),
neuer InvocationHandler() {
@Override
öffentlicher Objektaufruf (Objekt-Proxy, Methodenmethode, Objekt[]-Argumente)
wirft Throwable {
null zurückgeben;
}
});
u.update(2);
u.save();
}
}
Beachten Sie, dass die Methode der anonymen Klasse zu diesem Zeitpunkt null zurückgibt. Wenn Sie sie ausführen, werden Sie Folgendes feststellen:
Kopieren Sie den Codecode wie folgt:
Ausnahme im Thread „main“ java.lang.NullPointerException
bei $Proxy0.save(Unbekannte Quelle)
unter com.yixi.proxy.Test.main(Test.java:17)
In Zeile 17 gibt es einen Nullzeiger, das heißt, die Methode u.save() hat hier ein Nullelement. Könnte es sein, dass u leer ist? Das sollte nicht der Fall sein. Wenn u.update(2) dort eine Nullzeigerausnahme meldet, verschwindet die Ausnahme, was darauf hinweist, dass u.update() normal ausgeführt werden kann. Warum ist das so?
Aus diesem Grund gibt die Aufrufmethode tatsächlich null zurück:
Achten Sie auf die beiden Methoden in der UserService-Klasse:
Kopieren Sie den Codecode wie folgt:
öffentliche Schnittstelle UserService {
public int save();
public void update(int id);
}
Die Save()-Methode gibt einen int-Typ zurück und die update-Methode gibt einen void-Typ zurück. Gemäß der obigen Vermutung implementiert handler.invoke() ProxyClass.update(2); und die return-Methode in der invoke-Methode entspricht der return Wert der Proxy-Methode,
Wenn also die Aufrufmethode null zurückgibt, erhält die Aktualisierungsmethode des Agenten den Rückgabewert null und gibt ursprünglich void zurück, sodass keine Ausnahme gemeldet wird und der Agent save einen Wert vom Typ int zurückgeben muss. Was wir hier zurückgeben, ist immer noch null. und die JVM kann null nicht in den Typ int konvertieren, sodass eine Ausnahme gemeldet wird. Diese Erklärung kann klar erklärt werden und kann auch die vorherige Vermutung relativ beweisen.
Der erste Parameter-Proxy in der Aufrufmethode von InvocationHandler scheint nur dazu zu dienen, der Proxy-Klasse zu ermöglichen, die Referenz des Proxy-Objekts ProxyClass zu übergeben, wenn die Methode mit der Referenz ihres eigenen InvocationHandler-Objekts aufgerufen wird, um das Geschäft abzuschließen, das ProxyClass abschließen muss .
Literarisches Talent ist nicht gut! Die Möglichkeiten sind begrenzt! Ich hoffe, ihr könnt mich korrigieren...