Spring Java Config: Çalışma zamanı argümanları ile prototip kapsamlı bir @Bean'i nasıl yaratırsınız?


134

Spring'in Java Config'ini kullanarak, yalnızca çalışma zamanında elde edilebilen yapıcı argümanları ile prototip kapsamlı bir bean edinmem / başlatmam gerekiyor. Aşağıdaki kod örneğini göz önünde bulundurun (kısalık açısından basitleştirilmiş):

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

Thing sınıfı aşağıdaki gibi tanımlanır:

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

Uyarı nameolduğunu finalsadece bir yapıcı yoluyla sağlanabilir ve değişmezliğini garanti. Diğer bağımlılıklar, Thingsınıfın uygulamaya özgü bağımlılıklarıdır ve istek işleyici uygulamasının bilinmemesi (sıkı bir şekilde bağlanması) gerekir.

Bu kod, Spring XML yapılandırmasıyla mükemmel şekilde çalışır, örneğin:

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

Java yapılandırmasıyla aynı şeyi nasıl başarırım? Aşağıdakiler Spring 3.x kullanıldığında çalışmaz:

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

Şimdi, olabilir bir Fabrikası, örneğin oluşturun:

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

Ancak bu , bu kullanım durumu için ideal olan ServiceLocator ve Factory tasarım modelini değiştirmek için Spring'i kullanmanın tüm amacını ortadan kaldırır .

Spring Java Config bunu yapabilseydi, şunlardan kaçınabilirdim:

  • Fabrika arayüzünü tanımlama
  • Fabrika uygulamasını tanımlama
  • Fabrika uygulaması için yazma testleri

Bu, Spring'in zaten XML yapılandırması aracılığıyla desteklediği çok önemsiz bir şey için (nispeten konuşursak) bir ton çalışma.


15
Harika soru.
Sotirios Delimanolis

Bununla birlikte, sınıfı kendiniz somutlaştırıp Spring'den almak zorunda kalmanın bir nedeni var mı? Diğer fasulyelere bağımlılığı var mı?
Sotirios Delimanolis

@SotiriosDelimanolis evet, Thinguygulama aslında daha karmaşık ve diğer çekirdeklere bağımlılıkları var (kısalık için onları atladım). Bu nedenle, İstek işleyici uygulamasının onlar hakkında bilgi sahibi olmasını istemiyorum çünkü bu, işleyiciyi ihtiyaç duymadığı API'lere / fasulye'lere sıkıca bağlayacaktır. Soruyu (mükemmel) sorunuzu yansıtacak şekilde güncelleyeceğim.
Les Hazlewood

Spring'in bir kurucuda buna izin verip vermediğinden emin değilim, ancak ayarlayıcının kendisiyle @Qualifierbir ayarlayıcıya parametreler koyabileceğinizi biliyorum @Autowired.
CodeChimp

2
Bahar 4'te @Beaneserlerle olan örneğiniz . @BeanYöntemi geçirilen uygun argümanlarla çağrılan getBean(..).
Sotirios Delimanolis

Yanıtlar:


94

Bir @Configurationsınıfta @Beanböyle bir yöntem

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

bir fasulye tanımını kaydetmek ve fasulyeyi oluşturmak için fabrikayı sağlamak için kullanılır . Tanımladığı fasulye, yalnızca talep üzerine doğrudan veya bunu tarayarak belirlenen argümanlar kullanılarak somutlaştırılır ApplicationContext.

prototypeFasulye durumunda, her seferinde yeni bir nesne oluşturulur ve bu nedenle ilgili @Beanyöntem de yürütülür.

Sen bir fasulye alabilirsiniz ApplicationContextonun aracılığıyla BeanFactory#getBean(String name, Object... args)yöntemle hangi devletler

Fasulye tanımında belirtilen varsayılan bağımsız değişkenleri (varsa) geçersiz kılarak, açık yapıcı bağımsız değişkenlerini / fabrika yöntemi bağımsız değişkenlerini belirtmeye izin verir.

Parametreler:

args argümanları, statik bir fabrika yöntemine yönelik açık argümanlar kullanarak bir prototip oluştururken kullanılacak. Başka bir durumda boş olmayan bir bağımsız değişken değeri kullanmak geçersizdir.

Başka bir deyişle, bu prototypekapsamlı fasulye için, bean sınıfının yapıcısında değil, @Beanyöntem çağrısında kullanılacak bağımsız değişkenleri sağlıyorsunuz .

Bu, en azından 4+ Bahar sürümleri için geçerlidir.


12
Bu yaklaşımla ilgili sorunum, @Beanyöntemi manuel çağrı ile sınırlayamamanızdır . Eğer hiç yaparsanız @Autowire Thing, @Beanyöntem muhtemelen parametreyi enjekte edemediğiniz için ölmek üzere çağrılacaktır. Seninle aynı @Autowire List<Thing>. Bunu biraz kırılgan buldum.
Jan Zyka

