web-dev-qa-db-fra.com

Obtenir le contexte d'application du printemps

Existe-t-il un moyen de demander de manière statique/globale une copie de ApplicationContext dans une application Spring?

En supposant que la classe principale démarre et initialise le contexte de l'application, est-il nécessaire de le transmettre via 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?)

206
Joe Skora

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

Si un objet situé en dehors du conteneur nécessite un accès au conteneur, j'ai utilisé un modèle de singleton GoF standard pour le conteneur Spring. De cette façon, vous n'avez qu'un seul singleton dans votre application, les autres sont tous des singleton beans dans le conteneur.

165
Don Kirkby

Vous pouvez implémenter ApplicationContextAware ou simplement utiliser @Autowired:

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBean aura ApplicationContext injecté, dans lequel ce haricot 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 SpringBean est déclaré dans le contexte principal, le contexte principal sera injecté; sinon, s'il est déclaré dans le contexte MVC, le contexte MVC sera injecté.

114
omnomnom

Voici une bonne façon (pas la mienne, la référence originale 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 simple haricot qui contient une référence (statique) au contexte de l'application. En le référençant dans la configuration de printemps, il est initialisé.

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

38
Steve B.

Je crois que vous pourriez utiliser SingletonBeanFactoryLocator . Le fichier beanRefFactory.xml contiendrait le contexte d'application actuel. 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 haricot à partir du contexte de l'application, d'où qu'il vienne, ressemble à ceci:

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

L’équipe de Spring a déconseillé l’utilisation de cette classe et de yadayada, mais cela m’arrange bien là où je l’ai utilisé.

18
stian

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

  • Pourquoi est-ce que j'essaie d'obtenir l'ApplicationContext?
  • Est-ce que j'utilise efficacement ApplicationContext en tant que localisateur de services?
  • Puis-je éviter tout accès à 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.

Accéder à ApplicationContext enfreint en quelque sorte le principe d'injection de dépendance, mais parfois vous n'avez pas beaucoup de choix.

11
belugabob

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

Attention: si vous oubliez de désélectionner le ThreadLocal, vous rencontrerez des problèmes désagréables lorsque vous essayez d'annuler le déploiement de l'application! Ainsi, vous devez le définir et commencer immédiatement un essai qui désactive le ThreadLocal dans la partie finally.

Bien sûr, cela utilise toujours un singleton: le ThreadLocal. Mais les haricots réels ne doivent plus l'être. Le peut même être limité à la demande, et cette solution fonctionne également si vous avez plusieurs fichiers WAR dans une application avec les bibliothèques dans le fichier EAR. Néanmoins, vous pouvez considérer cette utilisation de ThreadLocal aussi néfaste que celle de singletons simples. ;-)

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

6
Hans-Peter Störr
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

5
Vanessa Schissato

Jetez un oeil à ContextSingletonBeanFactoryLocator . Il fournit des accesseurs statiques pour obtenir les contextes de Spring, à condition qu'ils aient été enregistrés de certaines manières.

Ce n'est pas joli et plus complexe que vous ne le souhaitez peut-être, mais ça marche.

5
skaffman

Notez qu'en stockant tout état à partir du ApplicationContext ou du ApplicationContext lui-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 machine virtuelle. Par exemple:

  1. Testez un cycle et il est annoté avec @ContextConfiguration({"classpath:foo.xml"}).
  2. Le test B est exécuté et il est annoté avec @ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. Le test C est exécuté et il est annoté avec @ContextConfiguration({"classpath:foo.xml"})

Lorsque le test A est exécuté, un ApplicationContext est créé et tout beans implémentant ApplicationContextAware ou autowiring ApplicationContext pourrait écrire dans la variable statique.

Lorsque le test B s'exécute, la même chose se produit et la variable statique pointe maintenant sur le test ApplicationContext

Lorsque le test C est exécuté, aucun bean n'est créé sous le nom TestContext (et ici, ApplicationContext) du test A est renvoyé. Vous avez maintenant une variable statique pointant vers un autre ApplicationContext que celui qui contient actuellement les beans pour votre test.

3
gogstad

Il existe de nombreuses façons d'obtenir un 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) méthode vous obtiendrez le applicationContext

ApplicationContextAware:

Interface à implémenter par tout objet souhaitant être averti du champ ApplicationContext dans lequel il s'exécute. L'implémentation de cette interface est utile, par exemple, lorsqu'un objet nécessite l'accès à un ensemble de beans en collaboration.

  1. Via Autowired:

    @Autowired
    private ApplicationContext applicationContext;
    

Ici, le mot clé @Autowired fournira le champ applicationContext. Le câblage automatique a un problème. Cela créera un problème lors des tests unitaires.

2
Md. Sajedul Karim

Vous ne savez 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);
1
Chloe

Faites le transfert automatique dans Spring bean comme ci-dessous: @Autowired private ApplicationContext appContext;

vous aurez l'objet applicationcontext.

0
Sandeep Jain

Je sais que cette question a reçu une réponse, mais j'aimerais partager le code Kotlin que j'ai créé pour extraire le contexte de printemps.

Je ne suis pas un spécialiste, je suis donc ouvert aux critiques, aux critiques et aux 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;
        }
    }
}

Maintenant, un contexte de ressort est disponible publiquement, pouvant appeler la même méthode indépendamment du contexte (tests junit, beans, classes instanciées manuellement) comme sur ce Java Servlet:

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


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


    public MyWebServlet() {

    }
}
0
John John Pichler

S'il vous plaît 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 beans.xml devrait faire partie de src/main/resources signifie en guerre qu'il fait partie de WEB_INF/classes, où l'application réelle sera chargée via applicationContext.xml mentionné à 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 applicationContext.xml chemin dans le constructeur ClassPathXmlApplicationContext. ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml") ne pourra pas localiser le fichier.

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

@Component
public class OperatorRequestHandlerFactory {

    public static ApplicationContext context;

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