Obteniendo el contexto de la aplicación Spring

216

¿Hay alguna manera de solicitar estática / globalmente una copia del ApplicationContext en una aplicación Spring?

Suponiendo que la clase principal se inicia e inicializa el contexto de la aplicación, ¿tiene que pasar eso a través de la pila de llamadas a cualquier clase que lo necesite, o hay una forma de que una clase solicite el contexto creado previamente? (¿Qué asumo tiene que ser un singleton?)

Joe Skora
fuente

Respuestas:

171

Si el objeto que necesita acceso al contenedor es un bean en el contenedor, simplemente implemente las interfaces BeanFactoryAware o ApplicationContextAware .

Si un objeto fuera del contenedor necesita acceso al contenedor, he usado un patrón estándar de GoF singleton para el contenedor de resorte. De esa manera, solo tiene un singleton en su aplicación, el resto son todos frijoles singleton en el contenedor.

Don Kirkby
fuente
15
También hay una mejor interfaz para ApplicationContexts: ApplicationContextAware. BeanFactoryAware debería funcionar, pero tendría que convertirlo en un contexto de aplicación si necesita la funcionalidad del contexto de la aplicación.
MetroidFan2002
@Don Kirkby Usar el patrón singleton significa instanciar su clase contenedor a partir de un método estático dentro de su clase contenedor ... una vez que instancia "manualmente" un objeto, Spring ya no lo administra: ¿cómo resolvió este problema?
Antonin
Mi memoria es un poco vaga después de nueve años, @Antonin, pero no creo que el singleton se haya gestionado dentro del contenedor Spring. Creo que el único trabajo del singleton era cargar el contenedor desde un archivo XML y mantenerlo en una variable miembro estática. No devolví una instancia de su propia clase, devolvió una instancia del contenedor Spring.
Don Kirkby
1
Gracias Don Kirkby, un singleton de Spring que posee una referencia estática a sí mismo, por lo que puede ser utilizado por objetos que no sean Spring.
Antonin
Eso podría funcionar, @Antonin, si le dijeras al contenedor Spring que usara el instance()método singleton como fábrica. Sin embargo, creo que primero dejé que todo el código fuera del contenedor acceda al contenedor. Entonces ese código podría solicitar objetos del contenedor.
Don Kirkby
118

Puede implementar ApplicationContextAwareo simplemente usar @Autowired:

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBeanhabrá ApplicationContextinyectado, dentro del cual se instanciará este bean. Por ejemplo, si tiene una aplicación web con una jerarquía de contextos bastante estándar:

main application context <- (child) MVC context

y SpringBeanse declara dentro del contexto principal, se inyectará el contexto principal; de lo contrario, si se declara dentro del contexto MVC, se inyectará el contexto MVC.

Om nom nom
fuente
2
Esto ayudó mucho. Tengo algunos problemas extraños con una aplicación anterior con Spring 2.0 y su respuesta fue la única forma en que podía hacer que las cosas funcionaran con un solo ApplicationContext, con un solo contenedor Spring IoC.
Stu Thompson
1
Lectores ... No olviden declarar este SpringBean en su springconfig.xml como un bean.
supernova
¿Qué pasa si esto ya es un Bean y uso Application.getApplicationContext () (patrón Singleton), que devuelve una instancia de nuevo XXXXApplicationContext (XXXX), ¿por qué no funciona? ¿Por qué tengo que conectarlo automáticamente?
Jaskey
Se puede utilizar @Injecttambién
Alireza Fattahi
39

Aquí hay una buena manera (no la mía, la referencia original está aquí: http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html

He usado este enfoque y funciona bien. Básicamente es un bean simple que contiene una referencia (estática) al contexto de la aplicación. Al hacer referencia a él en la configuración de primavera, se inicializa.

Echa un vistazo a la referencia original, está muy claro.

Steve B.
fuente
44
Ese enfoque puede fallar si llama getBeandesde un código que se ejecuta durante una prueba de Unidad porque el contexto Spring no se configurará antes de que lo solicite. Es una condición de carrera que acabo de golpear hoy después de 2 años de usar con éxito este enfoque.
HDave
Me encuentro con lo mismo ... no desde una prueba unitaria sino desde un disparador de base de datos ... ¿alguna sugerencia?
John Deverall
Excelente respuesta Gracias.
sagneta
17

Creo que podrías usar SingletonBeanFactoryLocator . El archivo beanRefFactory.xml contendría el contexto de aplicación real, sería algo así:

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

Y el código para obtener un bean del contexto de la aplicación de donde sea sería algo como esto:

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

El equipo de Spring desaconseja el uso de esta clase y yadayada, pero me ha quedado bien donde lo he usado.

stian
fuente
11

Antes de implementar cualquiera de las otras sugerencias, hágase estas preguntas ...

  • ¿Por qué estoy tratando de obtener el ApplicationContext?
  • ¿Estoy utilizando efectivamente el ApplicationContext como un localizador de servicios?
  • ¿Puedo evitar acceder al ApplicationContext?

Las respuestas a estas preguntas son más fáciles en ciertos tipos de aplicaciones (aplicaciones web, por ejemplo) que en otras, pero vale la pena preguntar de todos modos.

Acceder al ApplicationContext viola el principio de inyección de dependencia, pero a veces no tienes muchas opciones.

belugabob
fuente
55
Un buen ejemplo son las etiquetas JSP; su creación se rige por el contenedor de servlet, por lo que no tienen más remedio que obtener el contexto estáticamente. Spring proporciona clases base de etiquetas, y usan BeanFactoryLocators para obtener los contextos que necesitan.
skaffman
6

Si usa una aplicación web, también hay otra forma de acceder al contexto de la aplicación sin usar singletons usando un servletfilter y un ThreadLocal. En el filtro, puede acceder al contexto de la aplicación utilizando WebApplicationContextUtils y almacenar el contexto de la aplicación o los beans necesarios en TheadLocal.

Precaución: si olvida desarmar el ThreadLocal, obtendrá problemas desagradables al intentar desinstalar la aplicación. Por lo tanto, debe configurarlo e inmediatamente comenzar un intento que desarma el ThreadLocal en la parte final.

Por supuesto, esto todavía usa un singleton: ThreadLocal. Pero los frijoles reales ya no necesitan serlo. Incluso puede tener un alcance de solicitud, y esta solución también funciona si tiene múltiples WAR en una aplicación con las bibliotecas en el EAR. Aún así, puede considerar este uso de ThreadLocal tan malo como el uso de singletons simples. ;-)

