February 14th, 2008

Non invasive GWT and Spring integration

[update]
If you are working with GWT 1.6, you probably would like to have a look at this here:
http://pgt.de/2009/07/17/non-invasive-gwt-and-spring-integration-reloaded/
[update]

Obviously I am not the only one looking for a way to integrate my Spring backend into some GWiT application. After searching for a while I didn’t find any suiting solution. There are some interesting approaches (like GWT Widget Library SL and using the maven plugin), but being new to GWiT I did not want to give up the GWiT Development Shell neither the embeded Tomcat. I wanted the integration to be less invasive as possible.

So here is what I did…

First problem I had to solve was how to get my configuration elements into the tomcat configuration. I needed to add some listeners to the web.xml (e.g. to start the Spring container and Acegi security):

Here is a snippet from my web.xml showing how to use the listeners provided by the Spring Framework to start an application context and to configure log4j properly:

(...)
...)
 
log4jConfigLocation
/WEB-INF/log4j.xml
 
contextConfigLocation
classpath:/META-INF/spring-conf.xml
 
			org.springframework.web.util.Log4jConfigListener
 
			org.springframework.web.context.ContextLoaderListener
 
			org.springframework.web.context.request.RequestContextListener
 
(...)

GWiT does not provide any extension point for custom configuration, so I had to add the configuration elements directly to the provided Tomcat web.xml. I cannot make changes to the web.xml in the ROOT web application, since this web.xml is re-generated by GWiT every time the Development Shell is started. Fortunately, the default web.xml stored in the “conf” directory of the embedded Tomcat is generated only one. So I found some place to add my configuration elements. Unfortunately the configuration there is not reusable, so I am having double configuration here: configuration for the development and configuration for the deployment.

Next, I wanted to easily have access to my Spring beans. JSF developers have a variable resolver and have access to their Spring beans for free. I wanted that too. Again, being new to GWiT, I didn’t want to loose the features provided by the IDE (Eclipse + GWT-Designer in this case). It is nice to simply say “add new remote service” in the IDE and get everything wired out of the box. For different reasons I do want to expose my Spring beans automatically to the GWiT application. JSF developers have the services for free at the SERVER SIDE: the services do not get exposed via RPC automatically. I wanted something similar: my GWiT remote service implementation should have access to the Spring backend for free without exposing anything automatically.

I wanted some sort of dependency injection. Since the servlets are managed by the servlet container and the servlet container does not know anything about dependeny injection of Spring beans into servlets I had to do it myself.

First, I needed some sort of markup to identify what to inject. I was using JDK 5 syntax on none client sources, so I used an annotation. I could also have used some marker interface for my services, but I found this to be to invasive, I did not touch my backend files.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
/**
 * Annotation used to flag auto injection.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD)
public @interface AutoInject {
 
}

All I wanted to do is to add this annotation to my setter methods in my GWiT remote service implementations. So I extended the RemoteServiceServlet provided by GWiT and implemented the auto injection.

import java.lang.reflect.Method;
 
import javax.servlet.ServletException;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
 
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
 
/**
 * Provides automatic injection into GWT services. Only setter methods with exactly one parameter and
 * annotated with AutoInject will be considered for auto injection.
 *
 */
public abstract class AutoInjectingRemoteServiceServlet extends RemoteServiceServlet {
 
    /**
     * Logger.
     */
    private static final Logger LOG = LoggerFactory.getLogger(AutoInjectingRemoteServiceServlet.class);
 
    /** Reference for the WebApplicationContext. */
    private WebApplicationContext ctx;
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void init() throws ServletException {
        LOG.debug("Initializing servlet");
        super.init();
        LOG.debug("Get reference to the Spring Application Context");
        ctx = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        System.out.println(ctx);
        LOG.debug("Spring ctx with: {} beans.", ctx.getBeanDefinitionCount());
        this.populateVariables();
    }
 
    /**
     * Performs auto injection on this servlet instance.
     */
    @SuppressWarnings("unchecked")
    private void populateVariables() {
        LOG.debug("Start servlet auto injection");
        Method[] methods = this.getClass().getMethods();
        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
 
            if (method.getName().startsWith("set") && method.isAnnotationPresent(AutoInject.class)
                    && method.getParameterTypes().length == 1) {
                Class type = method.getParameterTypes()[0];
                LOG.debug("Looking for: {}", type);
                String[] beanNamesForType = ctx.getBeanNamesForType(type, false, true);
                LOG.debug("Got {} with {} elements", beanNamesForType, beanNamesForType.length);
                if (beanNamesForType.length != 1) {
                    LOG.error("There is no bean of type {} for autoinjection", type.toString());
                } else {
                    try {
                        method.invoke(this, ctx.getBean(beanNamesForType[0]));
                        LOG.info("Injected bean: {} on field {} ", beanNamesForType[0], this.getClass().getName());
                    } catch (Throwable e) {
                        LOG.error("Error: could not inject into " + "protected variable {}, cause: {}", method.getName(), e
                                .getMessage());
                        if (LOG.isDebugEnabled()) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

In short: in the initialization of the servlet I pickup the Spring application context and do the injection for all setter methods annotated.

In the following GWiT remote service implementation I use this injection to get access to my Spring login service (that itself uses acegi, the user DAO and a session scoped bean):

public class GwtLoginServiceImpl extends AutoInjectingRemoteServiceServlet implements GwtLoginService {
 
	private ILoginService loginService;
 
	@AutoInject
	public void setLogService(ILoginService loginService) {
		this.loginService = loginService;
	}
 
	@Override
	public LoginResult doLogin(String username, String password) {
		LoginResult result = new LoginResult();
		try {
			result.loggedInUser = loginService.doLogin(username, password);
			result.successFull = true;
		} catch (BadCredentialsException bce) {
			result.successFull = false;
			result.errorMessage = "Bad credentials.";
		} catch (Throwable t) {
			result.successFull = false;
			result.errorMessage = "Server error.";
		}
		return result;
	}
 
	@Override
	public UserDTO isLoggedIn() {
		return loginService.isLoggedIn();
	}
 
	@Override
	public void doLogout() {
		loginService.doLogout();
	}
}

While this leads to a lot of delegation code, I am still happy with the layer separation and the easy usage.

When playing with GWiT 1.5 (build from the trunk) a few weeks ago, I noticed that GWiT started overriding the web.xml in the embeded Tomcat configuration directory. I just thought “oh no!”, but next thing I noticed was that it stopped overriding the application web.xml in the ROOT webapps folder. Let’s see if it stays this way…

5 comments to Non invasive GWT and Spring integration

Leave a Reply