Obtención del contexto de la aplicación Spring
-
02-07-2019 - |
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?)
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:
- Prueba una ejecución y se anota con
@ContextConfiguration ({" classpath: foo.xml "})
. - La prueba B se ejecuta y se anota con
@ContextConfiguration ({" classpath: foo.xml " ;, " classpath: bar.xml})
- 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
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:
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.
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;
}
}
Realice el cableado automático en Spring Bean como se muestra a continuación: @Autowired privado ApplicationContext appContext;
será el objeto applicationcontext.