Alfonso Marín López

Blog personal de un programador

Integración de spring y GWT

| 7 Comments

Vamos a tratar la gestión de beans de spring dentro de GWT. En principio no tiene nada de especial. Solo hay que arrancar el contexto de spring para poder solicitar los beans que queramos.

Despues de navegar mucho, pero que mucho, he encontrado 3 metodos:

El primero y más simple, cargar el archivo de configuración xml en cualquier momento

org.springframework.beans.factory.access.BeanFactoryLocator beanFactoryLocator = org.springframework.context.access.ContextSingletonBeanFactoryLocator.getInstance(“Archivo-configuracion.xml”);

beanFactoryReference = beanFactoryLocator.useBeanFactory(“BeanId”);

(org.springframework.context.ApplicationContext)beanFactoryReference.getFactory();

Esto puesto en algún singleto y en un método synchronized, es posible que no de muchos problemas, lo único es que en cada servicio habría que llamarlo explícitamente para usar los beans de spring, no siendo muy automatico.

Usando el ContextLoaderListener

Con unos pequeños cambios podemos hacer que sea la propia aplicación web quien carge el contexto al iniciar la aplicación.

Para ello, solo hay que poner el nuestro web.xml el siguiente código:

<context-param>

	<param-name>contextConfigLocation</param-name>

		<param-value>

        /WEB-INF/applicationContext.xml

		classpath:applicationContext.xml

	</param-value>

</context-param>

Teneis las dos formas de cargar el archivo, si esta en el contexto de la aplicación, directamente por su path. O si lo tenemos en algún paquete de nuestro classpath. El ya lo busca y lo carga.

Y después:

<listener>

	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

Que ya se encarga de cargar el archivo de configuración.Despues podemos solicitar el Bean de spring utilizando la siguiente instrucción.

ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(getServletContext());

(MyService)context.getBean(“myservice”);

No hace falta decir que esto ha de hacerse desde un servlet en el servidor, para poder tener acceso al contexto de la aplicación.

Para no tener que llamarlo constantemente en cada servlet, podemos extender RemoteServiceServlet de GWT y montarlo en llamadas que nos den los servicios.

public class SpringRemoteServiceServlet extends RemoteServiceServlet{

	private ApplicationContext getContext(){

		ServletContext sc = getServletContext();

		return WebApplicationContextUtils.getWebApplicationContext(sc);

	}

	protected MyService getMyService(){

		return (MyService)getContext().getBean("myService");

	}

}

Así solo tenemos que llamar en nuestro servlet al metodo del servicio correspondiente. Aun asi, no deja de ser un poco intrusivo, y el hecho de tener una llamada en medio de nuestro servlet del tipo getServicio(), marea un poco.

Inyección de dependencias

Este es el que más me gusta, cumple con la filosofía de inyección de dependencias de spring, y lo hace todo muy transparente. Este lo he encontrada de aquí. Esta en ingles, pero se entiende muy bien. Pero hare un resumen:

Al igual que hacíamos en el anterior punto, extendemos RemoteServiceServlet de GWT:

import java.lang.reflect.Field;

import java.util.HashSet;

import java.util.Set;

import org.apache.log4j.Logger;

import org.springframework.beans.factory.NoSuchBeanDefinitionException;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.context.support.WebApplicationContextUtils;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**

 * {@link RemoteServiceServlet} that automatically injects IoC dependency.

 * "org.springframework.beans.factory.annotation.Autowired" annotation is used

 * for marking which fields to inject into. Uses

 * {@link SpringApplicationContextLoader} to retrieve beans by name.

 *

 * Note that the current implementation will only inject "declared" fields, and

 * not inherited fields. Fields can be private, protected, package or public.

 * 

 * @author See Wah Cheng

 * @created 27 Jun 2008

 */

@SuppressWarnings("serial")

public class DependencyInjectionRemoteServiceServlet extends

		RemoteServiceServlet {

	protected static Logger logger = Logger

			.getLogger(DependencyInjectionRemoteServiceServlet.class);

	@Override

	public void init() throws ServletException {

		super.init();

		doDependencyInjection();

	}

	/**

	 * Carries out dependency injection. This implementation uses Spring IoC

	 * container.

	 * 

	 * @exception NoSuchBeanDefinitionException

	 *                if a suitable bean cannot be found in the Spring

	 *                application context. The current implementation looks up

	 *                beans by name

	 */

	protected void doDependencyInjection() {

		for (Field field : getFieldsToDependencyInject()) {

			try {

				boolean isFieldAccessible = field.isAccessible();

				if (!isFieldAccessible) {

					field.setAccessible(true);

				}

				field.set(this, WebApplicationContextUtils

						.getWebApplicationContext(getServletContext()).getBean(

								field.getName()));

				if (!isFieldAccessible) {

					field.setAccessible(false);

				}

				logger.debug("Dependency injection successful: "

						+ this.getClass().getName() + "." + field.getName());

			} catch (IllegalArgumentException e) {

				throw new RuntimeException(e);

			} catch (IllegalAccessException e) {

				throw new RuntimeException(e);

			}

		}

	}

	/**

	 * Find annotated fields to inject.

	 * 

	 * @return a list of all the annotated fields

	 */

	private Set getFieldsToDependencyInject() {

		Set fieldsToInject = new HashSet();

		Field[] fields = this.getClass().getDeclaredFields();

		for (Field field : fields) {

			if (field.getAnnotation(Autowired.class) != null) {

				fieldsToInject.add(field);

			}

		}

		return fieldsToInject;

	}

}

