स्प्रिंग जावा कॉन्फिगरेशन: आप रनटाइम तर्कों के साथ एक प्रोटोटाइप-स्कोप @ बीन कैसे बनाते हैं?


134

स्प्रिंग के जावा कॉन्फिगरेशन का उपयोग करते हुए, मुझे कंस्ट्रक्टर तर्कों के साथ एक प्रोटोटाइप-स्कॉप्ड बीन का अधिग्रहण / त्वरित करने की आवश्यकता है जो केवल रनटाइम पर प्राप्य हैं। निम्नलिखित कोड उदाहरण पर विचार करें (संक्षिप्तता के लिए सरल):

@Autowired
private ApplicationContext appCtx;

public void onRequest(Request request) {
    //request is already validated
    String name = request.getParameter("name");
    Thing thing = appCtx.getBean(Thing.class, name);

    //System.out.println(thing.getName()); //prints name
}

जहाँ थिंग क्लास को इस प्रकार परिभाषित किया गया है:

public class Thing {

    private final String name;

    @Autowired
    private SomeComponent someComponent;

    @Autowired
    private AnotherComponent anotherComponent;

    public Thing(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

सूचना nameहै final: यह केवल एक निर्माता के माध्यम से आपूर्ति की जा सकती है, और अपरिवर्तनीयता की गारंटी देता है। अन्य निर्भरताएं Thingवर्ग के कार्यान्वयन-विशिष्ट निर्भरताएं हैं , और अनुरोध हैंडलर कार्यान्वयन के लिए (कसकर युग्मित) ज्ञात नहीं होना चाहिए।

यह कोड स्प्रिंग एक्सएमएल विन्यास के साथ पूरी तरह से अच्छी तरह से काम करता है, उदाहरण के लिए:

<bean id="thing", class="com.whatever.Thing" scope="prototype">
    <!-- other post-instantiation properties omitted -->
</bean>

मैं जावा कॉन्फिग के साथ एक ही चीज कैसे प्राप्त करूं? निम्नलिखित स्प्रिंग 3.x का उपयोग करके काम नहीं करता है:

@Bean
@Scope("prototype")
public Thing thing(String name) {
    return new Thing(name);
}

अब, मैं एक कारखाना बना सकता हूँ , जैसे:

public interface ThingFactory {
    public Thing createThing(String name);
}

लेकिन सेवाप्रचालक और फैक्टरी डिजाइन पैटर्न को बदलने के लिए स्प्रिंग का उपयोग करने के पूरे बिंदु को हरा देता है , जो इस उपयोग के मामले के लिए आदर्श होगा।

यदि स्प्रिंग जावा कॉन्फिगर ऐसा कर सकता है, तो मैं इससे बच सकूंगा:

  • फ़ैक्टरी इंटरफ़ेस को परिभाषित करना
  • फैक्टरी कार्यान्वयन को परिभाषित करना
  • फैक्टरी कार्यान्वयन के लिए परीक्षण लेखन

यह एक टन का काम है (अपेक्षाकृत बोलने वाला) कुछ इतना तुच्छ के लिए कि वसंत पहले से ही एक्सएमएल विन्यास के माध्यम से समर्थन करता है।


15
बहुत बढ़िया सवाल।
सोतिरियोस डेलिमोलिस

हालांकि, क्या कोई कारण है कि आप केवल कक्षा को तुरंत नहीं कर सकते हैं और इसे स्प्रिंग से प्राप्त करना होगा? क्या यह अन्य फलियों पर निर्भरता है?
सॉटिरियोस डेलिमोलिसिन 19

@SotiriosDelimanolis हाँ, Thingकार्यान्वयन वास्तव में अधिक जटिल है और अन्य सेम पर निर्भरता है (मैं सिर्फ उन्हें संक्षिप्तता के लिए छोड़ दिया है)। जैसे कि, मैं उनके बारे में जानने के लिए अनुरोध हैंडलर कार्यान्वयन नहीं चाहता, क्योंकि यह हैंडलर को एपीआई / सेम ​​के लिए कसकर जोड़ेगा। मैं आपके (उत्कृष्ट) प्रश्न को प्रतिबिंबित करने के लिए प्रश्न को अपडेट करूंगा।
लेस हेजलवुड

मुझे यकीन नहीं है कि अगर वसंत एक निर्माता पर यह अनुमति देता है, लेकिन मुझे पता है कि आप @Qualifierपैरामीटर पर एक सेट्टर के साथ @Autowiredही सेटटर पर रख सकते हैं ।
20 अक्टूबर को CodeChimp

2
वसंत 4 में, @Beanकाम के साथ आपका उदाहरण । @Beanविधि उचित तर्क आप के लिए पारित के साथ बुलाया जाता है getBean(..)
सोतिरियोस डेलिमोलिसिस 20

जवाबों:


94

एक @Configurationकक्षा में, ऐसा एक @Beanतरीका

@Bean
@Scope("prototype")
public Thing thing(String name) {
    return new Thing(name);
}

एक बीन परिभाषा दर्ज करने और बीन बनाने के लिए कारखाना प्रदान करने के लिए उपयोग किया जाता है । बीन जिसे वह परिभाषित करता है वह केवल उन तर्कों का उपयोग करने के अनुरोध पर त्वरित होता है जो सीधे या स्कैनिंग के माध्यम से निर्धारित किए जाते हैं ApplicationContext

prototypeबीन के मामले में , हर बार एक नई वस्तु बनाई जाती है और इसलिए इसी @Beanपद्धति को भी निष्पादित किया जाता है।

आप ApplicationContextइसकी BeanFactory#getBean(String name, Object... args)विधि के माध्यम से एक सेम प्राप्त कर सकते हैं जो बताता है

बीन परिभाषा में निर्दिष्ट डिफ़ॉल्ट तर्कों (यदि कोई हो) को ओवरराइड करते हुए स्पष्ट कंस्ट्रक्टर तर्कों / फैक्टरी विधि तर्कों को निर्दिष्ट करने की अनुमति देता है।

पैरामीटर:

आर्ग तर्क एक प्रोटोटाइप एक स्थिर कारखाने विधि करने के लिए स्पष्ट तर्कों का उपयोग बनाने यदि उपयोग करने के लिए। किसी अन्य मामले में गैर-शून्य आर्ग मूल्य का उपयोग करना अमान्य है।

दूसरे शब्दों में, इस prototypeस्कॉप्ड बीन के लिए, आप उन दलीलों को प्रदान कर रहे हैं जिनका उपयोग किया जाएगा, बीन क्लास के कंस्ट्रक्टर में नहीं, बल्कि @Beanविधि आह्वान में।

यह स्प्रिंग संस्करण 4+ के लिए कम से कम सच है।


12
इस दृष्टिकोण के साथ मेरी समस्या यह है कि आप @Beanमैनुअल इनवोकेशन के लिए विधि को सीमित नहीं कर सकते । यदि आप कभी @Autowire Thingभी @Beanविधि को पैरामीटर को इंजेक्ट करने में असमर्थ होने पर मरने वाले कहलाएंगे। अगर आप @Autowire List<Thing>। मुझे यह थोड़ा नाजुक लगा।
Jan Zyka

@JanZyka, क्या ऐसा कोई तरीका है जिससे मैं थिंग को इन उत्तरों में उल्लिखित करने के अलावा अन्य चीज़ों को स्वीकार कर सकूँ (जो कि अगर आप स्क्विंट हैं तो सभी अनिवार्य रूप से एक ही हैं)। अधिक विशेष रूप से, अगर मुझे तर्कों का पता है (संकलन / कॉन्फ़िगरेशन समय पर), तो क्या कोई तरीका है जिससे मैं इन टिप्पणियों को कुछ एनोटेशन में व्यक्त कर सकता हूं @Autowireजिनके साथ मैं अर्हता प्राप्त कर सकता हूं ?
एम। प्रोखोरोव

52

स्प्रिंग> 4.0 और जावा 8 के साथ आप इसे और अधिक सुरक्षित रूप से कर सकते हैं:

@Configuration    
public class ServiceConfig {

    @Bean
    public Function<String, Thing> thingFactory() {
        return name -> thing(name); // or this::thing
    } 

    @Bean
    @Scope(value = "prototype")
    public Thing thing(String name) {
       return new Thing(name);
    }

}

उपयोग:

@Autowired
private Function<String, Thing> thingFactory;

public void onRequest(Request request) {
    //request is already validated
    String name = request.getParameter("name");
    Thing thing = thingFactory.apply(name);

    // ...
}

तो अब आप अपने सेम रनटाइम पर प्राप्त कर सकते हैं। यह निश्चित रूप से एक फैक्ट्री पैटर्न है, लेकिन आप विशिष्ट वर्ग लिखने पर कुछ समय बचा सकते हैं ThingFactory( जैसे कि आपको @FunctionalInterfaceदो से अधिक मापदंडों को पारित करने के लिए कस्टम लिखना होगा )।


1
मुझे यह दृष्टिकोण बहुत उपयोगी और साफ लगता है। धन्यवाद!
एलेक्स ओजेलियन

1
क्या एक कपड़ा? मैं आपका उपयोग समझता हूं .. लेकिन शब्दावली नहीं .. मुझे नहीं लगता कि मैंने "कपड़े पैटर्न" के बारे में सुना है
एंथनीजलिंक

1
@AnthonyJClink मुझे लगता है कि मैं सिर्फ fabricइसके बजाय factory, मेरे बुरे का इस्तेमाल करता हूं :)
रोमन गोलिशेव

1
@ अभिजीतसरकर ओह, मैं देख रहा हूं। लेकिन आप पैरामीटर को a Providerया a में पास नहीं कर सकते ObjectFactory, या मैं गलत हूं? और मेरे उदाहरण में आप इसे (या किसी भी पैरामीटर) को एक स्ट्रिंग पैरामीटर पास कर सकते हैं
रोमन गोलिशेव

2
यदि आप स्प्रिंग बीन जीवन चक्र विधियों का उपयोग नहीं करना चाहते (या करने की आवश्यकता नहीं है) (जो कि प्रोटोटाइप बीन्स के लिए अलग हैं ...) आप विधि पर छोड़ सकते हैं @Beanऔर Scopeएनोटेशन कर सकते हैं Thing thing। इसके अलावा इस विधि को खुद को और केवल कारखाने को छिपाने के लिए निजी बनाया जा सकता है।
m52509791

17

स्प्रिंग 4.3 के बाद से, इसे करने का नया तरीका है, जिसे उस मुद्दे के लिए सिल दिया गया था।

ObjectProvider - यह आपको इसे अपने "बहस" के लिए एक निर्भरता के रूप में जोड़ने के लिए सक्षम करता है, प्रोटोटाइप सेम सेम और तर्क का उपयोग कर इसे तुरंत करने के लिए।

इसका उपयोग करने का एक सरल उदाहरण यहां दिया गया है:

@Configuration
public class MyConf {
    @Bean
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public MyPrototype createPrototype(String arg) {
        return new MyPrototype(arg);
    }
}

public class MyPrototype {
    private String arg;

