Obtendo o contexto do aplicativo Spring


216

Existe uma maneira de solicitar estaticamente / globalmente uma cópia do ApplicationContext em um aplicativo Spring?

Supondo que a classe principal seja inicializada e inicialize o contexto do aplicativo, ela deve ser transmitida através da pilha de chamadas para as classes que precisam, ou existe uma maneira de uma classe solicitar o contexto criado anteriormente? (Que eu assumo que deve ser um singleton?)

Respostas:


171

Se o objeto que precisa de acesso ao contêiner for um bean no contêiner, basta implementar o BeanFactoryAware ou ApplicationContextAware interfaces .

Se um objeto fora do contêiner precisar de acesso ao contêiner, usei um padrão singleton GoF padrão para o contêiner de primavera. Dessa forma, você possui apenas um singleton no seu aplicativo, o restante são todos os beans singleton no contêiner.


15
Também há uma interface melhor para ApplicationContexts - ApplicationContextAware. O BeanFactoryAware deve funcionar, mas você deve convertê-lo em um contexto de aplicativo se precisar da funcionalidade de contexto do aplicativo.
MetroidFan2002 30/03/09

@Don Kirkby Usar o padrão singleton significa instanciar sua classe de contêiner a partir de um método estático dentro de sua classe de contêiner ... depois que você "manualmente" instancia um objeto, ele não é mais gerenciado pelo Spring: como você resolveu esse problema?
Antonin

Minha memória é um pouco vaga depois de nove anos, @Antonin, mas não acho que o singleton tenha sido gerenciado dentro do contêiner Spring. Acho que o único trabalho do singleton era carregar o contêiner de um arquivo XML e mantê-lo em uma variável de membro estática. Eu não retornei uma instância de sua própria classe, ela retornou uma instância do contêiner Spring.
Don Kirkby

1
Obrigado Don Kirkby, um singleton do Spring que possui uma referência estática para si mesmo, portanto pode ser usado por objetos que não sejam do Spring.
Antonin

Isso pode funcionar, @Antonin, se você disser ao contêiner Spring para usar o instance()método do singleton como fábrica. No entanto, acho que deixei todo o código fora do contêiner acessar o contêiner primeiro. Então esse código pode solicitar objetos do contêiner.
Don Kirkby

118

Você pode implementar ApplicationContextAwareou apenas usar @Autowired:

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBeanterá ApplicationContextinjetado, dentro do qual esse bean é instanciado. Por exemplo, se você tiver um aplicativo da Web com uma hierarquia de contextos bastante padrão:

main application context <- (child) MVC context

e SpringBeané declarado no contexto principal, ele será injetado no contexto principal; caso contrário, se for declarado no contexto do MVC, ele será injetado no contexto do MVC.


2
Isso ajudou muito. Eu tenho alguns problemas estranhos com um aplicativo mais antigo com o Spring 2.0 e sua resposta foi a única maneira de fazer com que as coisas funcionassem com um único ApplicationContext, com um único contêiner Spring IoC.
Stu Thompson

1
Leitores .. Não se esqueça de declarar este SpringBean no seu springconfig.xml como um bean.
Supernova

E se esse já for um Bean e eu usar Application.getApplicationContext () (padrão Singleton), que retorna uma instância do novo XXXXApplicationContext (XXXX), por que não está funcionando? Por que eu tenho que conectá-lo automaticamente?
#

Você pode usar @Injecttambém
Alireza Fattahi

39

Aqui está uma maneira agradável (não minha, a referência original está aqui: http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html

Eu usei essa abordagem e funciona bem. Basicamente, é um bean simples que contém uma referência (estática) ao contexto do aplicativo. Ao referenciá-lo na configuração da primavera, ele é inicializado.

Dê uma olhada na ref original, é muito claro.


4
Essa abordagem pode falhar se você chamar getBeando código executado durante um teste de unidade, porque o contexto do Spring não será configurado antes de você solicitá-lo. É uma condição de corrida que acabei de bater hoje após 2 anos de sucesso usando essa abordagem.
usar o seguinte comando

Estou correndo para a mesma coisa .. não de um teste de unidade, mas de um gatilho de banco de dados .. alguma sugestão?
John Deverall

Excelente resposta. Obrigado.
sagneta

17

Eu acredito que você poderia usar SingletonBeanFactoryLocator . O arquivo beanRefFactory.xml conteria o applicationContext real, seria algo assim:

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

E o código para obter um bean do aplicativo application de onde quer que seja algo como isto:

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

A equipe da Primavera desencoraja o uso dessa classe e do yadayada, mas me serviu bem onde eu a usei.


11

Antes de implementar qualquer uma das outras sugestões, faça estas perguntas a si mesmo ...

  • Por que estou tentando obter o ApplicationContext?
  • Estou efetivamente usando o ApplicationContext como localizador de serviço?
  • Posso evitar acessar o ApplicationContext?

As respostas a essas perguntas são mais fáceis em certos tipos de aplicativos (aplicativos da Web, por exemplo) do que em outros, mas vale a pena perguntar de qualquer maneira.

O acesso ao ApplicationContext viola todo o princípio da injeção de dependência, mas às vezes você não tem muita escolha.


5
Um bom exemplo são as tags JSP; sua criação é governada pelo contêiner de servlet; portanto, eles não têm escolha a não ser obter o contexto estaticamente. O Spring fornece classes de tag base e elas usam BeanFactoryLocators para obter os contextos de que precisam.
27468 skaffman

6

Se você usar um aplicativo da Web, também há outra maneira de acessar o contexto do aplicativo sem usar singletons usando um servletfilter e um ThreadLocal. No filtro, você pode acessar o contexto do aplicativo usando WebApplicationContextUtils e armazenar o contexto do aplicativo ou os beans necessários no TheadLocal.

Cuidado: se você esquecer de desabilitar o ThreadLocal, terá problemas desagradáveis ​​ao tentar desimplementar o aplicativo! Portanto, você deve configurá-lo e iniciar imediatamente uma tentativa que desativa o ThreadLocal na parte final.

Obviamente, isso ainda usa um singleton: o ThreadLocal. Mas os grãos reais não precisam mais ser. Pode até ter um escopo de solicitação e essa solução também funcionará se você tiver vários WARs em um Aplicativo com as bibliotecas no EAR. Ainda assim, você pode considerar esse uso do ThreadLocal tão ruim quanto o uso de singletons simples. ;-)