@JanZyka, bu cevaplarda ana hatlarıyla belirtilenden başka bir şeyi otomatik olarak bağlayabilmemin bir yolu var mı (eğer gözlerinizi kısarsan hepsi aslında aynıdır). Daha spesifik olarak, argümanları önceden biliyorsam (derleme / yapılandırma zamanında), bu argümanları @Autowireile nitelendirebileceğim bazı ek açıklamalarda ifade etmenin herhangi bir yolu var mı?
M Prokhorov

52

Spring> 4.0 ve Java 8 ile bunu daha güvenli bir şekilde yapabilirsiniz:

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

}

Kullanımı:

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

    // ...
}

Böylece artık fasulyenizi çalışma zamanında alabilirsiniz. Bu elbette bir fabrika modelidir, ancak gibi belirli bir sınıf yazmak için biraz zaman kazanabilirsiniz ThingFactory(ancak @FunctionalInterfaceikiden fazla parametreyi geçmek için özel yazmanız gerekecektir ).


1
Bu yaklaşımı çok kullanışlı ve temiz buluyorum. Teşekkürler!
Alex Objelean

1
Kumaş nedir? Kullanımınızı anlıyorum .. ama terminolojiyi değil .. "kumaş modelini" duyduğumu sanmıyorum
AnthonyJClink

1
@AnthonyJClink Sanırım fabricyerine kullandım factory, benim hatam :)
Roman Golyshev

1
@AbhijitSarkar oh, anlıyorum. Ama bir parametreyi a Providerveya a'ya geçiremezsiniz ObjectFactory, yoksa yanılıyor muyum? Ve benim
örneğimde

2
İlkbahar fasulyesi yaşam döngüsü yöntemlerini (prototip çekirdekler için farklı olan ...) kullanmak istemiyorsanız (veya kullanmanız gerekmiyorsa) , yöntem üzerinden @Beanve Scopeek açıklamaları atlayabilirsiniz Thing thing. Üstelik bu yöntem gizli hale getirilebilir ve sadece fabrika bırakılabilir.
m52509791

17

Bahar 4.3'ten bu yana, bunu yapmanın yeni bir yolu var, bu konu için dikildi.

ObjectProvider - "bağımsız" Prototip kapsamlı fasulyenize bir bağımlılık olarak eklemenizi ve bağımsız değişkeni kullanarak onu başlatmanızı sağlar.

İşte nasıl kullanılacağına dair basit bir örnek:

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

Bu elbette usePrototype çağrılırken merhaba dizesini yazdıracaktır.


15

Yorum başına GÜNCELLENDİ

İlk olarak, Spring 3.x'te gayet iyi çalışan bir şey için neden "bu işe yaramıyor" dediğinizden emin değilim. Bir yerlerde konfigürasyonunuzda bir şeylerin yanlış olduğundan şüpheleniyorum.

Bu çalışıyor:

-- Yapılandırma dosyası:

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

- Yürütülecek Test Dosyası:

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

Spring 3.2.8 ve Java 7'yi kullanmak şu çıktıyı verir:

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

Bu yüzden 'Singleton' Bean iki kez istenir. Ancak beklediğimiz gibi, Bahar onu yalnızca bir kez yaratır. İkinci kez o fasulyeye sahip olduğunu görür ve sadece mevcut nesneyi döndürür. Yapıcı (@Bean yöntemi) ikinci kez çağrılmaz. Buna bağlı olarak, 'Prototype' Bean aynı bağlam nesnesinden iki kez talep edildiğinde, çıktıdaki referansın değiştiğini VE yapıcının (@Bean yöntemi) iki kez çağrıldığını görürüz.

Öyleyse soru prototipe bir singletonun nasıl enjekte edileceğidir. Yukarıdaki konfigürasyon sınıfı bunun nasıl yapılacağını da gösteriyor! Tüm bu tür referansları kurucuya iletmelisiniz. Bu, oluşturulan sınıfın saf bir POJO olmasına ve içerilen referans nesnelerini olması gerektiği gibi değişmez hale getirmesine izin verecektir. Dolayısıyla, transfer hizmeti şöyle görünebilir:

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

Birim Testleri yazarsanız, sınıfları bu şekilde @Autowired olmadan oluşturduğunuz için çok mutlu olacaksınız. Otomatik kablolu bileşenlere ihtiyacınız varsa, bunları java yapılandırma dosyalarında yerel olarak saklayın.

Bu, BeanFactory'de aşağıdaki yöntemi çağıracaktır. Bunun tam kullanım durumunuz için nasıl tasarlandığını açıklamaya not edin.