    public MyPrototype(String arg) {
        this.arg = arg;
    }

    public void action() {
        System.out.println(arg);
    }
}


@Component
public class UsingMyPrototype {
    private ObjectProvider<MyPrototype> myPrototypeProvider;

    @Autowired
    public UsingMyPrototype(ObjectProvider<MyPrototype> myPrototypeProvider) {
        this.myPrototypeProvider = myPrototypeProvider;
    }

    public void usePrototype() {
        final MyPrototype myPrototype = myPrototypeProvider.getObject("hello");
        myPrototype.action();
    }
}

यह निश्चित रूप से उपयोग करते समय हैलो स्ट्रिंग को प्रिंट करेगा।


15

प्रति टिप्पणी अद्यतन

सबसे पहले, मुझे यकीन नहीं है कि आप ऐसा क्यों करते हैं "यह काम नहीं करता है" बस 3.x में ठीक काम करता है। मुझे संदेह है कि आपके कॉन्फ़िगरेशन में कुछ गलत होना चाहिए।

यह काम:

- विन्यास फाइल:

@Configuration
public class ServiceConfig {
    // only here to demo execution order
    private int count = 1;

    @Bean
    @Scope(value = "prototype")
    public TransferService myFirstService(String param) {
       System.out.println("value of count:" + count++);
       return new TransferServiceImpl(aSingletonBean(), param);
    }

