An early believer in the ability of Java to deliver "enterprise-grade" software, Andrew Phillips quickly focused on the development of high-throughput, resilient and scalable J2EE applications. Specializing in concurrency and high performance development, Andrew gained substantial experience of the intricacies, complexity and challenges of enterprise application environments while working for a succession of multinationals. Continuously focused on effectively integrating promising new developments in the Java space into corporate software development, Andrew joined XebiaLabs in March 2009, where he is a member of the development team of their deployment automation product Deployit. Amongst others, he also contributes to Multiverse, an open-source Java STM implementation, and jclouds, a leading Java cloud library. Andrew is a DZone MVB and is not an employee of DZone and has posted 24 posts at DZone. You can read more from them at their website. View Full User Profile

Using Spring’s Java Configuration on Google App Engine

01.18.2010
| 7463 views |
  • submit to reddit

Recently, I put together a Spring demonstration for jclouds, the Java cloud library. This quickly turned into unexpected multi-dimensional experiment in integrating Guice, Google App Engine and Spring, but after much trial-and-error I finally came across a configuration that does the trick - or at least works1 as well as seems possible on GAE.

The tweetstore demo

Tweetstore is a simple web application that demonstrates jclouds' cloud capabilities. It queries Twitter for mentions of the user's account and creates a "backup" of this priceless record of your contemporary image in three cloud stores: Amazon S3, Microsoft's Azure and Rackspace. Nothing less than double redundancy is good enough for your popularity!

The original tweetstore application uses Guice for dependency injection and request mapping. In order to make it as easy as possible to compare the Spring version to the original, I decided to try to similarly do as much in Java code as possible. A perfect opportunity for me to get my hands dirty with Spring 3.0's support for Java configuration.

Bootstrapping Spring's Java Configuration

The first, relatively straight-forward step was to convert the Guice servlet module into an equivalent Spring @Configuration:

@Configuration
public class SpringServletConfig extends LoggingConfig implements ServletConfigAware {
private ServletConfig servletConfig;
...

@PostConstruct
public void initialize() {
...
}

@Bean
public StoreTweetsController storeTweetsController() {
StoreTweetsController controller = new StoreTweetsController(providerTypeToBlobStoreMap,
container, twitterClient);
injectServletConfig(controller);
return controller;
}

@Bean
public AddTweetsController addTweetsController() {
AddTweetsController controller = new AddTweetsController(providerTypeToBlobStoreMap,
serviceToStoredTweetStatuses());
injectServletConfig(controller);
return controller;
}

private void injectServletConfig(Servlet servlet) {
try {
servlet.init(checkNotNull(servletConfig));
} catch (ServletException exception) {
throw new BeanCreationException("Unable to instantiate " + servlet, exception);
}
}

@Bean
ServiceToStoredTweetStatuses serviceToStoredTweetStatuses() {
return new ServiceToStoredTweetStatuses(providerTypeToBlobStoreMap, container);
}

@Bean
public HandlerMapping handlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
Map<String, Object> urlMap = Maps.newHashMapWithExpectedSize(2);
urlMap.put("/store/*", storeTweetsController());
urlMap.put("/tweets/*", addTweetsController());
mapping.setUrlMap(urlMap);
/*
* "/store" and "/tweets" are part of the servlet mapping and thus stripped
* by the mapping if using default settings.
*/
mapping.setAlwaysUseFullPath(true);
return mapping;
}

@Bean
public HandlerAdapter servletHandlerAdapter() {
return new SimpleServletHandlerAdapter();
}

...
}

Being ServletConfigAware isn't exactly Spring best practice but then the chosen aim was to stay as close as possible to the original set-up rather than building a Spring reference application.

Hooking Spring into the GAE startup was the next step. The Spring reference example was my first attempt:

<web-app>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- Configure DispatcherServlet to use JavaConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>org.jclouds.demo.tweetstore.config.SpringServletConfig</param-value>
</init-param>
</servlet>

<servlet-mapping>
...
</web-app>

Unfortunately, this unceremonially blew up in my face with a security violation: AnnotationConfigWebApplicationContext (and all Spring contexts that register a CommonAnnotationBeanPostProcessor) attempts to load javax.annotation.Resource to determine support for JSR-250, and that class, unlike others in the javax.annotation package, happens to be missing from the GAE whitelist.

