Pobieranie kontekstu aplikacji wiosennej

216

Czy istnieje sposób, aby statycznie / globalnie zażądać kopii ApplicationContext w aplikacji Spring?

Zakładając, że główna klasa uruchamia się i inicjuje kontekst aplikacji, czy musi przekazywać to przez stos wywołań do dowolnych klas, które tego potrzebują, czy też istnieje sposób, aby klasa mogła zapytać o wcześniej utworzony kontekst? (Co, jak zakładam, musi być singletonem?)

Joe Skora
źródło

Odpowiedzi:

171

Jeśli obiektem wymagającym dostępu do kontenera jest fasola w kontenerze, wystarczy zaimplementować interfejsy BeanFactoryAware lub ApplicationContextAware .

Jeśli obiekt poza kontenerem potrzebuje dostępu do kontenera, zastosowałem standardowy wzorzec singletonu GoF dla kontenera sprężynowego. W ten sposób w aplikacji jest tylko jeden singleton, pozostałe to fasola singleton w pojemniku.

Don Kirkby
źródło
15
Istnieje również lepszy interfejs dla ApplicationContexts - ApplicationContextAware. BeanFactoryAware powinien działać, ale musisz przerzucić go na kontekst aplikacji, jeśli potrzebujesz funkcji kontekstu aplikacji.
MetroidFan2002
@Don Kirkby Używanie wzorca singleton oznacza zaimplementowanie klasy kontenera za pomocą metody statycznej w klasie kontenera ... po „ręcznym” utworzeniu obiektu obiekt nie jest już zarządzany przez Springa: jak poradziłeś sobie z tym problemem?
Antonin
Moja pamięć jest trochę niejasna po dziewięciu latach, @Antonin, ale nie sądzę, że singletonem zarządzano w kontenerze Spring. Myślę, że jedynym zadaniem singletona było załadowanie kontenera z pliku XML i przetrzymanie go w zmiennej składowej statycznej. Nie zwróciłem wystąpienia własnej klasy, zwróciło ono wystąpienie kontenera Spring.
Don Kirkby
1
Dzięki Don Kirkby, wiosenny singleton posiadający statyczne odniesienie do siebie, dlatego może być użyteczny przez obiekty nie wiosenne.
Antonin
To może zadziałać, @Antonin, jeśli powiesz kontenerowi Spring, aby używał metody singletonu instance()jako fabryki. Myślę jednak, że najpierw pozwalam, aby cały kod poza kontenerem uzyskał dostęp do kontenera jako pierwszy. Następnie ten kod może żądać obiektów z kontenera.
Don Kirkby
118

Możesz wdrożyć ApplicationContextAwarelub po prostu użyć @Autowired:

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBeanzostanie ApplicationContextwstrzyknięty, w którym ta fasola jest tworzona. Na przykład, jeśli masz aplikację internetową o dość standardowej hierarchii kontekstów:

main application context <- (child) MVC context

i SpringBeanjest zadeklarowany w głównym kontekście, zostanie wprowadzony główny kontekst; w przeciwnym razie, jeśli zostanie zadeklarowany w kontekście MVC, zostanie wstrzyknięty kontekst MVC.

mniam mniam mniam
źródło
2
To pomogło wielu. Mam kilka dziwnych problemów ze starszą aplikacją z Spring 2.0, a twoja odpowiedź była jedynym sposobem, w jaki mogłem rozsądnie sprawić, by działało się z jednym ApplicationContext, z jednym kontenerem IoC Spring.
Stu Thompson
1
Czytelnicy .. Nie zapomnij zadeklarować tego SpringBean w pliku springconfig.xml jako komponentu bean.
supernowa
Co jeśli jest to już komponent Bean i używam Application.getApplicationContext () (wzorzec Singleton), który zwraca instancję nowego XXXXApplicationContext (XXXX), dlaczego to nie działa? Dlaczego muszę to zrobić automatycznie?
Jaskey
Można użyć @Injectteż
Alireza Fattahi
39