    @Bean
    public AccountRepository aSingletonBean() {
        System.out.println("value of count:" + count++);
        return new InMemoryAccountRepository();
    }
}

- निष्पादित करने के लिए परीक्षण फ़ाइल:

@Test
public void prototypeTest() {
    // create the spring container using the ServiceConfig @Configuration class
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ServiceConfig.class);
    Object singleton = ctx.getBean("aSingletonBean");
    System.out.println(singleton.toString());
    singleton = ctx.getBean("aSingletonBean");
    System.out.println(singleton.toString());
    TransferService transferService = ctx.getBean("myFirstService", "simulated Dynamic Parameter One");
    System.out.println(transferService.toString());
    transferService = ctx.getBean("myFirstService", "simulated Dynamic Parameter Two");
    System.out.println(transferService.toString());
}

स्प्रिंग 3.2.8 और जावा 7 का उपयोग करके, यह आउटपुट देता है:

value of count:1
com.spring3demo.account.repository.InMemoryAccountRepository@4da8692d
com.spring3demo.account.repository.InMemoryAccountRepository@4da8692d
value of count:2
Using name value of: simulated Dynamic Parameter One
com.spring3demo.account.service.TransferServiceImpl@634d6f2c
value of count:3
Using name value of: simulated Dynamic Parameter Two
com.spring3demo.account.service.TransferServiceImpl@70bde4a2

