java - interfaz - Obtención de contexto de aplicación de primavera
java grafica (12)
¿Hay alguna manera de solicitar una copia de ApplicationContext en una aplicación Spring de forma estática / global?
Suponiendo que la clase principal se inicie e inicialice el contexto de la aplicación, ¿debe pasar eso a través de la pila de llamadas a alguna clase que lo necesite, o existe una forma en que una clase pueda solicitar el contexto creado anteriormente? (¿Que supongo que tiene que ser un singleton?)
Antes de implementar cualquiera de las otras sugerencias, hágase estas preguntas ...
- ¿Por qué estoy tratando de obtener el ApplicationContext?
- ¿Estoy utilizando efectivamente ApplicationContext como un localizador de servicios?
- ¿Puedo evitar acceder a ApplicationContext en absoluto?
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 preguntarlas de todos modos.
Acceder a ApplicationContext viola el principio de inyección de dependencia, pero a veces no tiene muchas opciones.
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 utilizado este enfoque y funciona bien. Básicamente es un simple bean que contiene una referencia (estática) al contexto de la aplicación. Al hacer referencia a él en la configuración de primavera se ha inicializado.
Echa un vistazo a la referencia original, es muy claro.
Creo que podrías usar SingletonBeanFactoryLocator . El archivo beanRefFactory.xml contendría la aplicación realContext, sería algo como esto:
<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 desde el contexto de la aplicación desde donde sea algo como esto:
BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
BeanFactoryReference bf = bfl.useBeanFactory("mainContext");
SomeService someService = (SomeService) bf.getFactory().getBean("someService");
El equipo de Spring desalienta el uso de esta clase y la yadayada, pero me ha ido bien donde lo he usado.
Echa un vistazo a ContextSingletonBeanFactoryLocator . Proporciona accesores estáticos para controlar los contextos de Spring, asumiendo que se hayan registrado de ciertas maneras.
No es bonito, y más complejo de lo que quizás te gustaría, pero funciona.
Hay muchas formas de obtener el contexto de la aplicación en la aplicación Spring. Los que 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í el setApplicationContext(ApplicationContext applicationContext)
obtendrá el applicationContext
ApplicationContextAware :
La interfaz debe ser implementada por cualquier objeto que desee recibir una notificación 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.
Via Autowired :
@Autowired private ApplicationContext applicationContext;
Aquí la palabra clave @Autowired
proporcionará el applicationContext. Autowired tiene algún problema. Creará problema durante la prueba de la unidad.
Gracias :)
Sé que esta pregunta está respondida, pero me gustaría compartir el código de Kotlin que hice para recuperar el Contexto de Spring.
No soy un 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 de junit, beans, clases creadas de manera manual) como en este Servlet de Java:
@WebServlet(name = "MyWebHook", value = "/WebHook")
public class MyWebServlet extends HttpServlet {
private MyBean byBean
= SpringUtils.INSTANCE.ctx(this, true).getBean(MyBean.class);
public MyWebServlet() {
}
}
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 singleton GoF estándar para el contenedor de resorte. De esa manera, solo tiene un singleton en su aplicación, el resto son frijoles singleton en el contenedor.
Si usa una aplicación web, también hay otra forma de acceder al contexto de la aplicación sin usar singletons utilizando 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 ThreadLocal, tendrá problemas desagradables al intentar desinstalar la aplicación. Por lo tanto, debe configurarlo e iniciar inmediatamente un intento que desmonte el ThreadLocal en la parte final.
Por supuesto, esto todavía usa un singleton: el ThreadLocal. Pero los frijoles reales no necesitan ser más. Incluso puede tener un ámbito de solicitud, y esta solución también funciona si tiene varios WAR en una aplicación con las bibliotecas en el EAR. Aún así, podría considerar este uso de ThreadLocal tan malo como el uso de singletons simples. ;-)
Tal vez Spring ya proporciona una solución similar? No encontré uno, pero no estoy seguro.
Tenga en cuenta que al almacenar cualquier estado desde el ApplicationContext
actual, o el propio ApplicationContext
en una variable estática, por ejemplo, utilizando el patrón de singleton, hará que sus pruebas sean inestables e impredecibles si está usando la prueba Spring. Esto se debe a que Spring-test almacena en caché y reutiliza los 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})
- Ejecute la prueba C y se anota con
@ContextConfiguration({"classpath:foo.xml"})
Cuando se ejecuta la Prueba A, se crea un ApplicationContext
, y cualquier bean que implemente ApplicationContext
o autowiring ApplicationContext
puede escribir en la variable estática.
Cuando la Prueba B se ejecuta, sucede lo mismo, y la variable estática ahora apunta a la ApplicationContext
Prueba B.
Cuando se ejecuta la Prueba C, no se crean beans como el TestContext
(y aquí el ApplicationContext
) de la Prueba A se vuelve a utilizar. Ahora tienes una variable estática que apunta a otro ApplicationContext
que el que actualmente tiene los beans para tu prueba.
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
debería ser parte de los medios src/main/resources
en war, es parte de WEB_INF/classes
, donde la aplicación real se cargará a través de applicationContext.xml
menciona en Web.xml
.
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>META-INF/spring/applicationContext.xml</param-value>
</context-param>
Es difficult mencionar la ruta de applicationContext.xml
en el constructor ClassPathXmlApplicationContext
. ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml")
no podrá localizar el archivo.
Por lo tanto, es mejor utilizar applicationContext existente mediante el uso de anotaciones.
@Component
public class OperatorRequestHandlerFactory {
public static ApplicationContext context;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
context = applicationContext;
}
}
Puede implementar ApplicationContextAware
o simplemente usar @Autowired
:
public class SpringBean {
@Autowired
private ApplicationContext appContext;
}
SpringBean
tendrá ApplicationContext
inyectado, dentro del cual se SpringBean
instancia de 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.
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