Obtention du contexte d'application Spring

216

Existe-t-il un moyen de demander statiquement / globalement une copie d'ApplicationContext dans une application Spring?

En supposant que la classe principale démarre et initialise le contexte de l'application, doit-elle le transmettre à travers la pile d'appels à toutes les classes qui en ont besoin, ou existe-t-il un moyen pour une classe de demander le contexte créé précédemment? (Ce qui, je suppose, doit être un singleton?)

Joe Skora
la source

Réponses:

171

Si l'objet qui a besoin d'accéder au conteneur est un bean dans le conteneur, implémentez simplement les interfaces BeanFactoryAware ou ApplicationContextAware .

Si un objet en dehors du conteneur a besoin d'accéder au conteneur, j'ai utilisé un modèle singleton GoF standard pour le conteneur à ressort. De cette façon, vous n'avez qu'un seul singleton dans votre application, les autres sont tous des beans singleton dans le conteneur.

Don Kirkby
la source
15
Il existe également une meilleure interface pour ApplicationContexts - ApplicationContextAware. BeanFactoryAware devrait fonctionner mais vous devrez le caster dans un contexte d'application si vous avez besoin d'une fonctionnalité de contexte d'application.
MetroidFan2002
@Don Kirkby Utiliser le modèle singleton signifie instancier votre classe de conteneur d'une méthode statique au sein de votre classe de conteneur ... une fois que vous instanciez "manuellement" un objet, il n'est plus géré par Spring: comment avez-vous résolu ce problème?
Antonin
Ma mémoire est un peu vague après neuf ans, @Antonin, mais je ne pense pas que le singleton ait été géré dans le conteneur Spring. Je pense que le seul travail du singleton était de charger le conteneur à partir d'un fichier XML et de le conserver dans une variable membre statique. Je n'ai pas renvoyé une instance de sa propre classe, il a renvoyé une instance du conteneur Spring.
Don Kirkby
1
Merci Don Kirkby, un singleton Spring possédant une référence statique à lui-même, donc utilisable par des objets non Spring peut-être.
Antonin
Cela pourrait fonctionner, @Antonin, si vous disiez au conteneur Spring d'utiliser la instance()méthode du singleton comme usine. Cependant, je pense que je laisse tout le code en dehors du conteneur accéder au conteneur en premier. Ensuite, ce code pourrait demander des objets au conteneur.
Don Kirkby
118

Vous pouvez implémenter ApplicationContextAwareou simplement utiliser @Autowired:

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBeanaura ApplicationContextinjecté, dans lequel ce bean est instancié. Par exemple, si vous avez une application Web avec une hiérarchie de contextes assez standard:

main application context <- (child) MVC context

et SpringBeanest déclaré dans le contexte principal, il aura injecté le contexte principal; sinon, s'il est déclaré dans le contexte MVC, le contexte MVC sera injecté.

Om Nom Nom
la source
2
Cela a aidé un tas. J'ai des problèmes étranges avec une ancienne application avec Spring 2.0 et votre réponse était la seule façon de faire fonctionner les choses avec un seul ApplicationContext, avec un seul conteneur Spring IoC.
Stu Thompson
1
Lecteurs..N'oubliez pas de déclarer ce SpringBean dans votre springconfig.xml en tant que bean.
supernova
Que se passe-t-il si c'est déjà un bean et que j'utilise Application.getApplicationContext () (modèle Singleton), qui retourne une instance de nouveau XXXXApplicationContext (XXXX), pourquoi cela ne fonctionne pas? Pourquoi dois-je le câbler automatiquement?
Jaskey
Vous pouvez @Injectégalement utiliser
Alireza Fattahi
39