इसलिए 'सिंगलटन' बीन का अनुरोध दो बार किया जाता है। हालाँकि जैसा कि हम उम्मीद करेंगे, स्प्रिंग केवल एक बार ही इसे बनाता है। दूसरी बार यह देखता है कि इसमें वह बीन है और मौजूदा वस्तु को वापस करता है। कंस्ट्रक्टर (@ बीन विधि) को दूसरी बार नहीं लगाया जाता है। इसके संदर्भ में, जब 'प्रोटोटाइप' बीन को एक ही संदर्भ वस्तु से दो बार अनुरोध किया जाता है, तो हम देखते हैं कि संदर्भ आउटपुट में बदलता है और यह कि कंस्ट्रक्टर (@ बीन विधि) आईएस ने दो बार मंगवाया है।

तो फिर सवाल यह है कि एक सिंगलटन को एक प्रोटोटाइप में कैसे इंजेक्ट किया जाए। ऊपर विन्यास वर्ग दिखाता है कि यह कैसे करना है! आपको ऐसे सभी संदर्भों को कंस्ट्रक्टर में पास करना चाहिए। इससे निर्मित वर्ग एक शुद्ध पीओजेओ होने के साथ-साथ निहित संदर्भ वस्तुओं को अपरिवर्तनीय बनाने की अनुमति देगा, जैसा कि उन्हें होना चाहिए। तो स्थानांतरण सेवा कुछ इस तरह दिख सकती है:

public class TransferServiceImpl implements TransferService {

    private final String name;

    private final AccountRepository accountRepository;

    public TransferServiceImpl(AccountRepository accountRepository, String name) {
        this.name = name;
        // system out here is only because this is a dumb test usage
        System.out.println("Using name value of: " + this.name);

        this.accountRepository = accountRepository;
    }
    ....
}

यदि आप यूनिट टेस्ट लिखते हैं, तो आप कभी भी खुश होंगे कि आपने सभी @Autowired के बिना कक्षाएं बनाईं। यदि आपको ऑटो-वायर्ड घटकों की आवश्यकता होती है, तो उन स्थानीय जावा फाइलों को रखें।

