Baharda kapsamlı vekil nedir?


21

Bildiğimiz gibi Spring, işlevsellik eklemek için vekilleri kullanıyor ( @Transactionalve @Scheduledörneğin). İki seçenek vardır - JDK dinamik proxy kullanma (sınıfın boş olmayan arabirimler uygulaması gerekir) veya CGLIB kod üretecini kullanarak bir alt sınıf oluşturma. Her zaman proxyMode'un JDK dinamik proxy ve CGLIB arasında seçim yapmamı sağladığını düşündüm.

Ancak varsayımımın yanlış olduğunu gösteren bir örnek oluşturabildim:

Dava 1:

Singleton:

@Service
public class MyBeanA {
    @Autowired
    private MyBeanB myBeanB;

    public void foo() {
        System.out.println(myBeanB.getCounter());
    }

    public MyBeanB getMyBeanB() {
        return myBeanB;
    }
}

Prototip:

@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;
    }
}

Ana:

MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());

Çıktı:

constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e

Burada iki şey görebiliriz:

  1. MyBeanByalnızca bir kez başlatıldı .
  2. İçin @Transactionalişlevsellik eklemek için MyBeanB, Bahar CGLIB kullandı.

Durum 2:

MyBeanBTanımı düzeltmeme izin verin :

@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {

Bu durumda çıktı:

constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2

Burada iki şey görebiliriz:

  1. MyBeanB3 kez başlatıldı .
  2. İçin @Transactionalişlevsellik eklemek için MyBeanB, Bahar CGLIB kullandı.

Neler olduğunu açıklayabilir misiniz? Proxy modu gerçekten nasıl çalışır?

PS

Belgeleri okudum:

/**
 * 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
 */

ama benim için net değil.

Güncelleme

Durum 3:

Arayüzü buradan çıkardığım bir vaka daha araştırdım 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 {

ve bu durumda çıktı:

constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92

Burada iki şey görebiliriz:

  1. MyBeanB3 kez başlatıldı .
  2. İçin @Transactionalişlevsellik eklemek için MyBeanB, Spring bir JDK dinamik proxy kullandı.

Lütfen bize işlem yapılandırmanızı gösterin.
Sotirios Delimanolis

@SotiriosDelimanolis Özel bir konfigürasyonum yok
gstackoverflow

Kapsamlı fasulye veya Spring veya JEE'de bulunan diğer kurumsal çerçeve sihrini bilmiyorum. @SotiriosDelimanolis bu konuda harika bir cevap yazdı, sadece JDK ve CGLIB proxy'leri hakkında yorum yapmak istiyorum: 1 ve 2 numaralı durumlarda MyBeanBsınıfınız herhangi bir arabirimi genişletmez, bu nedenle konsol günlüğünüzün CGLIB proxy örneklerini göstermesi şaşırtıcı değildir. 3. bir arabirim tanıtır ve uygularsanız, sonuç olarak bir JDK proxy'si alırsınız. Bunu tanıtım metninizde bile tanımlayabilirsiniz.
kriegaex

Arayüz olmayan türler için gerçekten seçeneğiniz yok, bunların CGLIB proxy'leri olması gerekir, çünkü JDK proxy'leri sadece arayüz tipleri için çalışır. Ancak Spring AOP kullanırken arabirim türleri için bile CGLIB proxy'lerini uygulayabilirsiniz. Bu , sırasıyla <aop:config proxy-target-class="true">veya aracılığıyla yapılandırılır @EnableAspectJAutoProxy(proxyTargetClass = true).
kriegaex

@kriegaex Aspectj'in proxy oluşturmak için CGlib kullandığını söylemek ister misiniz?
gstackoverflow

Yanıtlar:


10

@TransactionalDavranış için oluşturulan vekil , kapsam dahilindeki proxy'lerden farklı bir amaca hizmet eder.

@TransactionalProxy oturum yönetimi davranışını eklemek için belirli fasulye sarar biridir. Tüm yöntem çağrıları, gerçek çekirdeğe devredilmeden önce ve sonra işlem yönetimini gerçekleştirecektir.

Eğer örneklersen,

main -> getCounter -> (cglib-proxy -> MyBeanB)

Bizim amacımız için, davranışını görmezden gelebilirsiniz (kaldır @Transactionalve aynı davranışı görmelisin, cglib proxy'sine sahip değilsen).

@ScopeProxy farklı davranır. Belgeler şunları ifade eder:

[...] Kapsamlı nesne ile aynı ortak arabirimi açığa çıkaran, ancak gerçek hedef nesneyi ilgili kapsamdan (HTTP isteği gibi) alabilen ve yöntem çağrılarını gerçek nesneye delege edebilen bir proxy nesnesi enjekte etmeniz gerekir. .

Spring'in gerçekte yaptığı, proxy'yi temsil eden bir fabrika türü için tekli bir fasulye tanımı yaratmaktır. Bununla birlikte, karşılık gelen proxy nesnesi, her çağrı için gerçek fasulye bağlamını sorgular.

Eğer örneklersen,

main -> getCounter -> (cglib-scoped-proxy -> context/bean-factory -> new MyBeanB)

Yana MyBeanBbir prototip fasulye olduğunu bağlam her zaman yeni bir örneğini döndürür.

Bu cevabın amaçları doğrultusunda, MyBeanBdoğrudan

MyBeanB beanB = context.getBean(MyBeanB.class);

ki bu, Spring'in bir @Autowiredenjeksiyon hedefini karşılamak için yaptığı şeydir .


İlk örneğinizde,

@Service
@Scope(value = "prototype")
public class MyBeanB { 

Bir prototip fasulye tanımı bildirirsiniz (ek açıklamalar aracılığıyla). @Scopebir proxyModeelemanı olan

Bir bileşenin kapsamlı proxy olarak yapılandırılıp yapılandırılmayacağını ve varsa proxy'nin arabirim tabanlı mı, alt sınıf tabanlı mı olacağını belirtir.

Varsayılan olarak ScopedProxyMode.DEFAULT, bileşen tarama yönergesi düzeyinde farklı bir varsayılan yapılandırılmadığı sürece , genel olarak hiçbir proxy oluşturulmaması gerektiğini belirtir .

Yani Spring, ortaya çıkan fasulye için kapsamlı bir proxy oluşturmuyor. Şu fasulyeyi

MyBeanB beanB = context.getBean(MyBeanB.class);

Artık MyBeanBSpring tarafından oluşturulan yeni bir nesneye referansınız var . Bu, diğer Java nesnelerine benzer, yöntem çağrıları doğrudan başvurulan örneğe gider.

Eğer kullanılırsa getBean(MyBeanB.class)tekrar fasulye tanımı bir içindir çünkü, Bahar, yeni bir örneğini dönecekti prototip fasulyesi . Bunu yapmıyorsunuz, bu nedenle tüm yöntem çağrılarınız aynı nesneye gidiyor.


İkinci örneğinizde,

@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {

cglib aracılığıyla uygulanan kapsamlı bir proxy bildirirsiniz. Spring'den bu tür bir fasulye talep ederken

MyBeanB beanB = context.getBean(MyBeanB.class);

Spring bunun MyBeanBkapsamlı bir proxy olduğunu bilir ve bu nedenle MyBeanBdahili MyBeanBolarak her yöntem çağrısı için gerçek bir fasulye türünün nasıl alınacağını bilen (yani tüm genel yöntemlerini uygulayan) API'sini karşılayan bir proxy nesnesi döndürür .

Koşmayı dene

System.out.println("singleton?: " + (context.getBean(MyBeanB.class) == context.getBean(MyBeanB.class)));

Bu, trueSpring'in bir prototip fasulye değil tek bir proxy nesnesi döndürdüğüne dair ipucu verecektir .

Bir yöntem çağırma işleminde, proxy uygulaması içinde, Spring getBeanvekil tanım ile gerçek MyBeanBfasulye tanımını nasıl ayıracağını bilen özel bir sürüm kullanır . Bu yeni bir MyBeanBörnek döndürür (bir prototip olduğu için) ve Spring yöntem çağırma yöntemini yansıma (klasik Method.invoke) aracılığıyla temsil eder .


Üçüncü örneğiniz aslında ikincinizle aynıdır.


Yani ikinci durumda 2 proxy var: doğal MyBeanB_bean saran transactional_proxy saran scoped_proxy ? scoped_proxy -> transactional_proxy -> MyBeanB_bean
gstackoverflow

Scoped_proxy için CGLIB proxy ve transactiona_proxy için JDK_Dynamic_proxy olması mümkün mü?
gstackoverflow

1
@gstackoverflow Bunu yaptığınızda context.getBean(MyBeanB.class), aslında proxy almıyorsunuz, asıl çekirdeği alıyorsunuz. @Autowiredproxy'yi alır (aslında MyBeanBarabirim türü yerine enjekte ederseniz başarısız olur ). Spring'in getBean(MyBeanB.class)ARAYÜZLERLE neden yapmasına izin verdiğini bilmiyorum .
Sotirios Delimanolis

1
@gstackoverflow Unut gitsin @Transactional. İle @Autowired MyBeanBInterfacekapsamlı Proxy ve Bahar vekil nesne enjekte edecektir. getBean(MyBeanB.class)Ancak bunu yaparsanız , Spring proxy'yi döndürmez, hedef fasulyeyi döndürür.
Sotirios Delimanolis

1
Bunun Bahar İçindeki Fasulye ile ilgili bir heyet örneği uygulaması olduğunu belirtmek gerekir
Stephan
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.