Oto dobry sposób (nie mój, oryginalne odniesienie jest tutaj: http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html

Użyłem tego podejścia i działa dobrze. Zasadniczo jest to prosta fasola, która zawiera (statyczne) odniesienie do kontekstu aplikacji. Odwołując się do niego w wiosennej konfiguracji, jest on inicjowany.

Spójrz na oryginalny ref, to jest bardzo jasne.

Steve B.
źródło
4
Takie podejście może się nie powieść, jeśli zadzwonisz getBeanz kodu uruchamianego podczas testu jednostkowego, ponieważ kontekst sprężyny nie zostanie skonfigurowany, zanim poprosisz o to. To stan wyścigu, w który właśnie uderzyłem dzisiaj po 2 latach udanego stosowania tego podejścia.
HDave
Pracuję nad tym samym ... nie z testu jednostkowego, ale z wyzwalacza bazy danych ... jakieś sugestie?
John Deverall,
Doskonała odpowiedź. Dziękuję Ci.
sagneta
17

Wierzę, że możesz użyć SingletonBeanFactoryLocator . Plik beanRefFactory.xml zawierałby rzeczywistą aplikacjęContext, wyglądałby mniej więcej tak:

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

A kod do pobrania fasoli z kontekstu aplikacji z dowolnego miejsca byłby mniej więcej taki:

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

Zespół wiosenny odradza korzystanie z tej klasy i yadayada, ale dobrze mi to pasuje tam, gdzie z niej korzystałem.

stian
źródło
11

Przed wdrożeniem jakichkolwiek innych sugestii zadaj sobie następujące pytania ...

  • Dlaczego próbuję uzyskać ApplicationContext?
  • Czy skutecznie używam ApplicationContext jako lokalizatora usług?
  • Czy mogę w ogóle uniknąć dostępu do ApplicationContext?

Odpowiedzi na te pytania są łatwiejsze w niektórych typach aplikacji (na przykład w aplikacjach internetowych) niż w innych, ale i tak warto je zadać.

Dostęp do ApplicationContext w pewien sposób narusza zasadę wstrzykiwania zależności, ale czasami nie masz dużego wyboru.

belugabob
źródło
5
Dobrym przykładem są tagi JSP; ich tworzenie jest zależne od kontenera serwletu, więc nie mają wyboru, jak tylko uzyskać kontekst statycznie. Spring udostępnia podstawowe klasy Tag i używają BeanFactoryLocators, aby uzyskać potrzebne konteksty.
skaffman
6

Jeśli używasz aplikacji internetowej, istnieje również inny sposób na dostęp do kontekstu aplikacji bez korzystania z singletonów za pomocą serwletu filtrów i ThreadLocal. W filtrze można uzyskać dostęp do kontekstu aplikacji za pomocą WebApplicationContextUtils i przechowywać kontekst aplikacji lub potrzebne komponenty bean w TheadLocal.

Uwaga: jeśli zapomnisz rozbroić ThreadLocal, będziesz mieć paskudne problemy podczas próby odinstalowania aplikacji! Dlatego powinieneś go ustawić i natychmiast rozpocząć próbę, która rozłączy ThreadLocal w końcowej części.

Oczywiście nadal używa singletonu: ThreadLocal. Ale rzeczywiste ziarna nie muszą już być. Może być nawet objęty zakresem żądań, a to rozwiązanie działa również, jeśli masz wiele WAR w aplikacji z bibliotekami w EAR. Mimo to możesz uznać użycie ThreadLocal za równie złe, jak użycie zwykłych singletonów. ;-)

Być może wiosna już oferuje podobne rozwiązanie? Nie znalazłem, ale nie jestem pewien.

Hans-Peter Störr
źródło
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);
  }
}

Źródło: http://sujitpal.blogspot.de/2007/03/accessing-spring-beans-from-legacy-code.html

Vanessa Schissato
źródło
5

Spójrz na ContextSingletonBeanFactoryLocator . Zapewnia statyczne akcesoria do przechwytywania kontekstów Springa, zakładając, że zostały zarejestrowane w określony sposób.

To nie jest ładne i bardziej złożone, niż byś chciał, ale działa.

skaffman
źródło
4

Istnieje wiele sposobów na uzyskanie kontekstu aplikacji w aplikacji Spring. Poniżej podano:

  1. Przez ApplicationContextAware :

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

Tutaj setApplicationContext(ApplicationContext applicationContext)metoda otrzymasz kontekst aplikacji

