Saturday, September 5, 2009

Working with GWT / Spring / Dispatch

The Hive Development blog has a nice tutorial on setting up a Google Web Toolkit app with the Presenter and Dispatcher patterns. This is all based on a nice Google presentation explaining some useful patterns for developing robust app with GWT.

I recommend watching the video first and then reading the hivedevelopment blog article. Read through it and then download the full source code using the link near the top of the page. In the end, it's helpful example starter application for exploring concepts.

Anyways, the framework that you end up with uses Google Guice for server-side dependency injection and the servlet config. I am going to describe how I converted that to use Spring instead, using the GWT-Dispatch-Spring library. Comments are welcome since I am probably not doing things optimally in some places.

Why would you want to do this? I think it is personal preference whether you use Guice or Spring (nice discussion/war going on here). I am fairly new to both so I don't have a personal opinion. In the end of this, you will end up with a project using Guice's ally Gin on the client and Spring on the server, so you can be like Switzerland and remain neutral. ;)

The major steps described here include:

  • Adding the Spring and gwt-dispatch-spring-ext jars to your project
  • Eliminating Guice from your server code
  • Adding the Spring configuration to your server code
  • Switch the client to use the Spring version of the DispatchAsync

Initial Setup

As I mention above, I recommend going through the blog and then downloading the tutorial code. Import the code into Eclipse (File->Import->Existing Projects Into Workspace). Make sure it all builds and that you can run it in the debugger.

Add Jars

Download the Spring library jars.
Download the gwtrpc-spring library.
Download the GWT-Dispatch-Spring extension library.
Import these jars into the project's war/WEB-INF/lib directory (right-click on the directory, Import->File System):
  • spring-aop-2.0.8.jar
  • spring-beans-2.5.6.jar *
  • spring-context-2.5.6.jar *
  • spring-core-2.5.6.jar
  • spring-security-core-2.0.4.jar
  • spring-web-2.5.6.jar
  • gwtrpc-spring-1.01.jar *
  • gwt-dispatch-spring-ext-1.0.0 *
The jars that I've marked with an asterisk also need to be added to your build path (Right click Referenced Libraries in the package explorer, Build Path->Configure Build Path).

Eliminate Guice from Server Code

Just delete the entire greet.server.guice package.

Configure the Server Code Using Spring

Modify SendGreetingHandler

There are several things we need to do to adapt the SendGreetingHandler to use Spring:
  • All your Handlers will now inherit from SpringActionHandler instead of implementing the ActionHandler interface.
  • To inherit from SpringActionHandler, we need a new constructor since that class requires the ActionHandlerRegistry to be passed in.
  • We need to swap out the Guice annotations and replace them with their Spring counterparts. We use @Autowired instead of @Inject. We also label the class as a @Component.
  • I haven't yet hooked back up the Logging functionality. An exercise for the reader.
  • To get the servlet and http request context, we are using the gwt-spring-rpc library RemoteServiceUtil.
Here is what my class looks like after making these changes:

@Component
public class SendGreetingHandler extends SpringActionHandler<SendGreeting, SendGreetingResult> {
 
 @Autowired
 public SendGreetingHandler(ActionHandlerRegistry actionHandlerRegistry) {
  super(actionHandlerRegistry);
 }

 @Override
 public SendGreetingResult execute(final SendGreeting action,
       final ExecutionContext context) throws ActionException {
  final String name = action.getName();
   
  try {

      String serverInfo = RemoteServiceUtil.getThreadLocalContext().getServerInfo();
      String userAgent = RemoteServiceUtil.getThreadLocalRequest().getHeader("User-Agent");

      final String message = "Hello, " + name + "!<br><br>I am running " + serverInfo + ".<br><br>It looks like you are using:<br>" + userAgent;

   
   //final String message = "Hello " + action.getName(); 
   
   return new SendGreetingResult(name, message);
  }
  catch (Exception cause) {
   
   throw new ActionException(cause);
  }
 }

 @Override
 public void rollback(final SendGreeting action,
        final SendGreetingResult result,
        final ExecutionContext context) throws ActionException {
  // Nothing to do here
 }
 
 @Override
 public Class<SendGreeting> getActionType() {
  return SendGreeting.class;
 }
}

Create applicationContext.xml

This file will configure Spring to do annotation-based dependency injection. It also indicates to Spring that it should search the Spring/Dispatch package and our own server code package when looking for components to match dependencies. Place this in war/WEB-INF.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
 xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">


 <context:annotation-config />
 <context:component-scan base-package="com.adeoservices.gwt.dispatch.spring.server" />
 <context:component-scan base-package="co.uk.hivedevelopment.greet.server" />