Esta es la clase que deberemos heredar en todos nuestros servicios. Y en el servicio en concreto debemos marcas la dependencia, en este ejemplo utilizan una anotación de spring, @Autowired. Si nos fijamos, cuando solicita los campos de la clase, utiliza reflection para buscar esa marca, y después busca el servicio en el contexto de spring que tiene en el contexto del servlet y lo asigna. Muy limpio y fácil de utilizar.

  • GR360

    hola estoy tratando de configurar spring en gwt cuando lo cargo en el navegador me dice que no puede crear un bean ya copie todas las librerias de spring al web-inf l/ib y se quito el error que no cargaba el contexto,con jsf funciona de las mil patadas pero con gwt me da mucho problemas incluso cuando carga el servidor jetty da el error y sigo buscando como hacer el modo desarrollo en tomcat y no en jetty seria mucha ayuda si has pasado por esto

  • alfonsomarin

    Hola, te recomiendo que veas esta entrada http://www.alfonsomarin.es/gwt-spring-maven/ donde explico como integrar de una forma mas eficaz gwt y spring. 
    Es muy raro lo que te pasa, pero si no me das mas detalles del error que saca no puedo ayudarte.
    Si has seguido los pasos del tutorial debe ser un problema de configuración. Piensa que si ejecutar en jetty desde eclipse hay que tomar ciertas medidas, en ocasiones no coge las librerías si no estan en el lib o incluso si no tienes la estructura básica del plugin de google.
    Te recomiendo que actualices todo a la ultima versión.
    Un saludo

  • pablotandil

    Hola, está muy buena la idea de extender el RemoteServiceServlet para inyectar las dependencias de las propiedades del Servlet. Pero tengo una duda, no puede generar problemas de concurrencia el tener variables en el servlet, lo que quiero decir si los métodos del servicio acceden a estas variables en diferentes Threads no podría ocurrir que uno quiera leer un valor que otro lo ha modificado, bueno problemas de sincronización de cosas compartidas.

    Está duda la tengo desde que uso GWT (hace un poco más de un año) y cuando leí un poco de los servlets y sus multiples ejecuciones simultaneas con los diferentes hilos. Decidí por lo tanto no usar variables de servlet, y los beans que cargo con Spring en los métodos de servicios NO son singletons.

    Por favor decime si le estoy errando o vos lo comprendes de otra forma.Gracias, Pablo
    Saludos

  • alfonsomarin

    Hola, esta opción es un poco antigua. Quizás la aproximación de spring MVC sea más práctica. De todas maneras no se usa ninguna variable de la clase, el servlet que atiende la petición llama al contexto de Spring para asignar los beans, no hay peligro de concurrencia.
    Esta aproximación la estoy aplicando en una aplicación que lleva dos años en funcionamiento y sin problemas.
    Un saludo

  • pablotandil

    Gracias por tomarte el tiempo de contestar!.

    Para hacer la implementación de un servicio debería heredar de DependencyInjectionRemoteServiceServlet.

    A las propiedades de ese servicio les pondría el  (arroba)Autowire. Entonces en un método de servicio utilizaría estas propiedades que fueron instanciadas con beans desde archivos de contexto por Spring. 

    Acá es donde tengo dudas, no hay diferentes hilos atendiendo peticiones de usuarios que podrían querer usar los métodos del servicio y por lo tanto competirían por usar la propiedad?, va más allá del uso de los archivos de contexto sino más bien al uso de propiedades o variables del servlet.

    Es una duda que me ha quedado rebotando, me cuesta todavía pensar que cosas tengo que ver si tienen problemas de sincronización. Si por una de esas tenes algún link o doc para chusmear sobre esto será bien recibido.

    Gracias Alfonso!!

  • alfonsomarin

    Al final son todo servlets y cada servlet es un hilo por petición de usuario, con lo que no hay problema.

  • pablotandil

    Gracias Alfonso, no sabía que era así.