It ain't over until the App Engine sings

Moving back to a standard -servlet.xml file gets rid of the @Resource problem, but I was still left with an exception caused by Guice, which jclouds uses internally for dependency injection. I later discovered that the exception is relatively harmless and can be ignored2. But of course no developer likes exceptions - however harmless - in the boot logs, so I wondered whether executing this call earlier in the boot sequence might resolve the problem.

So I moved the offending call from the servlet to the application context, guessing that the ContextLoaderListener might have more permissions than a servlet:

<web-app>
<!-- can't use Java sconfiguration due to GAE's security restrictions -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/tweetstoreContext.xml</param-value>
</context-param>

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

<servlet>
...

And it worked! Or rather, it worked on the Java Development Server, the App Engine "simulator" provided with the SDK which is intended for development testing. But it didn't work on App Engine itself3.
A word of caution, therefore: test on the "real" App Engine before concluding that something works, especially if it's related to security restrictions!

Salvaging some @AnnotationConfig

By this time I knew that I wasn't going to get away without writing at least a bit of Spring XML, but I was still trying to stick to Java as much as possible. After plenty of digging around in the Spring sources and a bit of experimentation, I discovered that, of the bean post processors currently registered by <context:annotation-config/>4 that I was interested in, only the CommonAnnotationBeanPostProcessor actually causes a problem. In fact, you can even use its immediate superclass, InitDestroyAnnotationBeanPostProcessor, which provides the @PostConstruct and @PreDestroy support I was looking for:

<beans xmlns="...>
<!-- the usual <context:annotation-config/> can't be used because the
CommonAnnotationBeanPostProcessor causes a security exception in GAE when it
tries to load javax.annotation.Resource -->
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean class="org.springframework.context.annotation.ConfigurationClassPostProcessor" />
<bean class="org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor">
<property name="initAnnotationType" value="javax.annotation.PostConstruct" />
<property name="destroyAnnotationType" value="javax.annotation.PreDestroy" />
</bean>
<bean class="org.jclouds.demo.tweetstore.config.SpringServletConfig" />
</beans>

So if you can live without @Resource, it seems that you can get away with most of Spring's annotation-driven configuration on the GAE.

Debugging the Java Development Server

The GAE SDK starts the Java Development Server using a platform-independent launcher called KickStart, which is quite easy to integrate into your test suite.
Unfortunately, KickStart doesn't start the server in a new thread, but in a completely separate process, so attaching a debugger from your IDE is, well, tricky.
Luckily KickStart accepts jvm_flag arguments, as outlined in the Javadocs5:

At present, the only valid option to KickStart itself is:
--jvm_flag=<vm_arg>
Passes <vm_arg> as a JVM argument for the child JVM. May be repeated

Here you can add the standard JVM debugging parameters.

Footnotes
  1. Well, actually it's running into a timeout at the time of writing, but that's not related to the Spring config. Really ;-)
  2. Which is to say that it doesn't interrupt the bootstrapping process (it occurs in a separate thread), and your application will run fine. Here it is:


    com.google.inject.internal.FinalizableReferenceQueue : Failed to start reference finalizer thread. Reference cleanup will only occur when new references are created.
    java.lang.reflect.InvocationTargetException
    ...
    at com.google.inject.internal.InjectorBuilder.initializeStatically(InjectorBuilder.java:134)
    at com.google.inject.internal.InjectorBuilder.build(InjectorBuilder.java:108)
    at com.google.inject.Guice.createInjector(Guice.java:93)
    at com.google.inject.Guice.createInjector(Guice.java:70)

  3. I suspect this has something to do with how and when the security manager is installed in the Dev Server, but haven't investigated this in any detail.
  4. The Spring docs don't mention ConfigurationClassPostProcessor, for some reason, but according to its Javadoc it is "Registered by default when using <context:annotation-config/> or <context:component-scan/>."
  5. Which don't appear to be available online, for some reason.

From http://blog.xebia.com

 

Published at DZone with permission of Andrew Phillips, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Craig Doremus replied on Mon, 2010/01/18 - 11:59am

Hi,

What is your take on doing DI using JSR-330? Is it possible to easily swap Guice and Spring JSR-330 implementations?

TIA

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.