</beans>

Change web.xml

We need to change our servlet to use a RemoteServiceDispatcher servlet. Change web.xml to this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

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

 <servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>org.gwtrpcspring.RemoteServiceDispatcher</servlet-class>
 </servlet>

 <servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>*.rpc</url-pattern>
 </servlet-mapping>


 <servlet>
  <servlet-name>remoteLoggerServiceImpl</servlet-name>
  <servlet-class>com.allen_sauer.gwt.log.server.RemoteLoggerServiceImpl</servlet-class>
 </servlet>


 <!-- Default page to serve -->
 <welcome-file-list>
  <welcome-file>GreetMvp.html</welcome-file>
 </welcome-file-list>
 
</web-app>

Quick Review: What We Just Did

At this point, we have converted the Server side code to use Spring instead of Guice. Try running the app. You will probably notice a bunch of logging statements in the Console regarding the Spring dependency injection scanning:

1253 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Creating instance of bean 'sendGreetingHandler'
1254 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Returning cached instance of singleton bean 'springActionHandlerRegistry'
1255 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Autowiring by type from bean name 'sendGreetingHandler' via constructor to bean named 'springActionHandlerRegistry'

Try clicking the button to send the GreetAction to the server. You get an error because we haven't yet adjusted the client to work with Spring and to look for the new Spring RemoteServiceDispatcher that we defined in our new web.xml.

Update Client to Use Spring DispatchAsync

First, we need to add the Dispatch-Spring library to our client code. Add the highlighted line to GreetMvp.gwt.xml:

<inherits name='net.customware.gwt.dispatch.Dispatch' />
<inherits name="com.adeoservices.gwt.dispatch.spring.Dispatch-Spring"/>

Now, we just need to use the Spring-based DispatchAsync. Here is how I did it. I don't like it this way, so maybe somebody can help me (I'll explain). Go to the GreetingPresenter. Modify the constructor so that it takes a SpringDispatchServiceAsync instead of DispatchAsync:

public GreetingPresenter(final Display display, final EventBus eventBus, final SpringDispatchServiceAsyncImpl dispatcher, final GreetingResponsePresenter greetingResponsePresenter) {
What I don't like is how the GreetingPresenter now requires a specific implementation for DispatchAsync. What would be more appropriate is to configure Gin to inject the correct implementation in GreetingClientModule. At this point, I can't figure out how to get this to work: gin complains that DispatchAsync has been "double-bound". But I am basically brand new to Gin/Guice so I'm probably missing something.

In any case, the app should be working and the Client should receive a valid response from the server. You now have a working example using Spring on the server!

6 comments:

Alcor said...

Hi, i read hive dev's tutorial (really a good start!) and i'm now interessed in using spring with gwt-dispatch implementation of command pattern. but i didn't managed to compile gwt-dispatch-spring-ext (just because i'm not familiar with maven projects) and there is no binary to download on the google code link you provide. Is there any download link or advice to compile without maven? thanks!

PS : the comment box of your blog is badly displayed and not easy to use (we have to TAB to enter/validate captcha).

Alcor said...

Anyway that's ok, i put the source code in my project, modify the original code and it works fine. Now i have to check some hibernate and spring security issues but nothing impossible ... Thans for your tuto!

dipler said...

Hi, thanks for that tutorial - it's owesome. I've solved your issue about DispatchAsync binding. You're getting "double-bound" because of incorrect annotation in GreetingGinjector.
There should be only one module in: @GinModules(GreetingClientModule.class) and some changes in GreetingClientModule:
bind(DispatchAsync.class).to(SpringDispatchServiceAsyncImpl.class).in(Singleton.class);
That's all.

Peter said...

@Alcor: Sorry about the comment box, I'll look into that. Glad to hear you got it working. For future ref, for the gwt-dispatch-spring-ext jar, the easiest thing might be to go to the maven repository for it at Google Code here. I think you should be able to right-click on the "View Raw File" link at the right and save it that way. Compiling it _is_ a bit of a pain, even if you know maven, because you have to hunt down a bunch of missing dependencies.

Peter said...

@dipler Thanks very much for letting me know how to fix that Gin issue!

P.G. Taboada said...

Here is a less invasive way:

http://pgt.de/2009/09/16/use-spring-with-gwt-dispatch/

Post a Comment