Tuesday, February 20, 2007

GWT-Spring Integration Demystified

It is not a mistery that Google Web Toolkit (GWT) began to gain a lot of teritory in its battle with other similar frameworks. Google Web Toolkit (GWT) is an open source Java development framework that makes the writing of AJAX applications quite a pleasure and fun . When you deploy your application to production, the GWT compiler translates your Java application to browser-compliant JavaScript and HTML.What makes GWT a great tool to use ? This is not at all a tricky question. The answer is extremly simple: GWT is light, easy, browser compatible, has a simple RPC mechanism, Junit integration, dynamic, reusable UI components, you can use your favourite Java IDE to develop your application and use any GWT library you need and of course you can contribute to the improvement of GWT at any time as it is completely open-source.

What makes GWT more interesting is that it can be integrated with technologies like Struts, Spring, Hibernate, Ant, Maven etc and this is quite a wonder considering the speed of entreprise applications development.

When I first started this investigation I didn't find very many resources on how these technologies can be integrated and this is how I decided to help the community of those willing to make a step towards this integration. The purpose of this blog is to explain as simple as possible how GWT and Spring can be brought together and make the the most of your entreprise application.


Let's get to the real stuff ;-)


First thing to consider is the directories structure. Pay an extra attention to this step as you will face some difficulties in compiling the files if the sources are not in the right packages.


./src/com/MyApplication.gwt.xml
./src/com/public/MyAplication.htm

./src/com/client/MyApplication.java
./src/com/client/MyService.java
./src/com/client/MyServiceAsync.java
./src/com/server/MyServiceImpl.java
./src/com/server/ServletWrappingController.java

./src/spring-servlet.xml
./src/web.xml

./WEB-INF/lib/spring.jar
./WEB-INF/lib/gwt-user.jar
./WEB-INF/lib/gwt-dev-windows.jar
./WEB-INF/lib/gwt-servlet.jar
./WEB-INF/spring-servlet.xml

./WEB-INF/web.xml


The application we are going to develop is a simple interaction with the server across a network (that is making a remote procedure call). The client will send a request to the server and the server will response to the client.A sevice is the server-side code that gets invoked from the client.


Our application can be run either in hosted-mode or we can be compiled into JavaScript and HTML to run it in the web-mode. If you use Eclipse IDE to develop your application simply generate an Eclipse project using the projectCreator script from your gwt installation directory:


projectCreator -eclipse SpringGwtIntegration


and then generate your GWT application using the applicationCreator shell:

applicationCreator -eclipse SpringGwtIntegration com.client.MyApplication

MyApplication.gwt.xml file is the GWT configuration file that specifies the location of the core web toolkit material and the entry point for the application.


./src/com/MyApplication.gwt.xml


Notice the defined servlet, which is the server-side class for our RPC service.
In the public folder there is the HTML used primarily to test the application in the hosted mode:


./src/com/public/MyAplication.htm

In the “client” package we have the class that is our “main” application, and other two interfaces to the remote code. The MyService interface is implemented by the server-side code, in this case the MyServiceImpl class.

./src/com/client/MyService.java
package com.client;
import com.google.gwt.user.client.rpc.RemoteService;

public interface MyService extends RemoteService
{
public abstract String myMethod(String s);
}

The second one is used for the client-side code. The name of the interface MUST be the same as the server-side interface with "Async" appended. It must implement all of the methods in the server-side interface, but all of the methods MUST also take an additional parameter, which is an AsyncCallback object.


./src/com/client/MyServiceAsync.java
package com.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface MyServiceAsync
{
public abstract void myMethod(String s, AsyncCallback asynccallback);
}


