वसंत में एक स्कॉप्ड प्रॉक्सी क्या है?


21

जैसा कि हम जानते हैं कि वसंत कार्यक्षमता जोड़ने के लिए परदे के पीछे का उपयोग करता है ( @Transactionalऔर @Scheduledउदाहरण के लिए)। दो विकल्प हैं - जेडीके डायनेमिक प्रॉक्सी (कक्षा में गैर-रिक्त इंटरफेस को लागू करना है) का उपयोग करना, या सीजीएलआईबी कोड जनरेटर का उपयोग करके एक बाल वर्ग उत्पन्न करना। मैंने हमेशा सोचा कि प्रॉक्सीमोड मुझे JDK डायनेमिक प्रॉक्सी और CGLIB के बीच चयन करने की अनुमति देता है।

लेकिन मैं एक उदाहरण बनाने में सक्षम था जो दर्शाता है कि मेरी धारणा गलत है:

मामला एक:

सिंगलटन:

@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केवल एक बार त्वरित किया गया था ।
  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. MyBeanB3 बार त्वरित किया गया था ।
  2. के लिए @Transactionalकार्यक्षमता जोड़ने के लिए MyBeanB, स्प्रिंग ने CGLIB का उपयोग किया।

क्या आप बता सकते हैं कि क्या चल रहा है? प्रॉक्सी मोड वास्तव में कैसे काम करता है?

पुनश्च

मैंने प्रलेखन पढ़ा है:

/**
 * 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. MyBeanB3 बार त्वरित किया गया था ।
  2. के लिए @Transactionalकार्यक्षमता जोड़ने के लिए MyBeanB, स्प्रिंग ने JDK डायनेमिक प्रॉक्सी का उपयोग किया।

कृपया हमें अपना ट्रांसेक्शनल कॉन्फ़िगरेशन दिखाएं।
सोतीरियो डेलिमोलिसिन

@SotiriosDelimanolis मेरे पास कोई विशेष विन्यास नहीं है
gstackoverflow

मैं फली हुई फलियों या वसंत या जेईई में निहित अन्य प्रकार के उद्यम ढांचे के जादू के बारे में नहीं जानता। @SotiriosDelimanolis ने उस सामान के बारे में एक अद्भुत उत्तर लिखा, मैं केवल JDK बनाम CGLIB परदे के पीछे टिप्पणी करना चाहता हूं: 1 और 2 मामलों में आपकी MyBeanBकक्षा किसी भी इंटरफेस का विस्तार नहीं करती है, इसलिए यह आश्चर्यजनक नहीं है कि आपका कंसोल CGLIB प्रॉक्सी उदाहरण दिखाता है। 3 मामले में आप एक इंटरफ़ेस लागू करते हैं और लागू करते हैं, फलस्वरूप आपको JDK प्रॉक्सी मिलता है। आप अपने परिचयात्मक पाठ में भी इसका वर्णन करते हैं।
क्रेजीटेक्स

तो गैर-इंटरफ़ेस प्रकारों के लिए आपके पास वास्तव में कोई विकल्प नहीं है, उन्हें CGLIB परदे के पीछे होना चाहिए क्योंकि JDK परदे के पीछे केवल इंटरफ़ेस प्रकारों के लिए काम करते हैं। हालांकि आप स्प्रिंग AOP का उपयोग करते समय इंटरफ़ेस प्रकार के लिए भी CGLIB परदे के पीछे लागू कर सकते हैं। यह क्रमशः, <aop:config proxy-target-class="true">या के माध्यम से कॉन्फ़िगर किया @EnableAspectJAutoProxy(proxyTargetClass = true)गया है।
बजे क्रायजैक्स

@kriegaex क्या आप यह कहना चाहते हैं कि Aspectj प्रॉक्सी पीढ़ी के लिए CGlib का उपयोग करता है?
gstackoverflow

जवाबों:


10

@Transactionalव्यवहार के लिए उत्पन्न प्रॉक्सी , स्कॉप्ड प्रॉक्सी की तुलना में एक अलग उद्देश्य प्रदान करती है।

@Transactionalप्रॉक्सी एक है कि सत्र प्रबंधन व्यवहार को जोड़ने के लिए विशिष्ट सेम लपेटता है। सभी विधि चालान वास्तविक बीन को सौंपने से पहले और बाद में लेनदेन प्रबंधन का प्रदर्शन करेंगे।

यदि आप इसे चित्रित करते हैं, तो यह ऐसा लगेगा

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

हमारे उद्देश्यों के लिए, आप अनिवार्य रूप से इसके व्यवहार को अनदेखा कर सकते हैं (हटाएं @Transactionalऔर आपको समान व्यवहार देखना चाहिए, सिवाय इसके कि आपके पास cglib प्रॉक्सी नहीं है)।

@Scopeप्रॉक्सी अलग ढंग से व्यवहार करता है। प्रलेखन में कहा गया है:

[...] आपको एक प्रॉक्सी ऑब्जेक्ट को इंजेक्ट करने की आवश्यकता है जो एक ही सार्वजनिक इंटरफ़ेस को स्कॉप्ड ऑब्जेक्ट के रूप में उजागर करता है, लेकिन यह वास्तविक गुंजाइश से प्रासंगिक लक्ष्य (जैसे HTTP अनुरोध) और वास्तविक विधि पर कॉल प्रतिनिधि विधि कॉल को भी पुनः प्राप्त कर सकता है

वसंत वास्तव में क्या कर रहा है एक प्रकार की फैक्ट्री के लिए एक सिंगलटन बीन परिभाषा बना रहा है जो प्रॉक्सी का प्रतिनिधित्व करता है। हालांकि, संबंधित प्रॉक्सी वस्तु, हर आह्वान के लिए वास्तविक बीन के लिए संदर्भ पर सवाल उठाती है।

यदि आप इसे चित्रित करते हैं, तो यह ऐसा लगेगा

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 { 

आप एक प्रोटोटाइप बीन परिभाषा (एनोटेशन के माध्यम से) घोषित करते हैं । @Scopeएक proxyModeतत्व है जो

निर्दिष्ट करता है कि क्या घटक को स्कूप्ड प्रॉक्सी के रूप में कॉन्फ़िगर किया जाना चाहिए और यदि ऐसा है, तो क्या प्रॉक्सी को इंटरफ़ेस-आधारित या उपवर्ग आधारित होना चाहिए।

डिफॉल्ट्स ScopedProxyMode.DEFAULT, जो आमतौर पर इंगित करता है कि कोई स्कैन किए गए प्रॉक्सी को तब तक नहीं बनाया जाना चाहिए जब तक कि घटक-स्कैन अनुदेश स्तर पर एक अलग डिफ़ॉल्ट को कॉन्फ़िगर नहीं किया गया हो।

इसलिए स्प्रिंग परिणामस्वरूप फलियों के लिए एक स्कूप्ड प्रॉक्सी नहीं बना रहा है। आप उस बीन को पुनः प्राप्त करते हैं

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

अब आपके पास MyBeanBस्प्रिंग द्वारा बनाई गई एक नई वस्तु का संदर्भ है । यह किसी भी अन्य जावा ऑब्जेक्ट की तरह है, विधि इनवोकेशन सीधे संदर्भित उदाहरण पर जाएगा।

यदि आपने getBean(MyBeanB.class)फिर से उपयोग किया , तो स्प्रिंग एक नया उदाहरण लौटाएगा, क्योंकि बीन की परिभाषा एक प्रोटोटाइप बीन के लिए है । आप ऐसा नहीं कर रहे हैं, इसलिए आपके सभी विधि चालान एक ही वस्तु पर जाते हैं।


अपने दूसरे उदाहरण में,

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

आप एक स्कॉप्ड प्रॉक्सी घोषित करते हैं, जिसे cglib के माध्यम से कार्यान्वित किया जाता है। वसंत के साथ इस प्रकार की एक बीन का अनुरोध करते समय

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

स्प्रिंग जानता है कि MyBeanBएक स्कूप्ड प्रॉक्सी है और इसलिए एक प्रॉक्सी ऑब्जेक्ट देता है जो एपीआई को संतुष्ट करता है MyBeanB(यानी अपने सभी सार्वजनिक तरीकों को लागू करता है) जो आंतरिक रूप से जानता है कि MyBeanBप्रत्येक विधि के आह्वान के लिए वास्तविक बीन प्रकार कैसे प्राप्त करें ।

दौड़ने की कोशिश करो

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

यह इस trueतथ्य की ओर संकेत करता है कि स्प्रिंग एक सिंगलटन प्रॉक्सी ऑब्जेक्ट (एक प्रोटोटाइप बीन नहीं) लौटा रहा है।

प्रॉक्सी के कार्यान्वयन के अंदर एक विधि के आह्वान पर, स्प्रिंग एक विशेष getBeanसंस्करण का उपयोग करेगा जो जानता है कि प्रॉक्सी परिभाषा और वास्तविक MyBeanBमूंग परिभाषा के बीच अंतर कैसे किया जाए । यह एक नया MyBeanBउदाहरण लौटाएगा (क्योंकि यह एक प्रोटोटाइप है) और स्प्रिंग प्रतिबिंब (क्लासिक Method.invoke) के माध्यम से इसे करने के लिए विधि आह्वान को सौंप देगा ।


आपका तीसरा उदाहरण अनिवार्य रूप से आपके दूसरे के समान है।


तो seсond मामले के लिए मेरे पास 2 प्रॉक्सी हैं: scoped_proxy जो कि transactional_proxy को लपेटता है जो प्राकृतिक MyBeanB_bean को लपेटता है ? scoped_proxy -> transactional_proxy -> MyBeanB_bean
gstackoverflow

क्या लेनदेन के लिए scoped_proxy और JDK_Dynamic_proxy के लिए CGLIB प्रॉक्सी होना संभव है?
gstackoverflow

1
@gstackoverflow जब आप करते हैं context.getBean(MyBeanB.class), तो आप वास्तव में प्रॉक्सी नहीं प्राप्त कर रहे हैं, आप वास्तविक बीन प्राप्त कर रहे हैं। @Autowiredयदि आप MyBeanBइंटरफ़ेस प्रकार के बजाय इंजेक्ट करते हैं तो यह प्रॉक्सी को प्राप्त होता है (वास्तव में यह विफल हो जाएगा )। मुझे नहीं पता कि वसंत आपको getBean(MyBeanB.class)इंटरफैक्स के साथ क्यों करने देता है ।
सोतीरियोस डेलिमोलिसिस

1
@gstackoverflow के बारे में भूल जाओ @Transactional। साथ @Autowired MyBeanBInterfaceऔर स्कूप वाली परदे के पीछे, स्प्रिंग प्रॉक्सी ऑब्जेक्ट को इंजेक्ट करेगा। यदि आप अभी भी करते हैं getBean(MyBeanB.class), तो स्प्रिंग प्रॉक्सी को वापस नहीं करेगा, यह लक्ष्य बीन वापस कर देगा।
सोतीरियोस डेलिमोलिसिस

1
यह ध्यान देने योग्य है कि यह एक प्रतिनिधिमंडल पैटर्न है, जो कि बीन्स के संबंध में स्प्रिंग के साथ है
स्टीफन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.