इसे बीनफैक्ट्री में नीचे दी गई विधि कहेंगे। विवरण में नोट करें कि यह आपके सटीक उपयोग के मामले के लिए कैसा है।

/**
 * Return an instance, which may be shared or independent, of the specified bean.
 * <p>Allows for specifying explicit constructor arguments / factory method arguments,
 * overriding the specified default arguments (if any) in the bean definition.
 * @param name the name of the bean to retrieve
 * @param args arguments to use if creating a prototype using explicit arguments to a
 * static factory method. It is invalid to use a non-null args value in any other case.
 * @return an instance of the bean
 * @throws NoSuchBeanDefinitionException if there is no such bean definition
 * @throws BeanDefinitionStoreException if arguments have been given but
 * the affected bean isn't a prototype
 * @throws BeansException if the bean could not be created
 * @since 2.5
 */
Object getBean(String name, Object... args) throws BeansException;

3
उत्तर के लिए धन्यवाद! हालाँकि, मुझे लगता है कि आपने प्रश्न को गलत समझा। सवाल का सबसे महत्वपूर्ण हिस्सा यह है कि एक रनटाइम वैल्यू को कंस्ट्रक्टर तर्क के रूप में आपूर्ति किया जाना चाहिए जब प्रोटोटाइप को प्राप्त करना (तत्काल करना)।
लेस हेजलवुड

मैंने अपना उत्तर अपडेट कर दिया। वास्तव में ऐसा लग रहा था कि रनटाइम वैल्यू को सही तरीके से हैंडल किया गया था इसलिए मैंने उस हिस्से को छोड़ दिया। यह स्पष्ट रूप से समर्थित है, जैसा कि आप कार्यक्रम से अपडेट और आउटपुट से देख सकते हैं।
जोजी

0

आप केवल एक आंतरिक वर्ग का उपयोग करके एक समान प्रभाव प्राप्त कर सकते हैं :

@Component
class ThingFactory {
    private final SomeBean someBean;

    ThingFactory(SomeBean someBean) {
        this.someBean = someBean;
    }

    Thing getInstance(String name) {
        return new Thing(name);
    }

    class Thing {
        private final String name;

        Thing(String name) {
            this.name = name;
        }

        void foo() {
            System.out.format("My name is %s and I can " +
                    "access bean from outer class %s", name, someBean);
        }
    }
}


-1

थोड़ा अलग दृष्टिकोण के साथ देर से जवाब। यह इस हालिया प्रश्न का अनुसरण है जो इस प्रश्न को संदर्भित करता है।

हां, जैसा कि कहा गया था कि आप प्रोटोटाइप बीन की घोषणा कर सकते हैं जो एक @Configurationवर्ग में एक पैरामीटर को स्वीकार करता है जो प्रत्येक इंजेक्शन पर एक नया बीन बनाने की अनुमति देता है।
यह इस @Configuration वर्ग को एक कारखाना बना देगा और इस कारखाने को बहुत अधिक जिम्मेदारियां नहीं देगा, इसमें अन्य फलियां शामिल नहीं होनी चाहिए।

@Configuration    
public class ServiceFactory {

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Thing thing(String name) {
       return new Thing(name);
   }

}

लेकिन आप उस विन्यास बीन को Things बनाने के लिए इंजेक्ट भी कर सकते हैं :

@Autowired
private ServiceFactory serviceFactory;

public void onRequest(Request request) {
    //request is already validated
    String name = request.getParameter("name");
    Thing thing = serviceFactory.thing(name); // create a new bean at each invocation
    // ...    
}

यह दोनों प्रकार-सुरक्षित और संक्षिप्त है।


