Ottenere il contesto dell'applicazione Spring


216

Esiste un modo per richiedere staticamente / globalmente una copia di ApplicationContext in un'applicazione Spring?

Supponendo che la classe principale si avvii e inizializzi il contesto dell'applicazione, è necessario passarlo attraverso lo stack di chiamate a tutte le classi che ne hanno bisogno o c'è un modo per una classe di chiedere il contesto precedentemente creato? (Che presumo debba essere un singleton?)

Risposte:


171

Se l'oggetto che deve accedere al contenitore è un bean nel contenitore, implementare semplicemente le interfacce BeanFactoryAware o ApplicationContextAware .

Se un oggetto esterno al contenitore necessita dell'accesso al contenitore, ho usato un modello singleton GoF standard per il contenitore a molla. In questo modo, hai solo un singleton nella tua applicazione, il resto sono tutti chicchi di singleton nel contenitore.


15
Esiste anche un'interfaccia migliore per ApplicationContexts: ApplicationContextAware. BeanFactoryAware dovrebbe funzionare ma dovresti eseguirne il cast in un contesto dell'applicazione se hai bisogno della funzionalità del contesto dell'app.
MetroidFan2002,

@Don Kirkby Usare il modello singleton significa istanziare la tua classe container da un metodo statico all'interno della tua classe container ... una volta che hai "instanciato" manualmente un oggetto che non è più gestito da Spring: come hai affrontato questo problema?
Antonin,

La mia memoria è un po 'vaga dopo nove anni, @Antonin, ma non credo che il singleton sia stato gestito all'interno del container Spring. Penso che l'unico lavoro del singleton sia stato quello di caricare il contenitore da un file XML e tenerlo in una variabile membro statica. Non ho restituito un'istanza della sua stessa classe, ha restituito un'istanza del contenitore Spring.
Don Kirkby,

1
Grazie Don Kirkby, un singleton di Spring che possiede un riferimento statico a se stesso, quindi utilizzabile da oggetti non Spring.
Antonin,

Potrebbe funzionare, @Antonin, se dicessi al contenitore Spring di usare il instance()metodo singleton come fabbrica. Tuttavia, penso di aver prima consentito a tutto il codice esterno al contenitore di accedere al contenitore. Quindi quel codice potrebbe richiedere oggetti dal contenitore.
Don Kirkby,

118

Puoi implementare ApplicationContextAwareo semplicemente usare @Autowired:

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBeansarà ApplicationContextiniettato, all'interno del quale viene istanziato questo bean. Ad esempio, se si dispone di un'applicazione Web con una gerarchia di contesti piuttosto standard:

main application context <- (child) MVC context

ed SpringBeanè dichiarato nel contesto principale, verrà iniettato il contesto principale; in caso contrario, se viene dichiarato nel contesto MVC, verrà inserito il contesto MVC.


2
Questo ha aiutato un sacco. Ho alcuni strani problemi con un'app precedente con Spring 2.0 e la tua risposta è stata l'unico modo per far funzionare le cose con un singolo ApplicationContext, con un singolo contenitore IoC Spring.
Stu Thompson,

1
Lettori ... Non dimenticate di dichiarare questo SpringBean nel vostro springconfig.xml come un bean.
supernova,

Cosa succede se questo è già un bean e utilizzo Application.getApplicationContext () (modello Singleton), che restituisce un'istanza del nuovo XXXXApplicationContext (XXXX), perché non funziona? Perché devo autowired?
Jaskey,

Puoi @Injectanche usare
Alireza Fattahi il

39

Ecco un bel modo (non mio, il riferimento originale è qui: http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html

Ho usato questo approccio e funziona benissimo. Fondamentalmente è un semplice bean che contiene un riferimento (statico) al contesto dell'applicazione. Facendo riferimento nella configurazione di primavera è inizializzato.

Dai un'occhiata al riferimento originale, è molto chiaro.


4
Tale approccio può non riuscire se si chiama getBeandal codice che viene eseguito durante un test unitario perché il contesto Spring non verrà impostato prima di richiederlo. È una condizione di gara a cui mi sono appena imbattuto oggi dopo 2 anni di successo con questo approccio.
HDave

Sto correndo nella stessa cosa .. non da un test unitario ma da un trigger di database .. qualche suggerimento?
John Deverall,

Risposta eccellente. Grazie.
sagneta,

17

Credo che potresti usare SingletonBeanFactoryLocator . Il file beanRefFactory.xml conterrebbe il vero applicationContext, sarebbe simile a questo:

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

E il codice per ottenere un bean dal applicationcontext da dove sarebbe qualcosa del genere:

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

Il team di Spring scoraggia l'uso di questa classe e yadayada, ma mi è andato bene dove l'ho usato.


11

Prima di implementare uno qualsiasi degli altri suggerimenti, poniti queste domande ...

  • Perché sto cercando di ottenere ApplicationContext?
  • Sto effettivamente usando ApplicationContext come localizzatore di servizi?
  • Posso evitare di accedere a ApplicationContext?

Le risposte a queste domande sono più facili in alcuni tipi di applicazioni (app Web, ad esempio) che in altre, ma vale comunque la pena chiedere.

L'accesso a ApplicationContext in qualche modo viola l'intero principio dell'iniezione di dipendenza, ma a volte non hai molta scelta.


5
Un buon esempio sono i tag JSP; la loro creazione è governata dal contenitore servlet, quindi non hanno altra scelta che ottenere il contesto staticamente. Spring fornisce classi di tag di base e utilizzano BeanFactoryLocators per ottenere i contesti di cui hanno bisogno.
Skaffman,

6

Se usi un'app Web c'è anche un altro modo per accedere al contesto dell'applicazione senza usare i singleton usando un servletfilter e un ThreadLocal. Nel filtro è possibile accedere al contesto dell'applicazione utilizzando WebApplicationContextUtils e archiviare il contesto dell'applicazione oi bean necessari in TheadLocal.

Attenzione: se si dimentica di annullare l'impostazione di ThreadLocal, si verificheranno problemi spiacevoli quando si tenta di annullare la distribuzione dell'applicazione! Pertanto, è necessario impostarlo e iniziare immediatamente un tentativo che disattiva ThreadLocal nella parte finale.

Ovviamente, questo utilizza ancora un singleton: ThreadLocal. Ma i fagioli reali non devono più essere. Può anche essere nell'ambito della richiesta, e questa soluzione funziona anche se hai più WAR in un'applicazione con le librerie nell'AER. Tuttavia, potresti considerare questo uso di ThreadLocal cattivo come l'uso di singoli singleton. ;-)