ApplicationContextAware :

Interfejs do zaimplementowania przez dowolny obiekt, który chce być powiadamiany o ApplicationContext, w którym działa. Implementacja tego interfejsu ma sens na przykład, gdy obiekt wymaga dostępu do zestawu współpracujących komponentów bean.

  1. Przez Autowired :

    @Autowired
    private ApplicationContext applicationContext;

Tutaj @Autowiredsłowo kluczowe zapewni kontekst aplikacji. Autowired ma jakiś problem. Stworzy to problem podczas testów jednostkowych.

Md. Sajedul Karim
źródło
3

Zauważ, że przechowując dowolny stan z prądu ApplicationContextlub ApplicationContextsamego w zmiennej statycznej - na przykład przy użyciu wzorca singletonu - sprawisz, że twoje testy będą niestabilne i nieprzewidywalne, jeśli używasz testu wiosennego. Wynika to z tego, że Spring-test buforuje i ponownie wykorzystuje konteksty aplikacji w tej samej maszynie JVM. Na przykład:

  1. Test Uruchom i jest opatrzony adnotacją @ContextConfiguration({"classpath:foo.xml"}).
  2. Uruchom test B i jest opatrzony adnotacją @ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. Uruchom test C i jest opatrzony adnotacjami @ContextConfiguration({"classpath:foo.xml"})

Po uruchomieniu testu A ApplicationContexttworzony jest an , a każda implementacja fasoli ApplicationContextAwarelub automatyczne połączenie ApplicationContextmoże zapisywać do zmiennej statycznej.

Po uruchomieniu testu B dzieje się to samo, a zmienna statyczna wskazuje teraz na test B. ApplicationContext

Po uruchomieniu testu C ziarna nie są tworzone, ponieważ TestContext(i tutaj ApplicationContext) z testu A jest ponownie wykorzystywany. Teraz masz zmienną statyczną wskazującą na inną ApplicationContextniż ta, która aktualnie trzyma ziarna do testu.

Gogstad
źródło
1

Nie wiem, czy będzie to przydatne, ale możesz także uzyskać kontekst podczas inicjalizacji aplikacji. To najwcześniej możesz uzyskać kontekst, nawet przed @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);
Chloe
źródło
0

Proszę to zanotować; poniższy kod utworzy nowy kontekst aplikacji zamiast używać już załadowanego.

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

Należy również pamiętać, że beans.xmlpowinien być częścią src/main/resourcesśrodków wojennych, których jest częścią WEB_INF/classes, gdzie jak prawdziwa aplikacja zostanie załadowana, o której applicationContext.xmlmowa w Web.xml.

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

Jest to trudne wspomnieć applicationContext.xmlścieżkę w ClassPathXmlApplicationContextkonstruktorze. ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml")nie będzie w stanie zlokalizować pliku.

Lepiej więc użyć istniejącego kontekstu aplikacji za pomocą adnotacji.

@Component
public class OperatorRequestHandlerFactory {

    public static ApplicationContext context;

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        context = applicationContext;
    }
}
Kanagavelu Sugumar
źródło
0

Wiem, że odpowiedź na to pytanie, ale chciałbym udostępnić kod Kotlin, który zrobiłem, aby odzyskać kontekst wiosenny.

Nie jestem specjalistą, dlatego jestem otwarty na krytyków, recenzje i porady:

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

Teraz kontekst wiosenny jest publicznie dostępny i może wywoływać tę samą metodę niezależnie od kontekstu (testy Junit, komponenty bean, ręcznie tworzone instancje klas), jak w przypadku tego serwletu Java:

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


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


    public MyWebServlet() {

    }
}
John John Pichler
źródło
0

Wykonaj autowire w Spring Bean jak poniżej: @Autowired private ApplicationContext appContext;

będziesz obiektem kontekstu aplikacji.

Sandeep Jain
źródło
0

Podejście 1: Możesz wstrzyknąć ApplicationContext, implementując interfejs ApplicationContextAware. Link referencyjny .

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

Podejście 2: Kontekst aplikacji Autowire w dowolnej fasoli zarządzanej wiosną.

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

Link referencyjny .

Hari Krishna
źródło