Secara kebetulan hari ini, saya tiba-tiba ingin melihat proxy dinamis JDK, karena saya tahu sedikit tentangnya sebelumnya, dan saya hanya ingin menguji penggunaannya. Saya segera menulis antarmuka dan kelas ini:
Kelas antarmuka: UserService.java
Copy kode kodenya sebagai berikut:
paket com.yixi.proxy;
antarmuka publik Layanan Pengguna {
publik int simpan();
pembaruan kekosongan publik (int id);
}
Kelas implementasi: UserServiceImpl.java
Copy kode kodenya sebagai berikut:
paket com.yixi.proxy;
kelas publik UserServiceImpl mengimplementasikan UserService {
@Mengesampingkan
publik int simpan() {
System.out.println("simpan pengguna....");
kembali 1;
}
@Mengesampingkan
pembaruan kekosongan publik(int id) {
System.out.println("perbarui pengguna " + id);
}
}
Kemudian saya dengan cemas menulis InvocationHandler yang saya inginkan: fungsinya sangat sederhana, yaitu untuk mencatat waktu mulai dan waktu berakhirnya eksekusi metode.
TimeInvocationHandler.java
Copy kode kodenya sebagai berikut:
paket com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
kelas publik TimeInvocationHandler mengimplementasikan InvocationHandler {
@Mengesampingkan
pemanggilan Objek publik (Proksi objek, Metode metode, Objek[] args)
lemparan Dapat dilempar {
System.out.println("startTime : " +System.currentTimeMillis());
Objek obj = metode.invoke(proxy, args);
System.out.println("endTime : " +System.currentTimeMillis());
mengembalikan objek;
}
}
Setelah semua persiapan selesai, tentunya saatnya memulai tes menulis!
Tes.java
Copy kode kodenya sebagai berikut:
paket com.yixi.proxy;
import java.lang.reflect.Proxy;
Tes kelas publik {
public static void main(String[] args) { 9 TimeInvocationHandler timeHandler = new TimeInvocationHandler();
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.perbarui(2);
u.simpan();
}
}
Itu berjalan dengan gembira, tetapi tidak memberi Anda kesan apa pun. Hasilnya adalah pengecualian yang memenuhi layar:
Copy kode kodenya sebagai berikut:
Waktu mulai: 1352877835040
Waktu mulai: 1352877835040
Waktu mulai: 1352877835040
Pengecualian di thread "utama" java.lang.reflect.UndeclaredThrowableException
di $Proxy0.update(Sumber Tidak Dikenal)
di com.yixi.proxy.Test.main(Test.java:11)
Disebabkan oleh: java.lang.reflect.InvocationTargetException
di sun.reflect.NativeMethodAccessorImpl.invoke0(Metode Asli)
di sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
di sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
di java.lang.reflect.Method.invoke(Method.java:597)
di com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12)
... 2 lagi
Pengecualian com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12) dengan jelas memberitahukan masalah di baris 12 dari TimeInvocationHandle: yaitu
Copy kode kodenya sebagai berikut:
pemanggilan Objek publik (Proksi objek, Metode metode, Objek[] args)
lemparan Dapat dilempar {
System.out.println("startTime : " +System.currentTimeMillis());
Objek obj = metode.invoke(proxy, args);
System.out.println("endTime : " +System.currentTimeMillis());
mengembalikan objek;
}
Tidak ada yang salah dengan metodenya! Karena metode invoke() tampaknya menyediakan semua parameter yang diperlukan oleh metode.invoke(Object, Object[]), kami akan menggunakannya sebagai hal yang biasa. Jika Anda benar-benar berpikir seperti itu, maka Anda telah menipu JDK jebakan. Mari kita lihat cara menulis yang benar terlebih dahulu. Jika ada siswa yang tidak berminat membaca berikut ini, setidaknya beri saya solusi yang tepat:
Ubah TimeInvocationHandler.java
Copy kode kodenya sebagai berikut:
paket com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
kelas publik TimeInvocationHandler mengimplementasikan InvocationHandler {
Objek pribadi o;
publik TimeInvocationHandler(Objek o){
ini.o = o;
}
@Mengesampingkan
pemanggilan Objek publik (Proksi objek, Metode metode, Objek[] args)
lemparan Dapat dilempar {
System.out.println("startTime : " +System.currentTimeMillis());
Objek obj = metode.invoke(o, args);
System.out.println("endTime : " +System.currentTimeMillis());
mengembalikan objek;
}
}
Ubah Test.java
Copy kode kodenya sebagai berikut:
paket com.yixi.proxy;
import java.lang.reflect.Proxy;
Tes kelas publik {
public static void main(String[] args) {
TimeInvocationHandler timeHandler = TimeInvocationHandler baru(UserServiceImpl());
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.perbarui(2);
u.simpan();
}
}
Sekarang inilah keluaran yang benar:
Copy kode kodenya sebagai berikut:
Waktu mulai: 1352879531334
memperbarui pengguna 2
Waktu berakhir: 1352879531334
Waktu mulai: 1352879531334
simpan pengguna....
Waktu berakhir: 1352879531335
Jika Anda menginginkan lebih sedikit kode, Anda dapat menulis kelas anonim secara langsung:
paket com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
Tes kelas publik {
public static void main(String[] args) {
final UserServiceImpl usi = UserServiceImpl baru();
Layanan Pengguna u = (Layanan Pengguna) Proxy.newProxyInstance(
usi.getClass().getClassLoader(),
usi.getClass().getInterfaces(),
baru InvocationHandler() {
@Mengesampingkan
pemanggilan Objek publik (Proksi objek, Metode metode, Objek[] args)
lemparan Dapat dilempar {
System.out.println("startTime : " +System.currentTimeMillis());
Obj objek = metode.invoke(usi, args);
System.out.println("endTime : " +System.currentTimeMillis());
mengembalikan objek;
}
});
u.perbarui(2);
u.simpan();
}
}
Karena parameter pertama di method.invoke(target,args); adalah objek target, mengapa metode Invoke dari invocationHandler memerlukan parameter proxy Objek? Mari kita lihat ke bawah!
Untuk metode pemanggilan yang paling penting (menurut pendapat pribadi saya) mari kita lihat apa yang dikatakan JDK:
Copy kode kodenya sebagai berikut:
memohon
Pemanggilan objek (Proxy objek,
metode metode,
Objek[] argumen)
throws Throwable menangani pemanggilan metode pada instance proxy dan mengembalikan hasilnya. Metode ini dipanggil pada pengendali pemanggilan ketika metode ini dipanggil pada instans proksi yang terkait dengannya.
parameter:
proxy - contoh proksi tempat metode dipanggil
metode - contoh Metode yang sesuai dengan metode antarmuka yang dipanggil pada contoh proksi. Kelas yang mendeklarasikan objek Metode akan menjadi antarmuka di mana metode tersebut dideklarasikan, yang mungkin merupakan superinterface dari antarmuka proksi tempat kelas proksi mewarisi metode tersebut.
args - array objek yang berisi nilai argumen yang diteruskan ke pemanggilan metode pada instance proxy, atau null jika metode antarmuka tidak menggunakan argumen. Parameter tipe dasar dibungkus dalam instance kelas pembungkus dasar yang sesuai (seperti java.lang.Integer atau java.lang.Boolean).
proxy - contoh proxy tempat metode ini dipanggil? Apa arti kalimat ini? akting? metodenya adalah metode proxy? Maka bukankah metode saya untuk mengeksekusi proxy harus menjadi Object obj = method.invoke(proxy, args);? Saya tidak berbalik saat itu. Saya pergi ke grup diskusi dan membuka Google tetapi tidak menemukan inspirasi apa pun. Saya pikir sebaiknya saya melihat kode sumbernya dan mungkin saya bisa melihat sesuatu!
Buka kode sumber kelas Proxy dan cari tahu apa itu konstruktor:
Copy kode kodenya sebagai berikut:
protectedInvocationHandler h;
Proksi yang dilindungi(InvocationHandler h) {
ini.h = h;
}
Gunakan InvocationHandler sebagai parameter konstruktor Proxy.... Lalu untuk apa InvocationHandler digunakan? Apakah ada hubungan dengan metode invoke() di InvocationHandler?
Pikiran pertama saya adalah Proxy akan memanggil pernyataan berikut secara internal:
Copy kode kodenya sebagai berikut:
h.invoke(ini,namametode,args);
Karena Anda harus memanggil metode pemanggilan untuk menjalankan metode yang sesuai.
Mari kita lihat ini dulu
Di sini Anda akan menemukan sesuatu yang tampaknya masuk akal: ketika u.update(2), àProxy akan memanggil handler.invoke(proxyClass,update,2) à, yang berarti proxyClass.update(2);
Ketika u.save(); àProxy akan memanggil handler.invoke(proxyClass,save,null) àyaitu, proxyClass.save();
Ketika Test.java diubah menjadi ini:
Copy kode kodenya sebagai berikut:
Tes kelas publik {
public static void main(String[] args) {
final UserServiceImpl usi = UserServiceImpl baru();
Layanan Pengguna u = (Layanan Pengguna) Proxy.newProxyInstance(
usi.getClass().getClassLoader(),
usi.getClass().getInterfaces(),
baru InvocationHandler() {
@Mengesampingkan
pemanggilan Objek publik (Proksi objek, Metode metode, Objek[] args)
lemparan Dapat dilempar {
kembalikan nol;
}
});
u.perbarui(2);
u.simpan();
}
}
Perhatikan bahwa metode kelas anonim saat ini mengembalikan null. Jika Anda menjalankannya, Anda akan menemukan:
Copy kode kodenya sebagai berikut:
Pengecualian di thread "utama" java.lang.NullPointerException
di $Proxy0.save(Sumber Tidak Dikenal)
di com.yixi.proxy.Test.main(Test.java:17)
Ada penunjuk nol di baris 17, yaitu metode u.save() di sini memiliki elemen nol. Seharusnya tidak. Jika u adalah null, maka u.update(2) akan melaporkan pengecualian penunjuk nol di sana. Ketika saya mengomentari baris 17, pengecualian tersebut hilang, menunjukkan bahwa u.update() dapat dijalankan secara normal. Jadi mengapa demikian?
Faktanya, inilah sebabnya metode invoke mengembalikan null:
Perhatikan dua metode di kelas UserService:
Copy kode kodenya sebagai berikut:
antarmuka publik Layanan Pengguna {
publik int simpan();
pembaruan kekosongan publik (int id);
}
Metode Save() mengembalikan tipe int dan metode update mengembalikan tipe void sesuai dengan tebakan di atas, handler.invoke() mengimplementasikan proxyClass.update(2);, dan metode pengembalian dalam metode pemanggilan sesuai dengan Pengembaliannya. nilai metode proxy,
Jadi ketika metode pemanggilan mengembalikan null, metode pembaruan agen menerima nilai kembalian null, dan awalnya mengembalikan batal, jadi tidak ada pengecualian yang dilaporkan, dan penyimpanan agen harus mengembalikan nilai tipe int. Yang kami kembalikan di sini masih nol, dan JVM tidak dapat mengonversi null. Ini diubah menjadi tipe int, sehingga pengecualian dilaporkan. Penjelasan ini dapat dijelaskan dengan jelas, dan juga dapat membuktikan tebakan sebelumnya secara relatif.
Parameter pertama proxy dalam metode pemanggilan InvocationHandler tampaknya hanya mengizinkan kelas Proxy meneruskan referensi objek proxy proxyClass saat memanggil metode dengan referensi objek InvocationHandler miliknya sendiri untuk menyelesaikan bisnis yang perlu diselesaikan oleh proxyClass .
Bakat sastra itu tidak bagus! Kemampuannya terbatas! Saya harap kalian dapat mengoreksi saya...