Saturday, July 26, 2008

GWT Spring Integration

Update: I have recently posted a follow-up blog http://seewah.blogspot.com/2009/02/gwt-spring-integration-using.html which demostrates how to combine this approach with the more conventional method of loading ApplicationContext xml using ContextLoaderListener.

Update 2: I have put together a sample app to demonstrate the integration technique (based on the more conventional method of loading ApplicationContext.xml using ContextLoaderListner): http://seewah.blogspot.com/2009/06/gwt-and-spring-security-sample-demo.html Just take out applicationContext-security.xml if you are not interested in the Spring Security aspect!


This is my first technical post. Sorry about the code formatting. I will fix it later!

A couple of months ago, I started working on a GWT (Google Web Toolkit) project, using Spring as a IoC container to manage service beans on the server-side. An initial Google search for "spring gwt integration" led to a few ideas. Check out http://www.jroller.com/galina/entry/google_web_toolkit_integration_with for a summary of some of the notable contributions out there.

While there are a lot of working (and sometimes quite clever) solutions out there, most tend to tackle the problem in a rather round-about way. So I came up with my own solution, which, in my humble opinion, is quite a bit simpler. It is by no means perfect, but it may serve as an interesting reference for those who are interested.

First of all, I want to leverage the built-in Tomcat provided by GWT, and I want to stick to the GWT model of letting com.google.gwt.dev.shell.GWTShellServlet handle all requests in hosted mode - i.e absolutely no changes to the standard GWT application setup are required to run the app in hosted mode.

I created two classes: DependencyInjectionRemoteServiceServlet and SpringApplicationContextLoader:

DependencyInjectionRemoteServiceServlet extends the standard
com.google.gwt.user.server.rpc.RemoteServiceServlet, and this is going to be the class that all RPC servlet implementions will be extending. For example, if you have a service for authenticating users, you will have two interfaces:



public interface AuthenticationService extends RemoteService {...}

public interface AuthenticationServiceAsync {...}



and at least one implementation class:



public class AuthenticationServiceImpl extends DependencyInjectionRemoteServiceServlet implements AuthenticationService {...}



Before looking at what DependencyInjectionRemoteServiceServlet does, let's take a quick look at SpringApplicationContextLoader first:




import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
* Loads Spring application context from the classpath, and provides static
* method to retrieve Spring-managed beans from the loaded context by name.
*
* @author See Wah Cheng
* @created 27 Jun 2008
*/
public class SpringApplicationContextLoader {

private static final String CONTEXT_LOC = "classpath:applicationContext.xml";

private static ApplicationContext applicationContext = new ClassPathXmlApplicationContext(new String[] { CONTEXT_LOC });

/**
* Returns a Spring-managed bean by name.
*
* @param name
* name of bean in the application context
* @return the Spring-managed bean
* @exception NoSuchBeanDefinitionException
* if a suitable bean cannot be found in the Spring application
* context. The current implementation looks up beans by name
*/
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
}



So nothing clever here - just a class to load the Spring applicationContext and to retrieve beans by name.

Now let's look at the DependencyInjectionRemoteServiceServlet code:



import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
/**
* {@link RemoteServiceServlet} that automatically injects IoC dependency.
* "org.springframework.beans.factory.annotation.Autowired" annotation is used
* for marking which fields to inject into. Uses
* {@link SpringApplicationContextLoader} to retrieve beans by name.
* <p>
* Note that the current implementation will only inject "declared" fields, and
* not inherited fields. Fields can be private, protected, package or public.
*
* @author See Wah Cheng
* @created 27 Jun 2008
*/
@SuppressWarnings("serial")
public class DependencyInjectionRemoteServiceServlet extends RemoteServiceServlet {

protected static Logger logger = Logger.getLogger(DependencyInjectionRemoteServiceServlet.class);

public DependencyInjectionRemoteServiceServlet() {
super();
doDependencyInjection();
}

/**
* Carries out dependency injection. This implementation uses Spring IoC
* container.
*
* @exception NoSuchBeanDefinitionException
* if a suitable bean cannot be found in the Spring application
* context. The current implementation looks up beans by name
*/
protected void doDependencyInjection() {

for (Field field : getFieldsToDependencyInject()) {
try {
boolean isFieldAccessible = field.isAccessible();
if (!isFieldAccessible) {
field.setAccessible(true);
}
field.set(this, SpringApplicationContextLoader.getBean(field.getName()));
if (!isFieldAccessible) {
field.setAccessible(false);
}
logger.debug("Dependency injection successful: " + this.getClass().getName() + "." + field.getName());
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}

/**
* Find annotated fields to inject.
*
* @return a list of all the annotated fields
*/
private Set<Field> getFieldsToDependencyInject() {
Set<Field> fieldsToInject = new HashSet<Field>();
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.getAnnotation(Autowired.class) != null) {
fieldsToInject.add(field);
}
}
return fieldsToInject;
}
}



So what is happening here? When the servlet instantiates, it calls doDependencyInjection, which uses reflection to find out which fields to inject Spring beans into based on the @Autowired annotation. It will attempt to find the bean in the Spring application context by name using SpringApplicationContextLoader. Note that I am only using @Autowired to mark out fields. It does not do anything else. I could have devised my own annotation, but I have opted to use this Spring annotation for convenience's sake. So all you have to do now, when creating your RPC service implementation, is to extend DependencyInjectionRemoteServiceServlet and annotate your fields:



