Wednesday, August 13, 2014

Spring GWT Integration

GWT version - 2.1.0M3
Spring version - 3.0.1
GWT fits very well to handle the front end of application, whereas Spring is quite a mature way of handling service and data access layer. There are many ways to handle Spring-GWT integration ans there are many articles on the same in the web. I have tried in this to adopt the way as mentioned in Spring4Gwt(external link)(cache) project and adapted it a little bit, but the basic philosophy remains same.

At conceptual level, what we want to do is that in the callbacks of GWT, we need to hook the service beans managed by Spring ans we want to keep this integration transparent. Let's do the code part and understand how the pieces are getting fit together. To start with let's write a class which will give us a handle to Spring Application Context. We will not be using any Spring MVC infrastructure but rather connect directly to the global application context. Make sure in your spring application context you have registered it to read the components using the annotation way. I am assuming my base package is lalit

<context:component-scan base-package="com.lalit">

Now let's write a class which implements ApplicationContextAware interface so that Spring when getting booted sets the handle to ApplicationContext.

/**
 * SpringApplicationContext holds the reference to Application context. As we are not
 * using the Spring MVC, still we need access to spring non MVC backend 
 * infrastructure.
 * SpringApplicationContext class will help us on this.
 * @author lalit
 *
 */
@Component
public class SpringApplicationContext implements ApplicationContextAware{

/**
* Application context 
*/
private static ApplicationContext context;
    /**
     * Inject the application context through ApplicationContextAware interface.
     * Note that ApplicatonContext is  static as we want to use it in the
     * getBean method which is static.
     * @param context
     */
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}

/**
* Get the bean from the Spring application context.
* @param beanName
* @return
*/
public static Object getBean(String beanName) {
if (context == null) {
throw new IllegalStateException(
"No Spring web application context found");
}
if (!context.containsBean(beanName)) {
       {
throw new IllegalArgumentException("Spring bean not found: "
+ beanName);
}
}
return context.getBean(beanName);
}
}

Now let's write a servlet which will be handling the call coming from GWT front end but passing the call back to Spring managed beans. In fact, this servlet is the hook between two frameworks. The servlet looks like

public class SpringGwtRemoteServiceServlet extends RemoteServiceServlet {
   private static final Logger log = 
              Logger.getLogger(SpringGwtRemoteServiceServlet.class);

  /**
   * This will process the call which gets invoked via RemoteServiceServlet.
  * RemoteServiceServlet is the workhorse for GWT to process HTTP requests.
  */
   @Override
   public String processCall(String payload) throws SerializationException {
try {
Object handler = getBean(getThreadLocalRequest());
RPCRequest rpcRequest =
                    RPC.decodeRequest(payload, handler.getClass(), this);
onAfterRequestDeserialized(rpcRequest);
  if (log.isDebugEnabled()) {
log.debug("Invoking " + handler.getClass().getName() + "." + 
                                    rpcRequest.getMethod().getName());
      }
return RPC.invokeAndEncodeResponse(handler, rpcRequest.getMethod(), 
                            rpcRequest.getParameters(), rpcRequest
.getSerializationPolicy());
} catch (IncompatibleRemoteServiceException ex) {
     log("An IncompatibleRemoteServiceException was " + "
                             thrown while processing this call.", 
                      ex);
return RPC.encodeResponseForFailure(null, ex);
}
}

   /**
      * Determine Spring bean to handle request based on request URL, e.g. a
      * request ending in /myService will be handled by bean with name
      * "myService".
      * 
      * @param request
      * @return handler bean
    */
    protected Object getBean(HttpServletRequest request) {
String service = getService(request);
Object bean = SpringApplicationContext.getBean(service);
if (!(bean instanceof RemoteService)) {
throw new IllegalArgumentException
                    ("Spring bean is not a GWT RemoteService: " 
                + service +   " (" + bean + ")");
}
if (log.isDebugEnabled()) {
log.debug("Bean for service " + service + " is " + bean);
}
return bean;
}

/**
* Parse the service name from the request URL.
* @param request
* @return bean name
*/
protected String getService(HttpServletRequest request) {
String url = request.getRequestURI();
String service = url.substring(url.lastIndexOf("/") + 1);
if (log.isDebugEnabled()) {
log.debug("Service for URL " + url + " is " + service);
}
return service;
}
}

Now let's register this servlet in web.xml

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         version="2.4"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
                             http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    
    <display-name>gwtApp</display-name>
    
    <description>GWT Application</description>

    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:META-INF/spring/applicationContext*.xml</param-value>
    </context-param>
   
    <!-- Creates the Spring Container -->
    <listener>
        <listener-class>
             org.springframework.web.context.ContextLoaderListener
       </listener-class>
    </listener>   
    
    
    <!-- SpringGwt remote service servlet -->
     <servlet>
        <servlet-name>springGwtRemoteServiceServlet</servlet-name>
        <servlet-class>com.lalit.server.SpringGwtRemoteServiceServlet</servlet-class>
     </servlet>

    <!-- gwtApp is the GWT application name as mentioned in gwt.xml of the module -->
     <servlet-mapping>
        <servlet-name>springGwtRemoteServiceServlet</servlet-name>
        <url-pattern>/gwtApp/services/*</url-pattern>
    </servlet-mapping> 
    
</web-app>
Now let's write the spring service bean.

@Service("personService")
public class PersonServiceImpl implements PersonService {

public void savePerson(PersonRecord record) {
//do your service logic here
}

}

Note the Spring bean name is personService. And it implements PersonService which is a GWT interface and looks as follows

@RemoteServiceRelativePath("services/personService")
public interface PersonService extends RemoteService {

     public void savePerson(PersonRecord record);
}

Note that the services come from web.xml mapping and personService is the Spring bean name. To complete the Asyn interface looks like

public interface PersonServiceAsync {

void savePerson(PersonRecord record,
AsyncCallback<Void> callback);
}

Nothing special here. Now when we do the callback from GWT callback handlers, the SpringGwtRemoteServiceServlet ensures that the call goes to the Spring beans. Once this infrastructure is in place, the back end can be developed in a complete spring way.

No comments:

Post a Comment