Bahar Uygulama Bağlamını Edinme


216

Bir Spring uygulamasında statik / global olarak ApplicationContext'in bir kopyasını istemenin bir yolu var mı?

Ana sınıfın uygulama bağlamını başlattığı ve başlattığı varsayılarak, bunu çağrı yığını üzerinden ihtiyacı olan herhangi bir sınıfa geçirmesi mi gerekiyor yoksa bir sınıfın daha önce oluşturulan bağlamı sormasının bir yolu var mı? (Hangisinin bir singleton olduğunu varsayıyorum?)

Yanıtlar:


171

Kapsayıcıya erişmesi gereken nesne kapta bir fasulye ise, sadece BeanFactoryAware veya ApplicationContextAware uygulayın arabirimlerini uygulayın.

Kabın dışındaki bir nesnenin kaba erişmesi gerekiyorsa , yay kabı için standart bir GoF tekli desen kullandım . Bu şekilde, uygulamanızda sadece bir singleton vardır, geri kalanı da konteynerdeki singleton fasulyeleridir.


15
ApplicationContexts - ApplicationContextAware için daha iyi bir arayüz de vardır. BeanFactoryAware çalışmalıdır, ancak uygulama bağlamı işlevselliğine ihtiyacınız varsa bunu bir uygulama bağlamına atmanız gerekir.
MetroidFan2002

@Don Kirkby Tekli kalıbı kullanmak, kap sınıfınızı kap sınıfınızdaki statik bir yöntemden örneklemek anlamına gelir ... bir nesneyi "el ile" başlattığınızda, artık Spring tarafından yönetilmez: bu sorunu nasıl çözdünüz?
Antonin

Dokuz yıl sonra hafızam biraz belirsiz, @Antonin, ama singleton'un Spring konteyner içinde yönetildiğini sanmıyorum. Tek tek iş kapsayıcı bir XML dosyasından yüklemek ve statik üye değişkeni içinde tutmak olduğunu düşünüyorum. Kendi sınıfının bir örneğini döndürmedim, Spring konteynerinin bir örneğini döndürdüm.
Don Kirkby

1
Kendisine statik bir referansa sahip bir Spring single'ı olan Don Kirkby sayesinde, belki de Spring olmayan nesneler tarafından kullanılabilir.
Antonin

Spring konteynerine singleton instance()yöntemini fabrika olarak kullanmasını söylediyseniz bu işe yarayabilir @Antonin . Ancak, ben sadece konteyner dışında tüm kod ilk konteyner erişim izin düşünüyorum. Sonra bu kod kaptan nesneler isteyebilir.
Don Kirkby

118

Şunları uygulayabilir ApplicationContextAwareveya kullanabilirsiniz @Autowired:

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBeanApplicationContextBu çekirdeğin örneklendiği enjekte edilecektir . Örneğin, oldukça standart bir bağlam hiyerarşisine sahip web uygulamanız varsa:

main application context <- (child) MVC context

ve SpringBeanana bağlam içinde ilan edilirse, ana bağlam enjekte edilecektir; aksi takdirde, MVC bağlamında bildirilirse, MVC bağlamı enjekte edilir.


2
Bu bir demet yardımcı oldu. Spring 2.0 ile eski bir uygulama ile bazı garip sorunlarım var ve cevabınız, tek bir Spring IoC kapsayıcısı ile tek bir ApplicationContext ile çalışmak için bir şeyler almanın tek yoluydu.
Stu Thompson

1
Okuyucular .. Bu SpringBean'i springconfig.xml dosyasında fasulye olarak bildirmeyi unutmayın.
süpernova

Bu zaten bir Bean ise ve yeni XXXXApplicationContext (XXXX) örneğini döndüren Application.getApplicationContext () (Singleton pattern) kullanıyorsam, neden çalışmıyor? Neden otomatik olarak kablolamam gerekiyor?
Jaskey

Sen kullanabilirsiniz @Injectçok
Alireza Fattahi

39