Talvez o Spring já forneça uma solução semelhante? Não encontrei um, mas não tenho certeza.


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

Dê uma olhada em ContextSingletonBeanFactoryLocator . Ele fornece acessadores estáticos para se apossar dos contextos do Spring, supondo que eles tenham sido registrados de determinadas maneiras.

Não é bonito e mais complexo do que você gostaria, mas funciona.


4

Existem várias maneiras de obter o contexto do aplicativo no aplicativo Spring. Estes são dados abaixo:

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

Aqui setApplicationContext(ApplicationContext applicationContext)método, você receberá o applicationContext

ApplicationContextAware :

Interface a ser implementada por qualquer objeto que deseja ser notificado sobre o ApplicationContext em que é executado. A implementação dessa interface faz sentido, por exemplo, quando um objeto requer acesso a um conjunto de beans colaborativos.

  1. Via Autowired :

    @Autowired
    private ApplicationContext applicationContext;

Aqui a @Autowiredpalavra-chave fornecerá o applicationContext. O fio automático tem algum problema. Isso criará problemas durante o teste de unidade.


3

Observe que, armazenando qualquer estado da corrente ApplicationContextou a ApplicationContextprópria em uma variável estática - por exemplo, usando o padrão singleton - você tornará seus testes instáveis ​​e imprevisíveis se estiver usando o Spring-test. Isso ocorre porque o teste Spring armazena em cache e reutiliza os contextos de aplicativos na mesma JVM. Por exemplo:

  1. Teste A é executado e é anotado com @ContextConfiguration({"classpath:foo.xml"}) .
  2. O teste B é executado e é anotado com @ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. O teste C é executado e é anotado com @ContextConfiguration({"classpath:foo.xml"})

Quando o Teste A é executado, um ApplicationContexté criado e qualquer bean que ApplicationContextAwareimplemente ou fiação automáticaApplicationContext pode gravar na variável estática.

Quando o Teste B é executado, acontece a mesma coisa, e a variável estática agora aponta para os Testes B ApplicationContext

Quando o Teste C é executado, nenhum bean é criado quando o TestContext(e aqui o ApplicationContext) do Teste A é reutilizado. Agora você tem uma variável estática apontando para outra ApplicationContextque não a que está atualmente segurando o bean para o seu teste.


1

Não sabe ao certo quão útil isso será, mas você também pode obter o contexto ao inicializar o aplicativo. Este é o mais rápido que você pode obter no contexto, mesmo antes de um @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

Por favor note que; o código abaixo criará um novo contexto de aplicativo em vez de usar o já carregado.

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

Observe também que beans.xmldeve fazer parte dos src/main/resourcesmeios na guerra em que faz parte WEB_INF/classes, onde a aplicação real será carregada através do applicationContext.xmlmencionado em Web.xml.

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

É difícil mencionar o applicationContext.xmlcaminho no ClassPathXmlApplicationContextconstrutor. ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml")não conseguirá localizar o arquivo.

Portanto, é melhor usar o applicationContext existente usando anotações.

@Component
public class OperatorRequestHandlerFactory {

    public static ApplicationContext context;

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

0

Sei que esta pergunta foi respondida, mas gostaria de compartilhar o código Kotlin que fiz para recuperar o contexto da primavera.

Como não sou especialista, estou aberto a críticas, críticas e conselhos:

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

Agora, um contexto de primavera está disponível publicamente, podendo chamar o mesmo método independentemente do contexto (testes de junit, beans, classes instanciadas manualmente) como neste 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

Faça a conexão automática no Spring bean como abaixo: @Autowired private ApplicationContext appContext;

você será o objeto applicationcontext.


0

Abordagem 1: você pode injetar ApplicationContext implementando a interface ApplicationContextAware. Link de referência .

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

Abordagem 2: Contexto do aplicativo Autowire em qualquer um dos beans gerenciados por primavera.

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

Link de referência .

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.