Abrufen des Spring-Anwendungskontexts

216

Gibt es eine Möglichkeit, eine Kopie des ApplicationContext in einer Spring-Anwendung statisch / global anzufordern?

Angenommen, die Hauptklasse startet und initialisiert den Anwendungskontext. Muss sie diesen über den Aufrufstapel an alle Klassen weitergeben, die ihn benötigen, oder gibt es eine Möglichkeit für eine Klasse, nach dem zuvor erstellten Kontext zu fragen? (Was ich nehme an, muss ein Singleton sein?)

Joe Skora
quelle

Antworten:

171

Wenn das Objekt, das Zugriff auf den Container benötigt, eine Bean im Container ist, implementieren Sie einfach die Schnittstellen BeanFactoryAware oder ApplicationContextAware .

Wenn ein Objekt außerhalb des Containers Zugriff auf den Container benötigt, habe ich ein Standard-GoF-Singleton-Muster für den Federcontainer verwendet. Auf diese Weise haben Sie nur einen Singleton in Ihrer Anwendung, der Rest sind alle Singleton-Beans im Container.

Don Kirkby
quelle
15
Es gibt auch eine bessere Schnittstelle für ApplicationContexts - ApplicationContextAware. BeanFactoryAware sollte funktionieren, aber Sie müssten es in einen Anwendungskontext umwandeln, wenn Sie App-Kontextfunktionen benötigen.
MetroidFan2002
@Don Kirkby Wenn Sie das Singleton-Muster verwenden, müssen Sie Ihre Containerklasse über eine statische Methode in Ihrer Containerklasse instanziieren. Sobald Sie ein Objekt "manuell" instanziiert haben, wird es nicht mehr von Spring verwaltet. Wie haben Sie dieses Problem gelöst?
Antonin
Mein Gedächtnis ist nach neun Jahren ein wenig vage, @Antonin, aber ich glaube nicht, dass der Singleton im Spring-Container verwaltet wurde. Ich denke, die einzige Aufgabe des Singletons war es, den Container aus einer XML-Datei zu laden und in einer statischen Mitgliedsvariablen zu halten. Ich habe keine Instanz einer eigenen Klasse zurückgegeben, sondern eine Instanz des Spring-Containers.
Don Kirkby
1
Vielen Dank an Don Kirkby, einen Spring-Singleton, der einen statischen Verweis auf sich selbst besitzt und daher möglicherweise von Nicht-Spring-Objekten verwendet werden kann.
Antonin
Das könnte funktionieren, @Antonin, wenn Sie dem Spring-Container befehlen, die Singleton- instance()Methode als Factory zu verwenden. Ich denke jedoch, ich lasse nur den gesamten Code außerhalb des Containers zuerst auf den Container zugreifen. Dann könnte dieser Code Objekte vom Container anfordern.
Don Kirkby
118

Sie können implementieren ApplicationContextAwareoder einfach verwenden @Autowired:

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBeanwird ApplicationContextinjiziert haben, innerhalb dessen diese Bohne instanziiert wird. Zum Beispiel, wenn Sie eine Webanwendung mit einer ziemlich normalen Kontexthierarchie haben:

main application context <- (child) MVC context

und SpringBeanwird im Hauptkontext deklariert, es wird Hauptkontext injiziert; Andernfalls wird, wenn es im MVC-Kontext deklariert ist, der MVC-Kontext eingefügt.

OM Nom Nom
quelle
2
Das hat einem Haufen geholfen. Ich habe einige seltsame Probleme mit einer älteren App mit Spring 2.0 und Ihre Antwort war die einzige Möglichkeit, die Dinge mit einem einzigen ApplicationContext und einem einzigen Spring IoC-Container zum Laufen zu bringen.
Stu Thompson
1
Leser ... Vergessen Sie nicht, diese SpringBean in Ihrer springconfig.xml als Bean zu deklarieren.
Supernova
Was ist, wenn dies bereits eine Bean ist und ich Application.getApplicationContext () (Singleton-Muster) verwende, das eine Instanz des neuen XXXXApplicationContext (XXXX) zurückgibt? Warum funktioniert das nicht? Warum muss ich es automatisch verdrahten?
Jaskey
Sie können verwenden @Injectzu
Alireza Fattahi
39

