GWT Spring Integration - using ContextLoaderListener
Update: I have put together a sample app to demonstrate the integration technique: 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!
Just a quick followup post to my last post http://seewah.blogspot.com/2008/07/gwt-and-spring.html.
In the last post I demostrated how to use the SpringApplicationContextLoader to manually load an application context from an xml file and subsequently to access Spring-managed beans via this class inside DependencyInjectionRemoteServiceServlet.
For one reason or another, I have moved away from this approach and gone back to the traditional means of using the org.springframework.web.context.ContextLoaderListener in web.xml to load application contexts. So this is what to do if you prefer this approach.
First of all, create the listener as you would with a normal Spring web application
in web.xml. If you are running in host mode, this is found under the tomcat/webapps/ROOT/WEB-INF folder inside your project. Otherwise, just edit the usual web.xml inside your webapps folder in your tomcat installation.
Now, inside DependencyInjectionRemoteServiceServlet, simply use the static org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext(ServletContext sc) method to access the loaded application context. This following is the revised class:
Note that there are two changes to this class. First of all, doDependencyInjection() now accesses the application context using the org.springframework.web.context.support.WebApplicationContextUtils class as described above. Secondly, doDependencyInjection() is now invoked from inside the servlet's init() method and not its constructor. This is because, when org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext(ServletContext sc) is invoked inside doDependencyInjection(), a reference to the servlet's ServletContext must be obtained, and this is only available after the servlet object has been constructed.
Just a quick followup post to my last post http://seewah.blogspot.com/2008/07/gwt-and-spring.html.
In the last post I demostrated how to use the SpringApplicationContextLoader to manually load an application context from an xml file and subsequently to access Spring-managed beans via this class inside DependencyInjectionRemoteServiceServlet.
For one reason or another, I have moved away from this approach and gone back to the traditional means of using the org.springframework.web.context.ContextLoaderListener in web.xml to load application contexts. So this is what to do if you prefer this approach.
First of all, create the listener as you would with a normal Spring web application
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
in web.xml. If you are running in host mode, this is found under the tomcat/webapps/ROOT/WEB-INF folder inside your project. Otherwise, just edit the usual web.xml inside your webapps folder in your tomcat installation.
Now, inside DependencyInjectionRemoteServiceServlet, simply use the static org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext(ServletContext sc) method to access the loaded application context. This following is the revised class:
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 org.springframework.web.context.support.WebApplicationContextUtils;
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);
@Override
public void init() throws ServletException {
super.init();
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, WebApplicationContextUtils.getWebApplicationContext(getServletContext()).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;
}
}
Note that there are two changes to this class. First of all, doDependencyInjection() now accesses the application context using the org.springframework.web.context.support.WebApplicationContextUtils class as described above. Secondly, doDependencyInjection() is now invoked from inside the servlet's init() method and not its constructor. This is because, when org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext(ServletContext sc) is invoked inside doDependencyInjection(), a reference to the servlet's ServletContext must be obtained, and this is only available after the servlet object has been constructed.
Comments
on this line
for (Field field : getFieldsToDependencyInject())
i get error Type mismatch: cannot convert from element type Object to Field
I have also put the code into a scrollable pane, which hopefully should make it more readable.
At the time when I came up with this solution, exporting spring beans involved extending the GWTSpringController, which I did not really like. I wanted to keep my Spring layer as clean as possible and write my own GWT RPC servlets to access simple Spring service beans instead of using Spring MVC to "export" these services. It is simply a different way of doing things. Doing it my way means Spring developers do not have to care/know anything about GWT integration, and GWT developers have total control on how to use these service beans.
Since then they have introduced GWTRPCServiceExporter, which seems less invasive. Again it does not change the fundamental fact that the integration logic happen on the Spring side and not the GWT side.
As I said in my original article, there are so many different ways of tackling this problem. I just want to give people some ideas to create solutions that suit their particular needs.