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.