Hier ist ein guter Weg (nicht meiner, die ursprüngliche Referenz ist hier: http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html

Ich habe diesen Ansatz verwendet und es funktioniert gut. Grundsätzlich handelt es sich um eine einfache Bean, die einen (statischen) Verweis auf den Anwendungskontext enthält. Durch Referenzieren in der Frühlingskonfiguration wird es initialisiert.

Schauen Sie sich den Original-Ref an, es ist sehr klar.

Steve B.
quelle
4
Dieser Ansatz kann fehlschlagen, wenn Sie getBeanvon Code aus aufrufen , der während eines Unit-Tests ausgeführt wird, da der Spring-Kontext nicht eingerichtet wird, bevor Sie danach fragen. Es ist eine Rennbedingung, in die ich heute nach 2 Jahren erfolgreicher Anwendung dieses Ansatzes geraten bin.
HDave
Ich stoße auf dasselbe .. nicht von einem Unit-Test, sondern von einem Datenbank-Trigger .. irgendwelche Vorschläge?
John Deverall
Hervorragende Resonanz. Danke dir.
Sagneta
17

Ich glaube, Sie könnten SingletonBeanFactoryLocator verwenden . Die Datei beanRefFactory.xml würde den tatsächlichen applicationContext enthalten. Es würde ungefähr so ​​aussehen:

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

Und der Code, um eine Bean aus dem Anwendungskontext zu erhalten, von wo auch immer so etwas wäre:

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

Das Spring-Team rät von der Verwendung dieser Klasse und von Yadayada ab, aber es hat mir gut gepasst, wo ich es verwendet habe.

Stian
quelle
11

Bevor Sie einen der anderen Vorschläge umsetzen, stellen Sie sich diese Fragen ...

  • Warum versuche ich, den ApplicationContext abzurufen?
  • Benutze ich den ApplicationContext effektiv als Service Locator?
  • Kann ich den Zugriff auf den ApplicationContext überhaupt vermeiden?

Die Antworten auf diese Fragen sind in bestimmten Arten von Anwendungen (z. B. Webanwendungen) einfacher als in anderen, aber es lohnt sich trotzdem, sie zu stellen.

Der Zugriff auf den ApplicationContext verstößt zwar gegen das gesamte Prinzip der Abhängigkeitsinjektion, aber manchmal haben Sie nicht viel Auswahl.

Belugabob
quelle
5
Ein gutes Beispiel sind JSP-Tags. Ihre Erstellung wird vom Servlet-Container gesteuert, sodass sie keine andere Wahl haben, als den Kontext statisch abzurufen. Spring bietet Basis-Tag-Klassen und verwendet BeanFactoryLocators, um die benötigten Kontexte abzurufen.
Skaffman
6

Wenn Sie eine Web-App verwenden, gibt es auch eine andere Möglichkeit, auf den Anwendungskontext zuzugreifen, ohne Singletons zu verwenden, indem Sie einen Servletfilter und einen ThreadLocal verwenden. Im Filter können Sie mit WebApplicationContextUtils auf den Anwendungskontext zugreifen und entweder den Anwendungskontext oder die erforderlichen Beans im TheadLocal speichern.

Achtung: Wenn Sie vergessen, ThreadLocal zu deaktivieren, treten beim Versuch, die Bereitstellung aufzuheben, böse Probleme auf! Daher sollten Sie es festlegen und sofort einen Versuch starten, der das ThreadLocal im Endteil deaktiviert.

Dies verwendet natürlich immer noch einen Singleton: den ThreadLocal. Aber die eigentlichen Bohnen müssen nicht mehr sein. Das kann sogar anforderungsbezogen sein, und diese Lösung funktioniert auch, wenn Sie mehrere WARs in einer Anwendung mit den Bibliotheken in der EAR haben. Dennoch können Sie diese Verwendung von ThreadLocal als genauso schlecht betrachten wie die Verwendung von einfachen Singletons. ;-);

Vielleicht bietet Spring bereits eine ähnliche Lösung? Ich habe keinen gefunden, aber ich weiß es nicht genau.

Hans-Peter Störr
quelle
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);
  }
}

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

Vanessa Schissato
quelle
5

Schauen Sie sich ContextSingletonBeanFactoryLocator an . Es bietet statische Accessoren, um die Kontexte von Spring abzurufen, sofern sie auf bestimmte Weise registriert wurden.

Es ist nicht schön und komplexer als Sie vielleicht möchten, aber es funktioniert.

Skaffman
quelle
4

Es gibt viele Möglichkeiten, den Anwendungskontext in der Spring-Anwendung abzurufen. Diese sind unten angegeben:

  1. Über 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;
    }
    }