public class AuthenticationServiceImpl extends DependencyInjectionRemoteServiceServlet implements AuthenticationService {

@Autowired
private UserAccountService userAccountService;

public boolean authenticate(String userName, String password) {
userAccountService.authenticate(userName, password);
....
}
}



and of course in your applicationContext.xml, you have to define a bean whose id is userAccountService.



<bean class=".....UserAccountServiceImpl" id="userAccountService"/>

12 comments:

Anonymous said...

Thanks - this helped me jumpstart my gwt project with spring. Your approach does work with the GWT supplied tomcat - at the expense of being able to "configure" the spring application context files better.

See Wah said...

You are welcome - I am glad you got some ideas from my blog.

Incidentially, since I published the blog, I have had to implement Spring Security (formerly ACEGI), which means I am now using the standard org.springframework.web.context.ContextLoaderListener listener to load both my ACEGI context and the "main" application context in my web.xml's (both the embedded tomcat web.xml and the war deployment web.xml)

This means I have actually scrapped the idea of SpringApplicationContextLoader. I am directly calling WebApplicationContextUtils.getWebApplicationContext(getServletContext()).getBean(field.getName()) in DependencyInjectionRemoteServiceServlet instead.

Nonetheless, the idea of annotation-based injection using Java reflection remains!

Ayla said...

Interesting to know.

Frances said...

This is a great example. Currently, I want to integrate Spring security into my GWT-Ext application and I have some trouble with it. You mentioned that you have had to implemnet Spring security. Could you please give me some guidance how to do GWT/Ext Spring integration?
Thanks a bunch.

See Wah said...

Thanks Frances. I am afraid I do not have any experience with GWT-EXT (or GWT-EXT-SPRING, for that matter...)

I am actually planning to post another blog article on security integration, but I have been a bit busy, and I probably won't have time to do it until Christmas!

However, I can give you a quick overview of what I have done here:

- I manually create an org.springframework.security.Authentication and set it in the SecurityContext in a GWT RPC service - i.e. my "AuthenticationService".

- I set up an org.springframework.security.context.HttpSessionContextIntegrationFilter (as part of an org.springframework.security.util.FilterChainProxy) in the applicationContext to make sure that the SecurityContext is repopulated between RPC requests.

Now with the Authentication info readily available in the SecurityContext, I can look in there for a valid Authentication whenever I want in any GWT RPC services, OR even better, having configured "method security", I simply annotate methods in my Spring-managed beans using the @Secure Spring Security annotation and let Spring throw AccessDeniedException's!

So, what if you want access to this authentication information on the client-side so that the application knows whether someone is logged in, for example, without having to send a GWT RPC request. This is just a matter of replicating the Authentication information on the client-side and making sure that server-side and client-side are in sync. I will go into more details in my next post.

In the mean time, Frances, if you have any questions, do let me know. I am more than happy to answer them, if I can!

Anonymous said...

why there is no source code for "UserAccountService" class ? can you post it?

Anonymous said...

Hi See Wah,
May i know how to write UserAccountService, UserAccountServiceImp and do you have example in gwt how to call the service?

thank you

See Wah Cheng said...

I did not include source code for UserAccountService since this article focuses purely on how to access Spring-managed beans inside your GWT application. UserAccountService is just an example use case I have chosen. I could have chosen an entirely different use case. For example, it could have been a GWT RemoteServiceServlet talking to a Spring bean which carries out a complex calculation.

Incidentally, in one of my subsequent blog articles, http://seewah.blogspot.com/2009/02/gwt-and-spring-security.html, where I demonstrated how to use Spring Security with GWT, I did include an example of a UserAccountServiceImpl. But of course this implementation only makes sense if you are using Spring Security as your application security framework!

What exactly are you trying to do? Maybe I can offer some help.

See Wah Cheng said...

I have finally put together a sample app: http://seewah.blogspot.com/2009/06/gwt-and-spring-security-sample-demo.html

Hope this helps! :-)

Anonymous said...

Great!!!

Thanks! It is almost impossible to find a simple and clean solution on the web - until someone has the luck to find your article!

See Wah Cheng said...

Glad you found it useful!

Sergey said...

Very great solution. I've made some improvements into it. Here ise my class DependencyInjectionRemoteServiceServlet:

@SuppressWarnings("serial")
public class DependencyInjectionRemoteServiceServlet extends RemoteServiceServlet {

@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
doDependencyInjection();
}

/**
* Carries out dependency injection. This implementation uses Spring IoC
* container.
*
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
* if a suitable bean cannot be found in the Spring application
* context. The current implementation looks up beans by name
*/
protected void doDependencyInjection() {

for (Field field : getFieldsToDependencyInject()) {
try {
boolean isFieldAccessible = field.isAccessible();
if (!isFieldAccessible) {
field.setAccessible(true);
}
field.set(this, getBean(field.getName()));
if (!isFieldAccessible) {
field.setAccessible(false);
}
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}

/**
* Find annotated fields to inject.
*
* @return a list of all the annotated fields
*/
private Set getFieldsToDependencyInject() {
Set fieldsToInject = new HashSet();
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.getAnnotation(Autowired.class) != null) {
fieldsToInject.add(field);
}
}
return fieldsToInject;
}

private Object getBean(String beanName) {
WebApplicationContext ctx =
WebApplicationContextUtils.getRequiredWebApplicationContext(
getServletConfig().getServletContext());
return ctx.getBean(beanName);
}
}

As you see I use web.xml's configuration to get context. May be it will help someone.