I want to publish simple solution to expose Spring Bean as Google Web Toolkit Remote Service.
There are some solutions around but they are hard to configure, heavy-weight and often do not provide functionality required to use AOP-based annotations on service methods (like Spring Security @Secured) but I prefer minimal XML configuration (In this example there are only few lines of XML), annotation-driven configuration and functionality without any limitations.
So it is needed to create
a) Spring-managed servlet that will handle RPC calls
b) Remote Service common logic implementation
c) Simple GWT RPC Example
Spring servlet to handle RPC calls
package com.example.server.service; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import org.springframework.web.HttpRequestHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; @Component("gwt-request-handler") public class RemoteServiceHandler implements HttpRequestHandler, ApplicationContextAware { private ApplicationContext applicationContext; public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException { String requestPath = httpServletRequest.getServletPath(); @SuppressWarnings({"unchecked"}) Map<String, RemoteService> services = applicationContext.getBeansOfType(RemoteService.class); RemoteService service = null; for (String key : services.keySet()) { if (requestPath.endsWith(key)) { if (service != null) { throw new IllegalStateException("More than one GWT RPC service bean matches request: " + requestPath); } else { service = services.get(key); } } } if (service == null) { String availableServlets = ""; for (String key : services.keySet()) { availableServlets += key + " -> " + services.get(key).getClass().getName() + "\n"; } throw new IllegalStateException("Cannot find GWT RPC service bean for request: " + requestPath + "\nMake sure that service implementation extends RemoteServiceImpl instead of GWT RemoteServiceServlet\nList of available beans:\n" + availableServlets); } service.doPost(service, httpServletRequest, httpServletResponse); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
it should be configured as servlet in web.xml file:
<servlet> <servlet-name>gwt-request-handler</servlet-name> <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>gwt-request-handler</servlet-name> <url-pattern>*.gwtrpc</url-pattern> </servlet-mapping>
Spring HttpRequestHandlerServlet will try to locate bean with name same as servlet name and use it to handle all requests matching specified url-pattern.
Remote Service common implementation
This class will take care of ServletContext because these beans are not actually servlets but GWT requires ServletContext to work.
Also it invokes remote methods in a way that allows AOP annotations to work.
Interface is needed to correct work with AOP based annotations, like @Transactional or @Secured
package com.example.server.service; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public interface RemoteService { void doPost(RemoteService service, HttpServletRequest request, HttpServletResponse response); }
package com.example.server.service; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public abstract class RemoteServiceImpl extends RemoteServiceServlet implements RemoteService { private RemoteService service = null; public void doPost(RemoteService service, HttpServletRequest request, HttpServletResponse response) { this.service = service; doPost(request, response); } @Override public ServletContext getServletContext() { ServletContext context; try { context = super.getServletContext(); } catch (Exception e) { context = getThreadLocalRequest().getSession().getServletContext(); } return context; } @Override public String processCall(final String payload) throws SerializationException { final RPCRequest request = RPC.decodeRequest(payload, this.getClass(), this); try { Object result = request.getMethod().invoke(service, request.getParameters()); return RPC.encodeResponseForSuccess(request.getMethod(), result, request.getSerializationPolicy()); } catch (IllegalAccessException e) { return RPC.encodeResponseForFailure(request.getMethod(), e, request.getSerializationPolicy()); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); return RPC.encodeResponseForFailure(request.getMethod(), cause, request.getSerializationPolicy()); } catch (IncompatibleRemoteServiceException ex) { return RPC.encodeResponseForFailure(null, ex, request.getSerializationPolicy()); } finally { service = null; } } }
Simple Remote Service
Here is implementation of very simple remote service managed by Spring.You can use any AOP annotation and/or other spring features like autowiring without any limitation.
package com.example.client.service; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; import com.google.gwt.core.client.GWT; @RemoteServiceRelativePath(TestService.URL) public interface TestService extends RemoteService { public static final String URL = "services/TestService.gwtrpc"; public String hello(String userName); public static class Instance { private static final TestServiceAsync instance = (TestServiceAsync) GWT.create(TestService.class); public static TestServiceAsync get() { return instance; } } }
package com.example.server.service; import com.example.client.service.TestService; import org.springframework.stereotype.Component; @Component(TestService.URL) public class TestServiceImpl extends RemoteServiceImpl implements TestService { public String hello(String userName) { return "Hello " + userName; } }
Spring context configuration
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <context:component-scan base-package="com.example.server.service"/> </beans>