Hier erhalten setApplicationContext(ApplicationContext applicationContext)Sie den applicationContext

ApplicationContextAware :

Schnittstelle, die von jedem Objekt implementiert werden soll, das über den ApplicationContext benachrichtigt werden möchte, in dem es ausgeführt wird. Die Implementierung dieser Schnittstelle ist beispielsweise sinnvoll, wenn ein Objekt Zugriff auf eine Reihe von zusammenarbeitenden Beans benötigt.

  1. Über Autowired :

    @Autowired
    private ApplicationContext applicationContext;

Hier @Autowiredliefert das Schlüsselwort den applicationContext. Autowired hat ein Problem. Dies führt zu Problemen beim Testen von Einheiten.

Md. Sajedul Karim
quelle
3

Beachten Sie, dass Sie Ihre Tests instabil und unvorhersehbar machen, wenn Sie Spring-Test verwenden , indem Sie einen beliebigen Status aus dem aktuellen ApplicationContextoder dem ApplicationContextselbst in einer statischen Variablen speichern - beispielsweise mithilfe des Singleton-Musters. Dies liegt daran, dass Spring-Test Anwendungskontexte in derselben JVM zwischenspeichert und wiederverwendet. Beispielsweise:

  1. Test Ein Lauf und es wird mit kommentiert @ContextConfiguration({"classpath:foo.xml"}).
  2. Test B wird ausgeführt und mit Anmerkungen versehen @ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. Test C wird ausgeführt und mit Anmerkungen versehen @ContextConfiguration({"classpath:foo.xml"})

Wenn Test A ausgeführt wird, wird ein ApplicationContexterstellt, und alle Beans, die implementiert ApplicationContextAwareoder automatisch verdrahtet werden ApplicationContext, können in die statische Variable schreiben.

Wenn Test B ausgeführt wird, passiert dasselbe, und die statische Variable zeigt jetzt auf Test B. ApplicationContext

Wenn Test C ausgeführt wird, werden keine Beans erstellt, da die TestContext(und hier die ApplicationContext) aus Test A wiederverwendet werden. Jetzt haben Sie eine statische Variable, die auf eine andere ApplicationContextals die zeigt, die derzeit die Beans für Ihren Test enthält.

Gogstad
quelle
1

Sie sind sich nicht sicher, wie nützlich dies sein wird, aber Sie können den Kontext auch beim Initialisieren der App abrufen. Dies ist der früheste Zeitpunkt, an dem Sie den Kontext abrufen können, noch bevor ein @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
quelle
0

Bitte beachte, dass; Der folgende Code erstellt einen neuen Anwendungskontext, anstatt den bereits geladenen zu verwenden.

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

Beachten Sie auch, dass beans.xmldies Teil der src/main/resourcesMittel im Krieg sein sollte, zu denen es gehört WEB_INF/classes, wo die eigentliche Anwendung durch das unter applicationContext.xmlerwähnte geladen wird Web.xml.

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

Es ist schwierig , den applicationContext.xmlPfad im ClassPathXmlApplicationContextKonstruktor zu erwähnen . ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml")kann die Datei nicht finden.

Daher ist es besser, vorhandenen applicationContext mithilfe von Anmerkungen zu verwenden.

@Component
public class OperatorRequestHandlerFactory {

    public static ApplicationContext context;

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        context = applicationContext;
    }
}
Kanagavelu Sugumar
quelle
0

Ich weiß, dass diese Frage beantwortet wurde, aber ich möchte den Kotlin-Code teilen, den ich zum Abrufen des Spring-Kontexts verwendet habe.

Ich bin kein Spezialist, daher bin ich offen für Kritiker, Bewertungen und Ratschläge:

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

Jetzt ist ein Spring-Kontext öffentlich verfügbar, der dieselbe kontextunabhängige Methode (Junit-Tests, Beans, manuell instanziierte Klassen) wie in diesem Java-Servlet aufrufen kann:

@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
quelle
0

Führen Sie die automatische Verdrahtung in Spring Bean wie folgt aus: @Autowired private ApplicationContext appContext;

Sie werden das applicationcontext-Objekt.

Sandeep Jain
quelle
0

Ansatz 1: Sie können ApplicationContext einfügen, indem Sie die ApplicationContextAware-Schnittstelle implementieren. Referenz Link .

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

Ansatz 2: Autowire-Anwendungskontext in einer der im Frühjahr verwalteten Beans.

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

Referenz Link .

Hari Krishna
quelle