Seperti yang kita ketahui Spring menggunakan proksi untuk menambah fungsionalitas ( @Transactional
dan@Scheduled
misalnya). Ada dua opsi - menggunakan proxy dinamis JDK (kelas harus mengimplementasikan antarmuka yang tidak kosong), atau menghasilkan kelas anak menggunakan generator kode CGLIB. Saya selalu berpikir bahwa proxyMode memungkinkan saya untuk memilih antara proxy dinamis JDK dan CGLIB.
Tetapi saya dapat membuat contoh yang menunjukkan bahwa asumsi saya salah:
Kasus 1:
Singleton:
@Service
public class MyBeanA {
@Autowired
private MyBeanB myBeanB;
public void foo() {
System.out.println(myBeanB.getCounter());
}
public MyBeanB getMyBeanB() {
return myBeanB;
}
}
Prototipe:
@Service
@Scope(value = "prototype")
public class MyBeanB {
private static final AtomicLong COUNTER = new AtomicLong(0);
private Long index;
public MyBeanB() {
index = COUNTER.getAndIncrement();
System.out.println("constructor invocation:" + index);
}
@Transactional // just to force Spring to create a proxy
public long getCounter() {
return index;
}
}
Utama:
MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());
Keluaran:
constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e
Di sini kita dapat melihat dua hal:
MyBeanB
hanya dipakai satu kali .- Untuk menambahkan
@Transactional
fungsiMyBeanB
, Spring menggunakan CGLIB.
Kasus 2:
Biarkan saya memperbaiki MyBeanB
definisi:
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
Dalam hal ini outputnya adalah:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2
Di sini kita dapat melihat dua hal:
MyBeanB
dipakai 3 kali.- Untuk menambahkan
@Transactional
fungsiMyBeanB
, Spring menggunakan CGLIB.
Bisakah Anda menjelaskan apa yang sedang terjadi? Bagaimana cara proxy benar-benar berfungsi?
PS
Saya sudah membaca dokumentasinya:
/**
* Specifies whether a component should be configured as a scoped proxy
* and if so, whether the proxy should be interface-based or subclass-based.
* <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
* that no scoped proxy should be created unless a different default
* has been configured at the component-scan instruction level.
* <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
* @see ScopedProxyMode
*/
tetapi tidak jelas bagi saya.
Memperbarui
Kasus 3:
Saya menyelidiki satu lagi kasus, di mana saya mengekstrak antarmuka dari MyBeanB
:
public interface MyBeanBInterface {
long getCounter();
}
@Service
public class MyBeanA {
@Autowired
private MyBeanBInterface myBeanB;
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
public class MyBeanB implements MyBeanBInterface {
dan dalam hal ini hasilnya adalah:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92
Di sini kita dapat melihat dua hal:
MyBeanB
dipakai 3 kali.- Untuk menambahkan
@Transactional
fungsiMyBeanB
, Spring menggunakan proxy dinamis JDK.
MyBeanB
kelas Anda tidak memperluas antarmuka apa pun, sehingga tidak mengejutkan bahwa log konsol Anda menunjukkan contoh proxy CGLIB. Dalam kasus 3 Anda memperkenalkan dan mengimplementasikan antarmuka, akibatnya Anda mendapatkan proxy JDK. Anda bahkan menggambarkan ini dalam teks pengantar Anda.<aop:config proxy-target-class="true">
atau@EnableAspectJAutoProxy(proxyTargetClass = true)
, masing-masing.Jawaban:
Proxy yang dihasilkan untuk
@Transactional
perilaku memiliki tujuan yang berbeda dari proxy yang dicakup.The
@Transactional
proxy yang membungkus kacang khusus untuk menambahkan perilaku manajemen sesi. Semua doa metode akan melakukan manajemen transaksi sebelum dan setelah mendelegasikan ke kacang yang sebenarnya.Jika Anda menggambarkannya, akan terlihat seperti
Untuk tujuan kami, Anda pada dasarnya dapat mengabaikan perilakunya (menghapus
@Transactional
dan Anda akan melihat perilaku yang sama, kecuali Anda tidak memiliki proxy cglib).The
@Scope
proxy yang berperilaku berbeda. Dokumentasi menyatakan:Apa yang sebenarnya dilakukan Spring adalah menciptakan definisi kacang tunggal untuk jenis pabrik yang mewakili proxy. Objek proxy yang sesuai, bagaimanapun, menanyakan konteks untuk kacang yang sebenarnya untuk setiap doa.
Jika Anda menggambarkannya, akan terlihat seperti
Karena
MyBeanB
ini adalah prototipe kacang, konteksnya akan selalu mengembalikan contoh baru.Untuk keperluan jawaban ini, anggap Anda telah mengambil
MyBeanB
langsung denganyang pada dasarnya adalah apa yang dilakukan Spring untuk memuaskan seorang
@Autowired
target injeksi.Dalam contoh pertama Anda,
Anda mendeklarasikan definisi kacang prototipe (melalui anotasi).
@Scope
mempunyai sebuahproxyMode
elemen yangJadi Spring tidak membuat proksi cakupan untuk kacang yang dihasilkan. Anda mengambil kacang itu dengan
Anda sekarang memiliki referensi ke
MyBeanB
objek baru yang dibuat oleh Spring. Ini seperti objek Java lainnya, pemanggilan metode akan langsung ke contoh yang direferensikan.Jika Anda menggunakan
getBean(MyBeanB.class)
lagi, Spring akan mengembalikan contoh baru, karena definisi bean adalah untuk a kacang prototipe . Anda tidak melakukan itu, jadi semua pemanggilan metode Anda pergi ke objek yang sama.Dalam contoh kedua Anda,
Anda mendeklarasikan proksi cakupan yang diimplementasikan melalui cglib. Saat meminta kacang jenis ini dari Spring with
Spring tahu itu
MyBeanB
adalah proksi scoped dan karenanya mengembalikan objek proksi yang memenuhi APIMyBeanB
(mis. Mengimplementasikan semua metode publiknya) yang secara internal tahu cara mengambil jenis kacang yang sebenarnyaMyBeanB
untuk setiap doa metode.Coba jalankan
Ini akan kembali
true
mengisyaratkan fakta bahwa Spring mengembalikan objek proxy tunggal (bukan kacang prototipe).Pada permohonan metode, di dalam implementasi proxy, Spring akan menggunakan
getBean
versi khusus yang tahu bagaimana membedakan antara definisi proxy danMyBeanB
definisi kacang yang sebenarnya . Itu akan mengembalikanMyBeanB
contoh baru (karena itu prototipe) dan Spring akan mendelegasikan pemanggilan metode melalui refleksi (klasikMethod.invoke
).Contoh ketiga Anda pada dasarnya sama dengan yang kedua.
sumber
context.getBean(MyBeanB.class)
, Anda tidak benar-benar mendapatkan proxy, Anda mendapatkan kacang yang sebenarnya.@Autowired
mendapatkan proxy (sebenarnya itu akan gagal jika Anda menyuntikkanMyBeanB
bukan tipe antarmuka). Saya tidak tahu mengapa Spring memungkinkan Anda melakukangetBean(MyBeanB.class)
dengan INTERFACES.@Transactional
. Dengan@Autowired MyBeanBInterface
dan cakupan proxy, Spring akan menyuntikkan objek proxy. Jika Anda hanya melakukannyagetBean(MyBeanB.class)
, Spring tidak akan mengembalikan proxy, itu akan mengembalikan kacang target.