The two "myMethod" methods differ by the additional AsyncCallback asynccallback parameter and the return type.
From the client application first you create an AsyncCallback object by using the GWT.create() method that takes the server-side interface as an argument and returns an object that uses the client-side interface.
We create an object instance that implements the AsyncCallback interface. Here we create an anonymous class for this. The server-side code is called asyncronously (the code won't wait for a response). That is why we need to create this object. This object will handle the result when it makes it's way back to the browser. We need to write code to handle both success and error conditions.


./src/com/client/MyApplication.java
package com.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.*;


// Referenced classes of package csw.client MyService, MyServiceAsync
public class MyApplication implements EntryPoint {
public MyApplication() {
}
public void onModuleLoad() {
final Label label = new Label();

final MyServiceAsync svc = (MyServiceAsync) GWT.create(com.client.MyService.class);
ServiceDefTarget endpoint = (ServiceDefTarget) svc;
endpoint.setServiceEntryPoint("services/myService");
final AsyncCallback callback = new AsyncCallback() {
public void onSuccess(Object result) {
label.setText(result.toString());
}
public void onFailure(Throwable ex) {
label.setText(ex.toString());
}
};
Button button = new Button("Click ME");
button.addClickListener(new ClickListener() {
public void onClick(Widget w) {
svc.myMethod("Do Something", callback);
}
});
RootPanel.get("testing").add(button);
RootPanel.get("testing2").add(label);
}
}


The server-side code:


./src/com/server/MyServiceImpl.java
package com.server;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.client.MyService;
public class MyServiceImpl extends RemoteServiceServlet
implements MyService
{
private static final long serialVersionUID = 1L;
public MyServiceImpl()
{
}
public String myMethod(String s)
{
return (new StringBuilder("You submitted: '")).append(s).append("'").toString();
}
}


To integrate this with Spring we only have to register our GWT servlet in our web.xml so we can simply use Spring's ServletForwardingController to call services by name.
We also have to modify a bit our GWT client to reflect a path change we have to do. We add to the web.xml file the Spring's DispatcherServlet mappings:


The web.xml will look like:




The last thing to do is to create a new configuration file called spring-servlet.xml where we put the real configuration of
our GWT's service controller.

The ServletWrappingController controls the RemoteServiceServlet's lifecycle completely, emulating the servlet-container.


package com.server;
import java.util.Enumeration;
import java.util.Properties;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;


/**
* Spring Controller implementation that mimics standard ServletWrappingController behaviour
* (see its documentation), but with the important difference that it doesn't instantiate the
* Servlet instance directly but delegate for this the BeanContext, so that we can also use IoC.*
*/
public class ServletWrappingController extends AbstractController
implements BeanNameAware, InitializingBean, DisposableBean {
private Class servletClass;
private String servletName;
private Properties initParameters = new Properties();
private String beanName;
private Servlet servletInstance;
public void setServletClass(Class servletClass) {
System.out.print("setServletClass : "+servletClass);
this.servletClass = servletClass;
}
public void setServletName(String servletName) {
System.out.print("setServletName : "+servletName);
this.servletName = servletName;
}
public void setInitParameters(Properties initParameters) {
System.out.print("setInitParameters : "+initParameters);
this.initParameters = initParameters;
}
public void setBeanName(String name) {
System.out.print("setBeanName : "+name);
this.beanName = name;
}
public void setServletInstance(Servlet servletInstance) {
System.out.print("setServletInstance : "+servletInstance);
this.servletInstance = servletInstance;
}


public void afterPropertiesSet() throws Exception {
System.out.print("afterPropertiesSet");
if (this.servletInstance == null) {
throw new IllegalArgumentException("servletInstance is required");
}
if (!Servlet.class.isAssignableFrom(servletInstance.getClass())) {
throw new IllegalArgumentException("servletInstance [" + this.servletClass.getName() +
"] needs to implement interface [javax.servlet.Servlet]");
}
if (this.servletName == null) {
this.servletName = this.beanName;
}
this.servletInstance.init(new DelegatingServletConfig());
}
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
System.out.print("handleRequestInternal");
this.servletInstance.service(request, response);
return null;
}
public void destroy() {
System.out.print("destroy");
this.servletInstance.destroy();
}
private class DelegatingServletConfig implements ServletConfig {
public String getServletName() {
return servletName;
}
public ServletContext getServletContext() {
return getWebApplicationContext().getServletContext();
}
public String getInitParameter(String paramName) {
return initParameters.getProperty(paramName);


}
public Enumeration getInitParameterNames() {
return initParameters.keys();
}
}
}


Well, that was all folks. We only have to run the MyApplication-shell to run the application in the hosted-mode or compile the packages with the MyApplication-compile shell, add the generated output of the www directory to a SpringGwtIntegration war archive, add the necessary configuration files (web.xml, spring-servlet.xml, the bin and lib directories) and copy it in the server\default\deploy folder of your Jboss version.
Note: this application is deployed with the jboss-4.0.5.GA and jboss-3.2.8.SP1 versions of Jboss.