Forse Spring fornisce già una soluzione simile? Non ne ho trovato uno, ma non lo so per certo.


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

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


5

Dai un'occhiata a ContextSingletonBeanFactoryLocator . Fornisce accessori statici per acquisire i contesti di Spring, supponendo che siano stati registrati in alcuni modi.

Non è carino e più complesso di quanto forse ti piacerebbe, ma funziona.


4

Esistono molti modi per ottenere il contesto dell'applicazione nell'applicazione Spring. Quelli sono dati sotto:

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

Qui il setApplicationContext(ApplicationContext applicationContext)metodo otterrai applicationContext

ApplicationContextAware :

L'interfaccia deve essere implementata da qualsiasi oggetto che desideri ricevere una notifica di ApplicationContext in cui è in esecuzione. L'implementazione di questa interfaccia ha senso, ad esempio, quando un oggetto richiede l'accesso a un set di bean collaboranti.

  1. Via Autowired :

    @Autowired
    private ApplicationContext applicationContext;

Qui la @Autowiredparola chiave fornirà applicationContext. Autowired ha qualche problema. Creerà problemi durante i test unitari.


3

Notare che memorizzando qualsiasi stato dalla corrente ApplicationContexto lo ApplicationContextstesso in una variabile statica, ad esempio utilizzando il modello singleton, si renderanno instabili e imprevedibili i test se si utilizza Spring-test. Questo perché Spring-test memorizza nella cache e riutilizza i contesti applicativi nella stessa JVM. Per esempio:

  1. Test Una corsa ed è annotato con @ContextConfiguration({"classpath:foo.xml"}).
  2. Il test B viene eseguito ed è annotato con @ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. Esegui il test C ed è annotato con @ContextConfiguration({"classpath:foo.xml"})

Quando viene eseguito il test A, ApplicationContextviene creato un e qualsiasi bean ApplicationContextAwareche implementa o autowiring ApplicationContextpotrebbe scrivere sulla variabile statica.

Quando il Test B viene eseguito, accade la stessa cosa e la variabile statica ora punta ai Test B. ApplicationContext

Quando viene eseguito il test C, non vengono creati bean poiché TestContext(e nel presente documento ApplicationContext) dal test A viene ripristinato. Ora hai una variabile statica che punta a un'altra ApplicationContextrispetto a quella che attualmente contiene i bean per il tuo test.


1

Non sei sicuro di quanto utile sarà, ma puoi anche ottenere il contesto quando inizializzi l'app. Questo è il più presto possibile ottenere il contesto, anche prima di un @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

Si prega di notare che; il codice seguente creerà un nuovo contesto dell'applicazione invece di utilizzare quello già caricato.

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

Si noti inoltre che beans.xmldovrebbe essere parte dei src/main/resourcesmezzi in guerra di cui fa parte WEB_INF/classes, dove l'applicazione reale verrà caricata attraverso applicationContext.xmlmenzionata in Web.xml.

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

È difficile menzionare il applicationContext.xmlpercorso nel ClassPathXmlApplicationContextcostruttore. ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml")non sarà in grado di individuare il file.

Quindi è meglio usare applicationContext esistente usando le annotazioni.

@Component
public class OperatorRequestHandlerFactory {

    public static ApplicationContext context;

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

0

So che questa domanda ha una risposta, ma vorrei condividere il codice di Kotlin che ho fatto per recuperare il contesto di primavera.

Non sono uno specialista, quindi sono aperto a critici, recensioni e consigli:

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

Ora, un contesto primaverile è pubblicamente disponibile, essendo in grado di chiamare lo stesso metodo indipendentemente dal contesto (test junit, bean, classi istanziate manualmente) come su questo Servlet Java:

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


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


    public MyWebServlet() {

    }
}

0

Effettua l'autowire nel bean Spring come di seguito: @Autowired applicationContext appContext privato;

sarai l'oggetto applicationcontext.


0

Approccio 1: è possibile iniettare ApplicationContext implementando l'interfaccia ApplicationContextAware. Link di riferimento .

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

Approccio 2: contesto dell'applicazione Autowire in uno qualsiasi dei bean gestiti a molla.

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

Link di riferimento .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.