İşte güzel bir yol (benim değil, orijinal referans burada: http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html

Bu yaklaşımı kullandım ve işe yarıyor. Temel olarak, uygulama bağlamına (statik) bir referans tutan basit bir fasulye. Yay konfigürasyonunda referans alarak başlatılır.

Orijinal referansa bir göz atın, çok açık.


4
getBeanBir Unit testi sırasında çalışan koddan çağırırsanız bu yaklaşım başarısız olabilir çünkü Spring içeriği siz istemeden önce ayarlanmayacaktır. Bu, başarılı bir şekilde bu yaklaşımı kullanarak 2 yıl sonra bugün çarptı bir yarış durumu.
HDave

Ben aynı şey içine koşuyorum .. bir birim test değil bir veritabanı tetikleyicisi .. herhangi bir öneri?
John Deverall

Mükemmel yanıt. Teşekkür ederim.
sagneta

17

SingletonBeanFactoryLocator kullanabileceğine inanıyorum . BeanRefFactory.xml dosyası gerçek applicationContext tutacak, Bu gibi bir şey gitmek istiyorum:

<bean id="mainContext" class="org.springframework.context.support.ClassPathXmlApplicationContext">
     <constructor-arg>
        <list>
            <value>../applicationContext.xml</value>
        </list>
     </constructor-arg>
 </bean>

Ve applicationcontext'ten böyle bir fasulye nereden olursa olsun bir fasulye almak için kod:

BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
BeanFactoryReference bf = bfl.useBeanFactory("mainContext");
SomeService someService = (SomeService) bf.getFactory().getBean("someService");

Bahar ekibi bu sınıfın ve yadayada'nın kullanımını caydırıyor, ama kullandığım yerde bana çok yakıştı.


11

Diğer önerileri uygulamadan önce kendinize şu soruları sorun ...

  • Neden ApplicationContext'i almaya çalışıyorum?
  • ApplicationContext'i bir servis bulucu olarak etkin bir şekilde mi kullanıyorum?
  • ApplicationContext'e erişmekten kaçınabilir miyim?

Bu soruların cevapları belirli uygulama türlerinde (örneğin, Web uygulamaları) diğerlerinde olduğundan daha kolaydır, ancak yine de sormaya değer.

ApplicationContext'e erişmek, tüm bağımlılık enjeksiyon prensibini bir tür ihlal eder, ancak bazen fazla seçeneğiniz yoktur.


5
Buna iyi bir örnek JSP etiketleridir; bunların oluşturulması sunucu uygulaması konteyneri tarafından yönetilir, bu nedenle bağlamı statik olarak elde etmekten başka seçenekleri yoktur. Spring temel Tag sınıflarını sağlar ve ihtiyaçları olan bağlamları elde etmek için BeanFactoryLocators kullanırlar.
skaffman

6

Bir web uygulaması kullanıyorsanız, bir servletfilter ve ThreadLocal kullanarak tek tekton kullanmadan uygulama bağlamına erişmenin başka bir yolu da vardır. Filtrede WebApplicationContextUtils kullanarak uygulama içeriğine erişebilir ve uygulama bağlamını veya gerekli çekirdekleri TheadLocal içinde depolayabilirsiniz.

Dikkat: ThreadLocal ayarını kaldırmayı unutursanız, uygulamanın çözülmesini kaldırırken kötü sorunlar yaşarsınız! Bu nedenle, onu ayarlamalı ve hemen sondaki ThreadLocal öğesinin ayarını kaldırmaya çalışmalısınız.

Tabii ki, bu hala bir singleton kullanıyor: ThreadLocal. Ancak gerçek fasulye artık olmak zorunda değil. Talep kapsamı bile alınabilir ve bu çözüm, bir uygulamada EAR'daki libaries ile birden fazla WAR'unuz varsa da çalışır. Yine de, ThreadLocal'ın bu kullanımını düz singletonların kullanımı kadar kötü olarak düşünebilirsiniz. ;-)

Belki de Bahar zaten benzer bir çözüm sunuyor? Bir tane bulamadım, ama kesin olarak bilmiyorum.


6
SpringApplicationContext.java

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * Wrapper to always return a reference to the Spring Application 
Context from
 * within non-Spring enabled beans. Unlike Spring MVC's 
WebApplicationContextUtils
 * we do not need a reference to the Servlet context for this. All we need is
 * for this bean to be initialized during application startup.
 */
public class SpringApplicationContext implements 
ApplicationContextAware {

  private static ApplicationContext CONTEXT;

  /**
   * This method is called from within the ApplicationContext once it is 
   * done starting up, it will stick a reference to itself into this bean.
  * @param context a reference to the ApplicationContext.
  */
  public void setApplicationContext(ApplicationContext context) throws BeansException {
    CONTEXT = context;
  }

  /**
   * This is about the same as context.getBean("beanName"), except it has its
   * own static handle to the Spring context, so calling this method statically
   * will give access to the beans by name in the Spring application context.
   * As in the context.getBean("beanName") call, the caller must cast to the
   * appropriate target class. If the bean does not exist, then a Runtime error
   * will be thrown.
   * @param beanName the name of the bean to get.
   * @return an Object reference to the named bean.
   */
  public static Object getBean(String beanName) {
    return CONTEXT.getBean(beanName);
  }
}

Kaynak: http://sujitpal.blogspot.de/2007/03/accessing-spring-beans-from-legacy-code.html


5

ContextSingletonBeanFactoryLocator'a bir göz atın . Belirli yollarla kaydedildikleri varsayılarak, Spring'in bağlamlarını elde etmeleri için statik erişimcilere olanak sağlar.

Hoş değil ve belki de istediğinizden daha karmaşık, ama işe yarıyor.


4

Bahar uygulamasında uygulama bağlamı almanın birçok yolu vardır. Bunlar feryat verilir:

  1. ApplicationContextAware ile :

    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    
    public class AppContextProvider implements ApplicationContextAware {
    
    private ApplicationContext applicationContext;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    }

İşte setApplicationContext(ApplicationContext applicationContext)size applicationContext alacak yöntem

ApplicationContextAware :

İçinde çalıştığı ApplicationContext'ten haberdar olmak isteyen herhangi bir nesne tarafından uygulanacak arabirim. Bu arabirimi uygulamak, örneğin bir nesne, birlikte çalışan bir fasulye kümesine erişim gerektirdiğinde anlamlıdır.

  1. Autowired ile :

    @Autowired
    private ApplicationContext applicationContext;

Burada @Autowiredanahtar kelime applicationContext sağlayacaktır. Autowired'in bir sorunu var. Birim testi sırasında sorun yaratacaktır.


3

Akımdaki herhangi bir durumu ApplicationContextveya ApplicationContextkendisini statik bir değişkende saklayarak (örneğin, tekli kalıbı kullanarak), Bahar testi kullanıyorsanız, testlerinizi dengesiz ve öngörülemez hale getireceğinizi unutmayın. Bunun nedeni, Bahar testinin aynı JVM'deki uygulama içeriğini önbelleğe alması ve yeniden kullanmasıdır. Örneğin:

  1. Test Bir çalışma ve açıklama eklenir @ContextConfiguration({"classpath:foo.xml"}).
  2. Test B çalışması ve açıklamalı @ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. Test C çalışması ve açıklama eklenir @ContextConfiguration({"classpath:foo.xml"})

Test A çalıştığında, ApplicationContext a oluşturulur ve ApplicationContextAwareuygulayan veya otomatik kablolama yapan tüm fasulye ApplicationContextstatik değişkene yazabilir.

Test B çalıştığında aynı şey olur ve statik değişken şimdi Test B'leri gösterir. ApplicationContext

Test C çalıştığında, Test A'nın (ve burada ) yeniden kullanıldığı için hiçbir fasulye oluşturulmaz . Şimdi , testiniz için çekirdekleri tutandan farklı bir statik değişken var .TestContextApplicationContextApplicationContext


1

Bunun ne kadar yararlı olacağından emin değilsiniz, ancak uygulamayı başlattığınızda içeriği de alabilirsiniz. Bu, bir an önce bile bağlamı alabileceğiniz en kısa zamanda @Autowire.

@SpringBootApplication
public class Application extends SpringBootServletInitializer {
    private static ApplicationContext context;

    // I believe this only runs during an embedded Tomcat with `mvn spring-boot:run`. 
    // I don't believe it runs when deploying to Tomcat on AWS.
    public static void main(String[] args) {
        context = SpringApplication.run(Application.class, args);
        DataSource dataSource = context.getBean(javax.sql.DataSource.class);
        Logger.getLogger("Application").info("DATASOURCE = " + dataSource);

0

Lütfen bunu not al; Aşağıdaki kod, önceden yüklenmiş olanı kullanmak yerine yeni uygulama bağlamı oluşturur.

private static final ApplicationContext context = 
               new ClassPathXmlApplicationContext("beans.xml");

Ayrıca , savaştaki araçların bir beans.xmlparçası olması gerektiğini , bunun gerçek uygulamanın belirtildiği yer olan bir parçası olduğunu unutmayın .src/main/resourcesWEB_INF/classesapplicationContext.xmlWeb.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>META-INF/spring/applicationContext.xml</param-value>
</context-param>

Öyle zor bahsetmek applicationContext.xmlyolu ClassPathXmlApplicationContextyapıcısı. ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml")dosyayı bulamayacak.

Bu nedenle, ek açıklamaları kullanarak mevcut applicationContext'i kullanmak daha iyidir.

@Component
public class OperatorRequestHandlerFactory {

    public static ApplicationContext context;

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        context = applicationContext;
    }
}

0

Bu sorunun cevaplandığını biliyorum, ancak Bahar Bağlamını almak için yaptığım Kotlin kodunu paylaşmak istiyorum.

Ben uzman değilim, bu yüzden eleştirmenlere, incelemelere ve tavsiyelere açığım:

https://gist.github.com/edpichler/9e22309a86b97dbd4cb1ffe011aa69dd

package com.company.web.spring

import com.company.jpa.spring.MyBusinessAppConfig
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import org.springframework.stereotype.Component
import org.springframework.web.context.ContextLoader
import org.springframework.web.context.WebApplicationContext
import org.springframework.web.context.support.WebApplicationContextUtils
import javax.servlet.http.HttpServlet

@Configuration
@Import(value = [MyBusinessAppConfig::class])
@ComponentScan(basePackageClasses  = [SpringUtils::class])
open class WebAppConfig {
}

/**
 *
 * Singleton object to create (only if necessary), return and reuse a Spring Application Context.
 *
 * When you instantiates a class by yourself, spring context does not autowire its properties, but you can wire by yourself.
 * This class helps to find a context or create a new one, so you can wire properties inside objects that are not
 * created by Spring (e.g.: Servlets, usually created by the web server).
 *
 * Sometimes a SpringContext is created inside jUnit tests, or in the application server, or just manually. Independent
 * where it was created, I recommend you to configure your spring configuration to scan this SpringUtils package, so the 'springAppContext'
 * property will be used and autowired at the SpringUtils object the start of your spring context, and you will have just one instance of spring context public available.
 *
 *Ps: Even if your spring configuration doesn't include the SpringUtils @Component, it will works tto, but it will create a second Spring Context o your application.
 */
@Component
object SpringUtils {

        var springAppContext: ApplicationContext? = null
    @Autowired
    set(value) {
        field = value
    }



    /**
     * Tries to find and reuse the Application Spring Context. If none found, creates one and save for reuse.
     * @return returns a Spring Context.
     */
    fun ctx(): ApplicationContext {
        if (springAppContext!= null) {
            println("achou")
            return springAppContext as ApplicationContext;
        }

        //springcontext not autowired. Trying to find on the thread...
        val webContext = ContextLoader.getCurrentWebApplicationContext()
        if (webContext != null) {
            springAppContext = webContext;
            println("achou no servidor")
            return springAppContext as WebApplicationContext;
        }

        println("nao achou, vai criar")
        //None spring context found. Start creating a new one...
        val applicationContext = AnnotationConfigApplicationContext ( WebAppConfig::class.java )

        //saving the context for reusing next time
        springAppContext = applicationContext
        return applicationContext
    }

    /**
     * @return a Spring context of the WebApplication.
     * @param createNewWhenNotFound when true, creates a new Spring Context to return, when no one found in the ServletContext.
     * @param httpServlet the @WebServlet.
     */
    fun ctx(httpServlet: HttpServlet, createNewWhenNotFound: Boolean): ApplicationContext {
        try {
            val webContext = WebApplicationContextUtils.findWebApplicationContext(httpServlet.servletContext)
            if (webContext != null) {
                return webContext
            }
            if (createNewWhenNotFound) {
                //creates a new one
                return ctx()
            } else {
                throw NullPointerException("Cannot found a Spring Application Context.");
            }
        }catch (er: IllegalStateException){
            if (createNewWhenNotFound) {
                //creates a new one
                return ctx()
            }
            throw er;
        }
    }
}

Şimdi, bu Java Servletinde olduğu gibi bağlamdan (junit testleri, fasulye, manuel olarak başlatılan sınıflar) bağımsız olarak aynı yöntemi çağırabilen bir bahar içeriği herkese açıktır:

@WebServlet(name = "MyWebHook", value = "/WebHook")
public class MyWebServlet extends HttpServlet {


    private MyBean byBean
            = SpringUtils.INSTANCE.ctx(this, true).getBean(MyBean.class);


    public MyWebServlet() {

    }
}

0

Spring bean'da autowire'ı aşağıdaki gibi yapın: @Autowired private ApplicationContext appContext;

applicationcontext nesnesini kullanırsınız.


0

Yaklaşım 1: ApplicationContextAware arabirimini uygulayarak ApplicationContext'i enjekte edebilirsiniz. Referans bağlantısı .

@Component
public class ApplicationContextProvider implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

Yaklaşım 2: Bahar yönetilen çekirdeklerin herhangi birinde Autowire Uygulama içeriği.

@Component
public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

Referans bağlantısı .

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.