Any comment or suggestion is welcome.


17 comments:

Ċ˘ugurlan said...

OMG, that's one loooong post! Please serve it in smaller bites... pleease!

And yay for me, comment no. 1

Anonymous said...

Too long? ... No, leave it as it is. It's great. Thanks!

calos said...

i tried it out, it works perfectly in hosted mode, but when i deploy it on tomcat, classNotFoundError comes, eventhough all jars required r in libs folder, plz check it out n help me.

as i had deployed my application earlier n got d same error, n now with ur application too, which some ppl r sayin it workin.

thanks in advance..

Anonymous said...

Nice blog.Victor Manea :)

Kris Hofmans said...

This is way too much effort to adding more controllers to a gwt app.

People should read: http://g.georgovassilis.googlepages.com/usingthespringgwtcontroller or http://g.georgovassilis.googlepages.com/usingthegwthandler

I still recommend the springwtcontroller because even though it is deprecated, but the gwt handler currently breaks unit testing & mixing it with regular spring controllers.

Why in gods name would you wrap the controllers, you can add as many mappings as you want in the web.xml for each service you will use in your gwt app, and configure your spring as usual, having the only change from regular spring being the extending of the gwtspringcontroller.

Cesaro said...

I'm looking at this post as a solution for GWT and Spring integration. Looks good so far, I'll comment out my results as soon as I try it out.

Thanks for this post!

Buffa said...

Hi:

I find this very simple example fine however, I have a problem with the way GWT does serialization. i.e. As long as you return a built in type things are fine however, when you want to return a defined class things get nasty. I do not understand why GWT forces one to implement their serialization interface. This tightly couples you to the GWT regardless which, I find a serious problem.

Claudio

George Georgovassilis said...

I might add that historical reasons of the way RPC evolved in GWT lead to many different approaches towards binding Spring and RPC.

The most obvious one is re-implementation of the RPC facility by extension (which was my first attempt [1] and is also shown in your article), but you 'burn' your single free class in the java single inheritance model.

Fruitful discussion on the forum finally urged us to chose the hard way in the SL subproject of the GWT-WL [2]: runtime weaving over copy & paste or extension, because we wanted to rely on as little contracts as possible, the most important certainly being the RPC serialisation format itself or the RPC parts which were not a blessed, official API.

In the upcomming GWT 1.4 we expect an important change to the RPC code which essentially manifests as a public API and allows implementations such as yours or the GWT-SL to employ RPC as a library, which will make our life a lot easier on the frontier of integrating Spring with GWT.

[1] Post on GWTController
[2] gwt-wl
[3] Blog on RPC in 1.4

david said...

I'm developing an application that handles the view layer with GWT completely. To integrate my business logic with GWT, I'm simply adding Spring's ContextLoaderListener to web.xml and inject my business objects into GWT's remote service servlets.

Could you compare your approach to mine? Any ideas on advantages/disadvantages of your and my approach?

Anonymous said...

Thanks for this solution - I am going to use it for my app.

The web.xml you defined did not work for me in web mode - had to change the url-pattern from /com.MyApplication/services/* to /services/*.

A suggestion for future posts: don't insert code with .jpgs, we can't cut and paste your work!

koziolek said...

Hi!
Nice lesson of Spring and GWT :)
If you will write next posts as good as this your blog will be in my Required Lectures list :)

ps. sorry for terible language ;)

cristian said...

I have a problem: I am trying to run the hosted mode version. I don't think the web.xml gets interpreted (I don't see any log messages of spring, and if I modify something inside, the app continues to work).

How can I be sure that web.xml is interpreted? Is there another operation to be done besides putting the web.xml in the src folder?

Thank you in advance,
Cristian.

Anonymous said...

The length is just fine. Please do not serve it in smaller bites. Todays kids should learn how to read an adult book.

Annerose said...

These comments have been invaluable to me as is this whole site. I thank you for your comment.

/r:b: said...

Here's a briefer version of how to integrate Spring 2.0 and GWT.

Offshore Software Development said...

For organizations to stay ahead in today’s fast changing business scenario, time is the single biggest factor – time to market, time to launch new products, time to respond to customers. Organizations are faced with a growing number of challenges and business risks due to rapid advances in technology and increasing pressures on margins.

Software Development Company

art said...

Hi,

This article is good and informative.

Software Development Company

Free Directory

Software Jobs India