Pregunta

¿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?)

¿Fue útil?

Solución

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

Si un objeto fuera del contenedor necesita acceso al contenedor, he usado un singleton GoF estándar patrón 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.

Otros consejos

Puede implementar ApplicationContextAware o simplemente usar @Autowired :

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBean tendrá inyectado ApplicationContext , 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 SpringBean se 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.

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 ella en la configuración de primavera, se inicializa.

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

Creo que podría 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 la he usado.

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

  • ¿Por qué estoy tratando de obtener el ApplicationContext?
  • ¿Estoy usando 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.

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: el 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.

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

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

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

Tenga en cuenta que al almacenar cualquier estado del ApplicationContext actual, o del ApplicationContext en una variable estática, por ejemplo, utilizando el patrón singleton, hará que sus pruebas sean inestables e impredecible si está utilizando 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. Prueba 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, se crea un ApplicationContext , y cualquier bean que implemente ApplicationContextAware o el cableado automático ApplicationContext podría escribir en la variable estática.

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

de la Prueba B.

Cuando se ejecuta la Prueba C, no se crean beans como TestContext (y en este caso, ApplicationContext ) de la Prueba A se reutiliza. Ahora tiene una variable estática que apunta a otro ApplicationContext que el que actualmente contiene los beans para su prueba.

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

  1. A través de 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) obtendrá el applicationContext

  

ApplicationContextAware :

     

Interfaz para ser implementada por cualquier objeto que desee ser notificado   del ApplicationContext en el que se ejecuta. Implementación de esta interfaz   tiene sentido, por ejemplo, cuando un objeto requiere acceso a un conjunto de   frijoles colaboradores.

  1. Vía Autowired :

    @Autowired
    private ApplicationContext applicationContext;
    

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

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

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.xml debe ser parte de src / main / resources significa que en guerra es parte de WEB_INF / classes , donde como la aplicación real se cargará a través de applicationContext.xml mencionado 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 applicationContext.xml ruta en el constructor ClassPathXmlApplicationContext . 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;
    }
}

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, reseñas y consejos:

https://gist.github.com/edpichler/9e22309a86b97dbd4cb1ffe011>a

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 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() {

    }
}

Realice el cableado automático en Spring Bean como se muestra a continuación:   @Autowired   privado ApplicationContext appContext;

será el objeto applicationcontext.

scroll top