1
उत्तर के लिए धन्यवाद, लेकिन यह स्प्रिंग एंटी-पैटर्न है। कॉन्फ़िगर ऑब्जेक्ट्स को एप्लिकेशन कोड में 'लीक' नहीं होना चाहिए - वे आपके एप्लिकेशन ऑब्जेक्ट ग्राफ और इंटरफ़ेस को स्प्रिंग कंस्ट्रक्शन के साथ कॉन्फ़िगर करने के लिए मौजूद हैं। यह आपके एप्लिकेशन बीन्स (यानी एक और कॉन्फिगरेशन तंत्र) में एक्सएमएल कक्षाओं के समान है। यही है, यदि स्प्रिंग किसी अन्य कॉन्फ़िगरेशन तंत्र के साथ आता है, तो आपको अपने एप्लिकेशन कोड को फिर से भरना होगा - एक स्पष्ट संकेतक जो चिंताओं के पृथक्करण का उल्लंघन करता है। बेहतर होगा कि आपके कॉन्फिगरेशन में किसी फैक्ट्री / फंक्शन इंटरफेस का निर्माण हो और फैक्ट्री को इंजेक्ट करें - कॉन्फिगर करने के लिए कोई टाइट कपलिंग नहीं।
लेस हेज़लवुड

1) मैं पूरी तरह से सहमत हूं कि सामान्य मामले में, कॉन्फ़िगरेशन ऑब्जेक्ट को फ़ील्ड के रूप में लीक नहीं करना है। लेकिन इस विशिष्ट मामले में, एक प्रोटोटाइप ऑब्जेक्ट को इंजेक्ट करना जो प्रोटोटाइप बीन्स का उत्पादन करने के लिए केवल एक बीन को परिभाषित करता है, IHMO यह पूरी तरह से समझ में आता है: यह कॉन्फ़िगरेशन क्लास एक कारखाना बन जाता है। यदि यह केवल बनाता है तो चिंताओं के मुद्दे को अलग करना कहां है? ...
davidxxx

... 2) के बारे में "यही है, अगर वसंत एक और विन्यास तंत्र के साथ आता है,", यह एक गलत तर्क है क्योंकि जब आप अपने आवेदन में एक फ्रेमवर्क का उपयोग करने का निर्णय लेते हैं, तो आप अपने आवेदन को उसी के साथ जोड़ते हैं। तो किसी भी मामले में, आपको किसी भी स्प्रिंग एप्लिकेशन को रिफलेक्टर करना @Configurationहोगा जो उस तंत्र को बदलने पर निर्भर करता है ।
davidxxx 17

1
... 3) आपके द्वारा स्वीकार किए गए उत्तर का उपयोग करने का प्रस्ताव है BeanFactory#getBean()। लेकिन यह युग्मन के संदर्भ में बहुत बुरा है क्योंकि यह एक ऐसा कारखाना है जो आवेदन के किसी भी फलियों को प्राप्त करने / पलटने की अनुमति देता है और न केवल वर्तमान सेम की जरूरत है। इस तरह के उपयोग से आप अपनी कक्षा की जिम्मेदारियों को बहुत आसानी से मिला सकते हैं क्योंकि यह निर्भरता को खींच सकता है जो असीमित है, जो वास्तव में सलाह नहीं बल्कि असाधारण मामला है।
davidxxx 17

@ davidxxx - मैंने उत्तर वर्ष पहले स्वीकार किया था, इससे पहले JDK 8 और स्प्रिंग 4 डी-फैक्टो थे। आधुनिक स्प्रिंग यूसेज के लिए ऊपर रोमन का उत्तर अधिक सही है। अपने बयान के संबंध में "क्योंकि जब आप अपने आवेदन में एक फ्रेमवर्क का उपयोग करने का निर्णय लेते हैं, तो आप अपने आवेदन को उस" के साथ जोड़ते हैं "जो स्प्रिंग टीम की सिफारिशों और जावा कॉन्फिगरेशन के सर्वोत्तम व्यवहारों के प्रति काफी विरोधाभासी है - जोश लॉन्ग या जेर्गेन हूलर से पूछें कि क्या आपको एक उन्हें व्यक्तिगत रूप से बोलने का मौका (मेरे पास है, और मैं आपको आश्वस्त कर सकता हूं कि वे स्पष्ट रूप से जब भी संभव हो, अपने ऐप कोड को स्प्रिंग से जोड़े न रखने की सलाह दें )। चीयर्स।
लेस हेज़लवुड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.