Trifork Blog

Axon Framework, DDD, Microservices

Integrating Spring and GWT

June 4th, 2006 by
|

I just completed my first shot at integrating Spring with GWT. You can check it out here

Here’s what you do to expose a simple service.

1) Write the remote service, note that the service no longer extends RemoteServiceServlet however HelloService should still implement RemoteService


public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
return "hello " + name;
}
}

2) Define this service in your application context

<bean id=”helloService” class=”nl.jteam.hello.server.HelloServiceImpl”/>

3) expose the bean as a gwt service

<bean id=”defaultHandlerMapping” class=”org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping”/>

<bean name=”/helloService” class=”nl.jteam.gwt.exporter.GwtInvokerServiceExporter”>
<property name=”service” ref=”helloService”/>
<property name=”serviceInterface” value=”nl.jteam.hello.client.HelloService”/>
</bean>

4) Change the clientcode to point to your Spring service. be sure to use an absolute url while running in hosted mode or it will complain


helloService = (HelloServiceAsync) GWT.create(HelloService.class);
ServiceDefTarget endpoint = (ServiceDefTarget) helloService;
endpoint.setServiceEntryPoint("http://localhost:8080/helloservice");

Feedback is appreciated

14 Responses

  1. June 8, 2006 at 00:35 by Fred Beltrão

    Hi,

    Your class nl.jteam.gwt.exporter.GwtInvokerServiceExporter implements org.springframework.web.servlet.mvc.RequestHandler. However this interface is missing at http://static.springframework.org/spring/docs/1.2.x/api/index.html
    What spring version are you using?

    Best Regards,

    Fred

  2. June 8, 2006 at 04:24 by Jim White

    The link to the download is to (numeric ip) server that doesn’t respond.

  3. June 8, 2006 at 08:47 by site admin

    fred, it uses spring 2.0, maybe i’ll put up an 1.2.x version later

    jim, yes somehow it got cut of the net yesterday night it should be back up

  4. June 8, 2006 at 13:05 by Fred Beltrão

    Thanks for your answer.

    How can I pass to my helloService the client IP address (request.getRemoteAddr()) ? Do you have any idea on how to perform this?

    Thanks in advance!

    Fred

  5. June 8, 2006 at 14:41 by site admin

    I would probably define a handlerinterceptor that binds the ip address to a threadlocal like LocaleContextHolder does for Locales

    then you can reference that from your servlet

  6. July 10, 2006 at 00:36 by Rodrigo López-Guzmán

    Your aproach is great, congratulations. I’m a Spring newbie as well as a GWT newbie, and I’m trying to build a new application using these two technologies.

    The problem I’m having is that my server shows the following message:
    [http-8000-Processor24] WARN org.springframework.web.servlet.PageNotFound – No mapping for [/MyWebapp/path.to.MyApp/service/myService] in DispatcherServlet with name ‘action’

    In web.xml I have:

    action
    org.springframework.web.servlet.DispatcherServlet
    1

    action
    /path.to.MyApp/service/*

    My action-servlet.xml has:

    And my context looks like this:

    I don’t know why this problem arises.

    Any sugestions or ideas?

  7. July 10, 2006 at 09:33 by thanghv

    How to write some thing to web.xml? I do as you write but you do not mention anything about use spring to configure servlet life cycle 😀

  8. July 10, 2006 at 15:12 by Rodrigo López-Guzmán

    My previous post didn’t show xml code. What I had in those files was everything as mentioned, but the fact was that it was not working.

    The only trick I had to do was including the RequestHandler interface by hand, since my spring.jar (2.0RC2) didn’t include it.

    That was the problem!

    Recent builds of Spring 2.0 removed RequestHandler and the new interface is org.springframework.web.HttpRequestHandler

    Spring’s DispatcherServlet processes HttpRequestHandler instances in the way that we want.

    I made that chance and it worked great with 2.0RC2

    Regards,

    Rodrigo.

  9. July 26, 2006 at 04:59 by Jeff

    Hi,
    Thanks for putting this out there.

    Could you explain the use of ServerSerializableTypeOracle class a bit more?

    Also, I keep getting this error:

    javax.servlet.ServletException: Content-Length must be specified
    at com.aavu.server.helper.GwtInvokerServiceExporter.readPayloadAsUtf8(GwtInvokerServiceExporter.java:180)

    Any suggestions what this might mean?

    Thanks.

  10. August 18, 2006 at 00:21 by Rodrigo López Guzmán

    Hello,

    Have you planned to implement a new version that works with GWT 1.1?

    Previous version stopped working since new GWT’s version was released.

    Thanks,

    Rodrigo.

  11. August 30, 2006 at 11:14 by Dominic

    I’m also needing an updated exporter for GWT 1.1.10
    Any plans for an update?

    Thanks in advance,
    Dominic

  12. August 30, 2006 at 11:19 by site admin

    I’ll see what i can do. If I can I’ll probably turn it into a java.net project

  13. September 2, 2006 at 21:01 by Rodrigo López Guzmán

    I made one myself. Class body is:

    private Object proxy;
    /**
    * Our logger will not be servlet context’s log (we don’t have
    * direct access to it at this point)
    * @author rlogman@gmail.com
    */
    protected final Log logger = LogFactory.getLog(getClass());

    /**
    * Implementation of handleRequest method of Spring’s
    * org.springframework.web.HttpRequestHandler, which is
    * handled by org.springframework.web.servlet.DispatcherServlet
    *
    * This method is almost an exact copy of RemoteServiceServlet’s doPost
    * @author rlogman@gmail.com
    */
    public void handleRequest(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    /**
    * @author rlogman@gmail.com
    */
    Assert.notNull(this.proxy,
    “GwtInvokerServiceExporter has not been initialized”);
    Throwable caught;
    try {
    // Store the request object in thread-local storage.
    //
    perThreadRequest.set(request);

    // Read the request fully.
    //
    String requestPayload = readPayloadAsUtf8(request);

    // Invoke the core dispatching logic, which returns the serialized
    // result.
    //
    String responsePayload = processCall(requestPayload);

    // Write the response.
    //
    writeResponse(request, response, responsePayload);

    return;

    } catch (IOException e) {
    caught = e;
    } catch (ServletException e) {
    caught = e;
    } catch (SerializationException e) {
    caught = e;
    } catch (Throwable e) {
    caught = e;
    }

    respondWithFailure(response, caught);
    }

    /**
    * Implementation of afterPropertiesSet method of Spring’s
    * InitializingBean. This is the link between the Spring’s exporter
    * and the actual service implementation.
    * @author rlogman@gmail.com
    */
    public void afterPropertiesSet()
    throws Exception {
    /**
    * This is a reference for our actual class
    * @author rlogman@gmail.com
    */
    this.proxy = getProxyForService();
    }

    /*
    * These members are used to get and set the different HttpServletResponse
    * and HttpServletRequest headers.
    */
    private static final String ACCEPT_ENCODING = “Accept-Encoding”;

    private static final String CHARSET_UTF8 = “UTF-8”;

    private static final String CONTENT_ENCODING = “Content-Encoding”;

    private static final String CONTENT_ENCODING_GZIP = “gzip”;

    private static final String CONTENT_TYPE_TEXT_PLAIN_UTF8 = “text/plain; charset=utf-8”;

    private static final String GENERIC_FAILURE_MSG = “The call failed on the server; see server log for details”;

    private static final HashMap TYPE_NAMES;

    /**
    * Controls the compression threshold at and below which no compression will
    * take place.
    */
    private static final int UNCOMPRESSED_BYTE_SIZE_LIMIT = 256;

    /**
    * Return true if the response object accepts Gzip encoding. This is done by
    * checking that the accept-encoding header specifies gzip as a supported
    * encoding.
    */
    private static boolean acceptsGzipEncoding(HttpServletRequest request) {
    assert (request != null);

    String acceptEncoding = request.getHeader(ACCEPT_ENCODING);
    if (null == acceptEncoding) {
    return false;
    }

    return (acceptEncoding.indexOf(CONTENT_ENCODING_GZIP) != -1);
    }

    /**
    * This method attempts to estimate the number of bytes that a string will
    * consume when it is sent out as part of an HttpServletResponse.
    *
    * This really a hack since we are assuming that every character will
    * consume two bytes upon transmission. This is definitely not true since
    * some characters actually consume more than two bytes and some consume
    * less. This is even less accurate if the string is converted to UTF8.
    * However, it does save us from converting every string that we plan on
    * sending back to UTF8 just to determine that we should not compress it.
    */
    private static int estimateByteSize(final String buffer) {
    return (buffer.length() * 2);
    }

    /**
    * Find the invoked method on either the specified interface or any super.
    */
    private static Method findInterfaceMethod(Class intf, String methodName,
    Class[] paramTypes, boolean includeInherited) {
    try {
    return intf.getDeclaredMethod(methodName, paramTypes);
    } catch (NoSuchMethodException e) {
    if (includeInherited) {
    Class[] superintfs = intf.getInterfaces();
    for (int i = 0; i this but from this.proxy;
    * this is the exporter, this.proxy is the actual service
    * implementation
    * @author rlogman@gmail.com
    */
    Object returnVal = serviceIntfMethod.invoke(this.proxy, args);
    responsePayload = createResponse(streamWriter, returnType,
    returnVal, false);
    } catch (IllegalArgumentException e) {
    caught = e;
    } catch (IllegalAccessException e) {
    caught = e;
    } catch (InvocationTargetException e) {
    // Try to serialize the caught exception if the client is expecting
    // it,
    // otherwise log the exception server-side.
    caught = e;
    Throwable cause = e.getCause();
    if (cause != null) {
    // Update the caught exception to the underlying cause
    caught = cause;
    // Serialize the exception back to the client if it’s a declared
    // exception
    if (isExpectedException(serviceIntfMethod, cause)) {
    Class thrownClass = cause.getClass();
    responsePayload = createResponse(streamWriter, thrownClass,
    cause, true);
    // Don’t log the exception on the server
    caught = null;
    }
    }
    }

    if (caught != null) {
    responsePayload = GENERIC_FAILURE_MSG;
    // servletContext may be null (for example, when unit testing)
    /**
    * Our logger will not be servlet context’s log (we don’t have
    * direct access to it at this point)
    * @author rlogman@gmail.com
    */
    if (logger != null) {
    // Log the exception server side
    logger.error(“Exception while dispatching incoming RPC call”,
    caught);
    }
    }

    // Let subclasses see the serialized response.
    //
    onAfterResponseSerialized(responsePayload);

    return responsePayload;
    }

    /**
    * Gets the HttpServletRequest object for the current call.
    * It is stored thread-locally so that simultaneous invocations can have
    * different request objects.
    */
    protected final HttpServletRequest getThreadLocalRequest() {
    return (HttpServletRequest) perThreadRequest.get();
    }

    /**
    * Gets the HttpServletResponse object for the current call.
    * It is stored thread-locally so that simultaneous invocations can have
    * different response objects.
    */
    protected final HttpServletResponse getThreadLocalResponse() {
    return (HttpServletResponse) perThreadResponse.get();
    }

    /**
    * Override this method to examine the serialized response that will be
    * returned to the client. The default implementation does nothing and need
    * not be called by subclasses.
    */
    protected void onAfterResponseSerialized(String serializedResponse) {
    }

    /**
    * Override this method to examine the serialized version of the request
    * payload before it is deserialized into objects. The default
    * implementation does nothing and need not be called by subclasses.
    */
    protected void onBeforeRequestDeserialized(String serializedRequest) {
    }

    /**
    * Determines whether the response to a given servlet request should or
    * should not be GZIP compressed. This method is only called in cases where
    * the requestor accepts GZIP encoding.
    *
    *
    * This implementation currently returns true if the response
    * string’s estimated byte length is longer than 256 bytes. Subclasses can
    * override this logic.
    *
    *
    * @param request
    * the request being served
    * @param response
    * the response that will be written into
    * @param responsePayload
    * the payload that is about to be sent to the client
    * @return true if responsePayload should be GZIP compressed,
    * otherwise false.
    */
    protected boolean shouldCompressResponse(HttpServletRequest request,
    HttpServletResponse response, String responsePayload) {
    return estimateByteSize(responsePayload) > UNCOMPRESSED_BYTE_SIZE_LIMIT;
    }

    /**
    * @param stream
    * @param responseType
    * @param responseObj
    * @param isException
    * @return
    */
    private String createResponse(ServerSerializationStreamWriter stream,
    Class responseType, Object responseObj, boolean isException) {
    stream.prepareToWrite();
    if (responseType != void.class) {
    try {
    stream.serializeValue(responseObj, responseType);
    } catch (SerializationException e) {
    responseObj = e;
    isException = true;
    }
    }

    String bufferStr = (isException ? “{EX}” : “{OK}”) + stream.toString();
    return bufferStr;
    }

    private Class getClassFromName(String name) throws ClassNotFoundException {
    Object value = TYPE_NAMES.get(name);
    if (value != null) {
    return (Class) value;
    }

    return Class.forName(name, false, this.getClass().getClassLoader());
    }

    /**
    * Obtain the special package-prefixes we use to check for custom
    * serializers that would like to live in a package that they cannot. For
    * example, “java.util.ArrayList” is in a sealed package, so instead we use
    * this prefix to check for a custom serializer in
    * “com.google.gwt.user.client.rpc.core.java.util.ArrayList”. Right now,
    * it’s hard-coded because we don’t have a pressing need for this mechanism
    * to be extensible, but it is imaginable, which is why it’s implemented
    * this way.
    */
    private String[] getPackagePaths() {
    return new String[] { “com.google.gwt.user.client.rpc.core” };
    }

    /**
    * Returns true if the {@link java.lang.reflect.Method Method} definition on
    * the service is specified to throw the exception contained in the
    * InvocationTargetException or false otherwise.
    *
    * NOTE we do not check that the type is serializable here. We assume that
    * it must be otherwise the application would never have been allowed to
    * run.
    *
    * @param serviceIntfMethod
    * @param e
    * @return
    */
    private boolean isExpectedException(Method serviceIntfMethod,
    Throwable cause) {
    assert (serviceIntfMethod != null);
    assert (cause != null);

    Class[] exceptionsThrown = serviceIntfMethod.getExceptionTypes();
    if (exceptionsThrown.length this but from this.proxy;
    * this is the exporter, this.proxy is the actual service
    * implementation
    * @author rlogman@gmail.com
    */
    Class[] intfs = this.proxy.getClass().getInterfaces();
    for (int i = 0; i