/**
 * 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
Cevap için teşekkürler! Ancak, soruyu yanlış anladığınızı düşünüyorum. Sorunun en önemli kısmı, prototip edinilirken (somutlaştırılırken) yapıcı argüman olarak bir çalışma zamanı değerinin sağlanması gerektiğidir.
Les Hazlewood

Cevabımı güncelledim. Aslında, çalışma zamanı değerinin işlenmesi doğru şekilde yapılmış gibi görünüyordu, bu yüzden bu kısmı dışarıda bıraktım. Programın güncellemelerinden ve çıktılarından da görebileceğiniz gibi, açıkça desteklenmektedir.
JoeG

0

Sadece bir iç sınıf kullanarak benzer bir etki elde edebilirsiniz :

@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

Biraz farklı bir yaklaşımla geç cevap. Bu, bu son sorunun devamı niteliğindedir bu sorunun kendisine atıfta niteliğindedir.

Evet, söylendiği gibi @Configuration, her enjeksiyonda yeni bir fasulye oluşturmaya izin veren bir sınıftaki bir parametreyi kabul eden prototip çekirdeği bildirebilirsiniz .
Bu @Configuration sınıfı bir fabrika haline getirecek ve bu fabrikaya çok fazla sorumluluk verilmemesi için diğer fasulyeleri kapsamamalı.

@Configuration    
public class ServiceFactory {

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

}

Ama aynı zamanda bu konfigürasyon çekirdeğini Things oluşturmak için enjekte edebilirsiniz :

@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
    // ...    
}

Hem tür açısından güvenli hem de özlüdür.


1
Cevabınız için teşekkürler, ama bu bir Bahar paterni. Yapılandırma nesneleri uygulama koduna 'sızmamalıdır' - uygulama nesne grafiğinizi ve Spring yapıları ile arabirimi yapılandırmak için mevcutturlar. Bu, uygulamanızın fasulyesindeki XML sınıflarına benzer (yani başka bir yapılandırma mekanizması). Yani, Spring başka bir yapılandırma mekanizmasıyla birlikte gelirse, uygulama kodunuzu yeniden düzenlemeniz gerekir - bu, endişelerin ayrılmasını ihlal ettiğinin açık bir göstergesi. Yapılandırmanızın bir Fabrika / İşlev arabirimi örneklerini oluşturması ve Fabrikayı enjekte etmesi daha iyidir - yapılandırma için sıkı bağlantı gerekmez.
Les Hazlewood

1) Genel durumda, konfigürasyon nesnelerinin alan olarak sızmaması gerektiğini tamamen kabul ediyorum. Ancak bu özel durumda, prototip çekirdekler üretmek için bir ve yalnızca bir çekirdeği tanımlayan bir yapılandırma nesnesi enjekte etmek, IHMO tamamen mantıklıdır: bu yapılandırma sınıfı bir fabrika haline gelir. Sadece bunu yapıyorsa, endişelerin ayrılması sorunu nerede? ...
davidxxx

... 2) "Yani, Bahar başka bir yapılandırma mekanizmasıyla gelirse" hakkında, bu yanlış bir argümandır çünkü uygulamanızda bir çerçeve kullanmaya karar verdiğinizde, uygulamanızı bununla birleştirirsiniz. Yani her durumda, @Configurationbu mekanizmanın değişip değişmediğine bağlı olan tüm Spring uygulamalarını yeniden düzenlemeniz gerekecektir .
davidxxx

1
... 3) Kabul ettiğiniz cevap kullanılmayı önerir BeanFactory#getBean(). Ancak bu, bağlama açısından çok daha kötüdür çünkü bu, yalnızca mevcut fasulyenin ihtiyaç duyduğu değil, uygulamanın herhangi bir fasulyesinin alınmasına / somutlaştırılmasına izin veren bir fabrikadır. Böyle bir kullanımla sınıfınızın sorumluluklarını çok kolay bir şekilde karıştırabilirsiniz, çünkü çekebileceği bağımlılıklar sınırsızdır, ki bu gerçekten tavsiye edilmez, ancak istisnai bir durumdur.
davidxxx

@ davidxxx - Yanıtı yıllar önce, JDK 8 ve Spring 4 fiilen olmadan önce kabul ettim. Roman'ın yukarıdaki cevabı, modern Bahar kullanımları için daha doğrudur. İfadenizle ilgili olarak, "çünkü uygulamanızda bir çerçeve kullanmaya karar verdiğinizde, uygulamanızı bununla birleştirirsiniz", Spring ekibinin tavsiyelerine ve Java Config en iyi uygulamalarına oldukça aykırıdır - Josh Long veya Jeurgen Hoeller'e sorun şans (ı var ve onlar açıkça tavsiye size temin ederim şahsen onlara hitap edecek değil mümkünse Bahar çift uygulama kodu). Şerefe.
Les Hazlewood
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.