Voici une belle façon (pas la mienne, la référence d'origine est ici: http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html

J'ai utilisé cette approche et cela fonctionne bien. Fondamentalement, c'est un bean simple qui contient une référence (statique) au contexte de l'application. En le référençant dans la configuration du printemps, il est initialisé.

Jetez un oeil à la référence d'origine, c'est très clair.

Steve B.
la source
4
Cette approche peut échouer si vous appelez à getBeanpartir d'un code qui s'exécute pendant un test unitaire car le contexte Spring ne sera pas configuré avant que vous ne le demandiez. C'est une condition de course dans laquelle je viens de claquer aujourd'hui après 2 ans d'utilisation réussie de cette approche.
HDave
Je rencontre la même chose .. pas à partir d'un test unitaire mais à partir d'un déclencheur de base de données .. des suggestions?
John Deverall
Excellente réponse. Je vous remercie.
sagneta
17

Je pense que vous pouvez utiliser SingletonBeanFactoryLocator . Le fichier beanRefFactory.xml contiendrait le contexte d'application réel, il ressemblerait à ceci:

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

Et le code pour obtenir un bean à partir du contexte d'application où que ce soit quelque chose comme ça:

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

L'équipe de Spring décourage l'utilisation de cette classe et du yadayada, mais cela me convient bien là où je l'ai utilisé.

stian
la source
11

Avant de mettre en œuvre l'une des autres suggestions, posez-vous ces questions ...

  • Pourquoi j'essaye d'obtenir le ApplicationContext?
  • Suis-je en train d'utiliser efficacement ApplicationContext comme localisateur de services?
  • Puis-je éviter du tout d'accéder à ApplicationContext?

Les réponses à ces questions sont plus faciles dans certains types d'applications (applications Web, par exemple) que dans d'autres, mais valent quand même la peine d'être posées.

L'accès à ApplicationContext viole en quelque sorte le principe d'injection de dépendance dans son ensemble, mais parfois vous n'avez pas beaucoup de choix.

bélugabob
la source
5
Un bon exemple est les balises JSP; leur création est goverened par le conteneur de servlet, ils n'ont donc pas d'autre choix que d'obtenir le contexte statiquement. Spring fournit des classes de balises de base et utilise BeanFactoryLocators pour obtenir les contextes dont il a besoin.
skaffman
6

Si vous utilisez une application Web, il existe également un autre moyen d'accéder au contexte de l'application sans utiliser de singletons en utilisant un servletfilter et un ThreadLocal. Dans le filtre, vous pouvez accéder au contexte d'application à l'aide de WebApplicationContextUtils et stocker le contexte d'application ou les beans nécessaires dans TheadLocal.

Attention: si vous oubliez de désinstaller le ThreadLocal, vous aurez des problèmes désagréables en essayant de retirer l'application! Ainsi, vous devez le définir et commencer immédiatement un essai qui désactive le ThreadLocal dans la partie finale.

Bien sûr, cela utilise toujours un singleton: le ThreadLocal. Mais les haricots réels n'ont plus besoin d'être. La portée peut même être limitée à la demande, et cette solution fonctionne également si vous avez plusieurs fichiers WAR dans une application avec les bibliothèques dans le fichier EAR. Pourtant, vous pourriez considérer cette utilisation de ThreadLocal aussi mauvaise que l'utilisation de singletons simples. ;-)

Peut-être que Spring propose déjà une solution similaire? Je n'en ai pas trouvé, mais je n'en suis pas sûr.

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

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

Vanessa Schissato
la source
5

Jetez un œil à ContextSingletonBeanFactoryLocator . Il fournit des accesseurs statiques pour saisir les contextes de Spring, en supposant qu'ils ont été enregistrés de certaines manières.

Ce n'est pas joli et plus complexe que vous ne le souhaiteriez peut-être, mais cela fonctionne.

skaffman
la source
4

Il existe de nombreuses façons d'obtenir le contexte d'application dans l'application Spring. Ceux-ci sont donnés ci-dessous:

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

Ici setApplicationContext(ApplicationContext applicationContext), vous obtiendrez l'applicationContext

ApplicationContextAware :

Interface à implémenter par tout objet qui souhaite être informé de l'ApplicationContext dans lequel il s'exécute. L'implémentation de cette interface a un sens, par exemple lorsqu'un objet nécessite l'accès à un ensemble de beans collaboratifs.

  1. Via Autowired :

    @Autowired
    private ApplicationContext applicationContext;

Ici, le @Autowiredmot-clé fournira l'applicationContext. Autowired a un problème. Cela créera un problème lors des tests unitaires.

Md. Sajedul Karim
la source
3

Notez qu'en stockant n'importe quel état du courant ApplicationContextou le ApplicationContextlui - même dans une variable statique - par exemple en utilisant le modèle singleton - vous rendrez vos tests instables et imprévisibles si vous utilisez Spring-test. En effet, Spring-test met en cache et réutilise les contextes d'application dans la même JVM. Par exemple:

  1. Testez une exécution et elle est annotée avec @ContextConfiguration({"classpath:foo.xml"}).
  2. Test B run et il est annoté avec @ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. Test C run et il est annoté avec @ContextConfiguration({"classpath:foo.xml"})

Lorsque le test A s'exécute, un ApplicationContextest créé et tout bean ApplicationContextAwareimplémentant ou câblage automatique ApplicationContextpeut écrire dans la variable statique.

Lorsque le test B s'exécute, la même chose se produit et la variable statique pointe désormais vers les tests B ApplicationContext

Lorsque le test C s'exécute, aucun bean n'est créé car le TestContext(et ici le ApplicationContext) du test A est réutilisé. Vous avez maintenant une variable statique pointant vers une autre ApplicationContextque celle contenant actuellement les beans pour votre test.

gogstad
la source
1

Je ne sais pas à quel point cela sera utile, mais vous pouvez également obtenir le contexte lorsque vous initialisez l'application. C'est le plus tôt possible pour obtenir le contexte, même avant 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);
Chloe
la source
0

Veuillez noter que; le code ci-dessous créera un nouveau contexte d'application au lieu d'utiliser celui déjà chargé.

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

Notez également que cela beans.xmldevrait faire partie des src/main/resourcesmoyens de guerre dont il fait partie WEB_INF/classes, où la vraie application sera chargée via applicationContext.xmlmentionné à Web.xml.

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

Il est difficile de mentionner le applicationContext.xmlchemin dans le ClassPathXmlApplicationContextconstructeur.ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml")ne pourra pas localiser le fichier.

Il est donc préférable d'utiliser le contexte d'application existant en utilisant des annotations.

@Component
public class OperatorRequestHandlerFactory {

    public static ApplicationContext context;

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

Je sais que cette question reçoit une réponse, mais je voudrais partager le code Kotlin que j'ai fait pour récupérer le contexte de printemps.

Je ne suis pas un spécialiste, donc je suis ouvert aux critiques, critiques et conseils:

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

Désormais, un contexte Spring est accessible au public, pouvant appeler la même méthode indépendamment du contexte (tests junit, beans, classes instanciées manuellement) comme sur ce 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() {

    }
}
John John Pichler
la source
0

Effectuez le câblage automatique dans Spring Bean comme ci-dessous: @Autowired private ApplicationContext appContext;

vous serez l'objet applicationcontext.

Sandeep Jain
la source
0

Approche 1: vous pouvez injecter ApplicationContext en implémentant l'interface ApplicationContextAware. Lien de référence .

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

Approche 2: Contexte d'application Autowire dans l'un des beans gérés par le printemps.

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

Lien de référence .

Hari Krishna
la source