พร็อกซีที่ถูกกำหนดขอบเขตใน Spring คืออะไร


21

ดังที่เราทราบ Spring ใช้พร็อกซีเพื่อเพิ่มฟังก์ชันการทำงาน ( @Transactionalและ@Scheduledตัวอย่าง) มีสองตัวเลือก - การใช้ JDK ไดนามิกพร็อกซี่ (คลาสต้องใช้อินเตอร์เฟสที่ไม่ว่าง) หรือสร้างคลาสย่อยโดยใช้ตัวสร้างโค้ด CGLIB ฉันคิดเสมอว่า proxyMode ช่วยให้ฉันสามารถเลือกระหว่าง JDK dynamic proxy และ CGLIB

แต่ฉันสามารถสร้างตัวอย่างที่แสดงว่าข้อสันนิษฐานของฉันไม่ถูกต้อง:

กรณีที่ 1:

ซิงเกิล:

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

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

    public MyBeanB getMyBeanB() {
        return myBeanB;
    }
}

ต้นแบบ:

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

หลัก:

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

เอาท์พุท:

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

ที่นี่เราสามารถเห็นสองสิ่ง:

  1. MyBeanBถูก instantiated เพียงครั้งเดียว
  2. เพื่อเพิ่ม@Transactionalฟังก์ชันการทำงานสำหรับMyBeanBฤดูใบไม้ผลิใช้ CGLIB

กรณีที่ 2:

ให้ฉันแก้ไขMyBeanBคำนิยาม:

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

ในกรณีนี้ผลลัพธ์คือ:

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

ที่นี่เราสามารถเห็นสองสิ่ง:

  1. MyBeanBถูกยกตัวอย่าง3ครั้ง
  2. เพื่อเพิ่ม@Transactionalฟังก์ชันการทำงานสำหรับMyBeanBฤดูใบไม้ผลิใช้ CGLIB

คุณช่วยอธิบายสิ่งที่เกิดขึ้นได้ไหม โหมดพร็อกซี่ทำงานอย่างไร

PS

ฉันอ่านเอกสารแล้ว:

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

แต่มันไม่ชัดเจนสำหรับฉัน

ปรับปรุง

กรณีที่ 3:

ฉันตรวจสอบอีกหนึ่งกรณีซึ่งฉันได้แยกอินเทอร์เฟซจาก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 {

และในกรณีนี้ผลลัพธ์คือ:

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

ที่นี่เราสามารถเห็นสองสิ่ง:

  1. MyBeanBถูกยกตัวอย่าง3ครั้ง
  2. ในการเพิ่ม@Transactionalฟังก์ชั่นการMyBeanBใช้งาน Spring ใช้ JDK dynamic proxy

โปรดแสดงการกำหนดค่าธุรกรรมของคุณให้เราทราบ
Sotirios Delimanolis

@SotiriosDelimanolis ฉันไม่มีการกำหนดค่าพิเศษ
gstackoverflow

ฉันไม่รู้เกี่ยวกับการกำหนดขอบเขตของถั่วหรือสิ่งอื่นใดที่อยู่ใน Spring หรือ JEE @SotiriosDelimanolis เขียนคำตอบที่ยอดเยี่ยมเกี่ยวกับสิ่งนั้นฉันต้องการแสดงความคิดเห็นเฉพาะใน JDK เทียบกับพร็อกซี CGLIB: ในกรณีที่ 1 และ 2 MyBeanBคลาสของคุณไม่ได้เพิ่มส่วนต่อประสานใด ๆ ดังนั้นจึงไม่น่าแปลกใจที่บันทึกคอนโซลของคุณ ในกรณีที่ 3 คุณแนะนำและใช้อินเทอร์เฟซดังนั้นคุณจะได้รับ JDK proxy คุณยังอธิบายสิ่งนี้ในข้อความเกริ่นนำของคุณ
kriegaex

ดังนั้นสำหรับประเภทที่ไม่ใช่ส่วนต่อประสานที่คุณไม่มีทางเลือกพวกเขาต้องเป็น CGLIB proxies เพราะ JDK proxies ใช้งานได้กับประเภทอินเตอร์เฟสเท่านั้น อย่างไรก็ตามคุณสามารถบังคับใช้ CGLIB proxies ได้แม้จะเป็นประเภทอินเตอร์เฟสเมื่อใช้ Spring AOP นี่คือการกำหนดค่าผ่าน<aop:config proxy-target-class="true">หรือ@EnableAspectJAutoProxy(proxyTargetClass = true)ตามลำดับ
kriegaex

@kriegaex คุณต้องการที่จะบอกว่า Aspectj ใช้ CGlib สำหรับการสร้างพร็อกซี่หรือไม่?
gstackoverflow

คำตอบ:


10

พร็อกซีที่สร้างขึ้นสำหรับ@Transactionalพฤติกรรมมีวัตถุประสงค์ที่แตกต่างจากพร็อกซีที่ถูกกำหนดขอบเขต

@Transactionalพร็อกซี่เป็นหนึ่งในที่ล้อมถั่วที่เฉพาะเจาะจงเพื่อเพิ่มพฤติกรรมการจัดการเซสชั่น การเรียกใช้เมธอดทั้งหมดจะดำเนินการจัดการธุรกรรมก่อนและหลังการมอบหมายให้กับ bean จริง

หากคุณแสดงให้เห็นมันก็จะดูเหมือน

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

สำหรับวัตถุประสงค์ของเราคุณสามารถเพิกเฉยต่อพฤติกรรมของมัน (ลบ@Transactionalแล้วคุณจะเห็นพฤติกรรมเดียวกันยกเว้นคุณจะไม่มีพร็อกซี cglib)

การทำงานของ@Scopeพร็อกซีแตกต่างกัน เอกสารประกอบฯ :

[... ] คุณต้องฉีดวัตถุพร็อกซีที่เปิดเผยอินเทอร์เฟซสาธารณะเดียวกันกับวัตถุที่กำหนดขอบเขตแต่ยังสามารถดึงวัตถุเป้าหมายจริงจากขอบเขตที่เกี่ยวข้อง (เช่นการร้องขอ HTTP) และวิธีการมอบหมายบนวัตถุจริง .

สิ่งที่ฤดูใบไม้ผลิกำลังทำจริง ๆ คือการสร้างคำจำกัดความถั่วเดี่ยวสำหรับประเภทของโรงงานที่เป็นตัวแทนของพร็อกซี อย่างไรก็ตามวัตถุพร็อกซีที่สอดคล้องกันจะสอบถามบริบทสำหรับ bean จริงสำหรับการเรียกใช้ทุกครั้ง

หากคุณแสดงให้เห็นมันก็จะดูเหมือน

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

เนื่องจากMyBeanBเป็นถั่วต้นแบบบริบทจะส่งคืนอินสแตนซ์ใหม่เสมอ

สำหรับจุดประสงค์ของคำตอบนี้สมมติว่าคุณได้รับข้อมูลMyBeanBโดยตรงด้วย

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

ซึ่งเป็นสิ่งที่สปริงทำเพื่อสนอง@Autowiredเป้าหมายการฉีด


ในตัวอย่างแรกของคุณ

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

คุณประกาศนิยามbean ต้นแบบ (ผ่านหมายเหตุประกอบ) @ScopeมีproxyModeองค์ประกอบที่

ระบุว่าควรกำหนดค่าคอมโพเนนต์เป็นพร็อกซีที่มีขอบเขตและถ้าเป็นเช่นนั้นไม่ว่าพร็อกซีควรเป็นแบบอิงอินเตอร์เฟสหรือใช้คลาสย่อย

ค่าเริ่มScopedProxyMode.DEFAULTต้นซึ่งโดยทั่วไปจะระบุว่าไม่ควรสร้างพรอกซีพร็อกซีที่มีขอบเขตยกเว้นว่ามีการกำหนดค่าเริ่มต้นที่แตกต่างกันในระดับคำสั่งการสแกนส่วนประกอบ

ดังนั้น Spring ไม่ได้สร้างพร็อกซีที่มีการกำหนดขอบเขตสำหรับ bean ที่เป็นผลลัพธ์ คุณดึงข้อมูล bean นั้นด้วย

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

ตอนนี้คุณมีการอ้างอิงถึงMyBeanBวัตถุใหม่ที่สร้างโดย Spring นี่เหมือนกับวัตถุ Java อื่น ๆ การเรียกใช้เมธอดจะไปยังอินสแตนซ์ที่อ้างอิงโดยตรง

หากคุณใช้getBean(MyBeanB.class)อีกครั้งในฤดูใบไม้ผลิจะกลับตัวอย่างใหม่ตั้งแต่นิยามถั่วเป็นสำหรับถั่วต้นแบบ คุณไม่ได้ทำเช่นนั้นดังนั้นการเรียกใช้เมธอดทั้งหมดของคุณจึงไปที่วัตถุเดียวกัน


ในตัวอย่างที่สองของคุณ

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

คุณประกาศพร็อกซีที่ถูกกำหนดขอบเขตซึ่งถูกนำไปใช้ผ่าน cglib เมื่อขอถั่วประเภทนี้จาก Spring ด้วย

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

Spring รู้ว่าMyBeanBเป็นพร็อกซีที่ถูกกำหนดขอบเขตดังนั้นจึงส่งคืนพร็อกซีออบเจ็กต์ที่ตรงตาม API ของMyBeanB(เช่นใช้วิธีสาธารณะทั้งหมด) ที่ภายในรู้วิธีดึงข้อมูลถั่วชนิดจริงMyBeanBสำหรับการเรียกใช้แต่ละวิธี

ลองวิ่ง

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

นี่จะเป็นการtrueบอกใบ้ถึงข้อเท็จจริงที่ว่า Spring กำลังส่งคืนออบเจกต์พร็อกซีซิงเกิล (ไม่ใช่บีนต้นแบบ)

ในการเรียกใช้เมธอดภายในการปรับใช้พร็อกซีสปริงจะใช้getBeanเวอร์ชันพิเศษที่รู้วิธีแยกแยะระหว่างการกำหนดพร็อกซีและการMyBeanBกำหนดถั่วจริง นั่นจะส่งคืนMyBeanBอินสแตนซ์ใหม่(เนื่องจากเป็นเครื่องต้นแบบ) และ Spring จะมอบหมายวิธีการเรียกใช้ผ่านการสะท้อน (แบบดั้งเดิมMethod.invoke)


ตัวอย่างที่สามของคุณเป็นหลักเหมือนกับที่สองของคุณ


ดังนั้นสำหรับกรณีที่ฉันมี 2 ผู้รับมอบฉันทะ: scoped_proxy ซึ่ง wraps transactional_proxy ซึ่ง wraps MyBeanB_beanธรรมชาติ? scoped_proxy -> transactional_proxy -> MyBeanB_bean
gstackoverflow

เป็นไปได้หรือไม่ที่จะมีพร็อกซี CGLIB สำหรับ scoped_proxy และ JDK_Dynamic_proxy สำหรับ transactiona_proxy
gstackoverflow

1
@gstackoverflow เมื่อคุณทำเช่นcontext.getBean(MyBeanB.class)นั้นคุณไม่ได้รับพรอกซีคุณจะได้รับ bean จริง @Autowiredรับพร็อกซี่ (อันที่จริงแล้วมันจะล้มเหลวหากคุณฉีดMyBeanBแทนประเภทอินเตอร์เฟส) ฉันไม่รู้ว่าทำไม Spring ให้คุณทำgetBean(MyBeanB.class)กับ INTERFACES
Sotirios Delimanolis

1
@gstackoverflow @Transactionalลืมเกี่ยวกับ เมื่อใช้พร@Autowired MyBeanBInterfaceอกซีที่มีและกำหนดขอบเขตสปริงจะฉีดอ็อบเจ็กต์พร็อกซี หากคุณทำเช่นgetBean(MyBeanB.class)นั้น Spring จะไม่ส่งคืนพร็อกซีมันจะส่งคืน bean เป้าหมาย
Sotirios Delimanolis

1
เป็นที่น่าสังเกตว่านี่เป็นการใช้รูปแบบของคณะผู้แทนในเรื่องถั่วในฤดูใบไม้ผลิ
เตฟาน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.