¿Quizás Spring ya ofrece una solución similar? No encontré uno, pero no estoy seguro.

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

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

Vanessa Schissato
fuente
5

Eche un vistazo a ContextSingletonBeanFactoryLocator . Proporciona accesores estáticos para controlar los contextos de Spring, suponiendo que se hayan registrado de ciertas maneras.

No es bonito y más complejo de lo que te gustaría, pero funciona.

skaffman
fuente
4

Hay muchas formas de obtener el contexto de la aplicación en la aplicación Spring. Esos se dan a continuación:

  1. Vía 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;
    }
    }

Aquí setApplicationContext(ApplicationContext applicationContext)método obtendrá el applicationContext

ApplicationContextAware :

La interfaz debe ser implementada por cualquier objeto que desee ser notificado del ApplicationContext en el que se ejecuta. La implementación de esta interfaz tiene sentido, por ejemplo, cuando un objeto requiere acceso a un conjunto de beans de colaboración.

  1. Vía Autowired :

    @Autowired
    private ApplicationContext applicationContext;

Aquí la @Autowiredpalabra clave proporcionará el contexto de la aplicación. Autowired tiene algún problema. Creará un problema durante las pruebas unitarias.

MD Sajedul Karim
fuente
3

Tenga en cuenta que al almacenar cualquier estado desde el actual ApplicationContexto el ApplicationContextpropio en una variable estática, por ejemplo, utilizando el patrón singleton, hará que sus pruebas sean inestables e impredecibles si está usando Spring-test. Esto se debe a que Spring-test almacena en caché y reutiliza contextos de aplicación en la misma JVM. Por ejemplo:

  1. Pruebe una ejecución y se anota con @ContextConfiguration({"classpath:foo.xml"}).
  2. La prueba B se ejecuta y se anota con @ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. La prueba C se ejecuta y se anota con @ContextConfiguration({"classpath:foo.xml"})

Cuando se ejecuta la Prueba A, ApplicationContextse crea un an , y cualquier ApplicationContextAwarebean ApplicationContextque implemente o se conecte automáticamente podría escribir en la variable estática.

Cuando se ejecuta la Prueba B, sucede lo mismo y la variable estática ahora apunta a la Prueba B ApplicationContext

Cuando Ensayo C se ejecuta, no hay granos se crean como el TestContext(y en el presente documento la ApplicationContext) de prueba A se resused. Ahora tiene una variable estática que apunta a otra ApplicationContextque no es la que actualmente contiene los beans para su prueba.

Gogstad
fuente
1

No estoy seguro de lo útil que será, pero también puede obtener el contexto cuando inicializa la aplicación. Esto es lo más pronto que puede obtener el contexto, incluso antes de 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
fuente
0

Tenga en cuenta que; El siguiente código creará un nuevo contexto de aplicación en lugar de utilizar el ya cargado.

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

También tenga en cuenta que beans.xmldebe ser parte de los src/main/resourcesmedios en la guerra que es parte de WEB_INF/classes, ya que la aplicación real se cargará a través de applicationContext.xmlmencionado en Web.xml.

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

Es difícil mencionar la applicationContext.xmlruta en el ClassPathXmlApplicationContextconstructor. ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml")No podrá localizar el archivo.

Por lo tanto, es mejor usar applicationContext existente mediante anotaciones.

@Component
public class OperatorRequestHandlerFactory {

    public static ApplicationContext context;

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

Sé que esta pregunta está respondida, pero me gustaría compartir el código de Kotlin que hice para recuperar el Contexto de Primavera.

No soy especialista, por lo que estoy abierto a críticas, críticas y consejos:

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

Ahora, un contexto de primavera está disponible públicamente, pudiendo llamar al mismo método independientemente del contexto (pruebas junit, beans, clases instanciadas manualmente) como en este 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() {

    }
}
John John Pichler
fuente
0

Realice el cableado automático en Spring Bean de la siguiente manera: @Autowired private ApplicationContext appContext;

será el objeto applicationcontext.

Sandeep Jain
fuente
0

Enfoque 1: puede inyectar ApplicationContext implementando la interfaz ApplicationContextAware. Referencia enlace .

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

Enfoque 2: contexto de aplicación de autowire en cualquiera de los beans gestionados por Spring.

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

Referencia enlace .

Hari Krishna
fuente