Using Spring JavaConfig on Google App Engine

Andrew Phillips

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.

Comments (4)

  1. Chris Beams - Reply

    January 18, 2010 at 4:37 am

    Andrew,

    Thanks for the great write-up. I'm glad you got things working, but it is unfortunate about @Resource not being supported in the GAE. From the looks of things on the mailing list, it sounds like they will add this in. I'm now following the issue that I believe you created regarding this, and will generally keep tabs on it. It's probably best to simply wait for Google to fix this, as opposed to making changes to Spring itself to defensively avoid classloading / processing @Resource in environments that don't support it.

    Cheers,

    - Chris Beams

  2. Andrew Phillips - Reply

    January 20, 2010 at 10:55 am

    Actually, someone beat me to it with the creation of that issue ;-) And yes, I agree that it doesn't make sense to change Spring for this - having almost all the JSR-250 annotations on the GAE whitelist but not @Resource just doesn't make sense.

  3. Larry Cable - Reply

    February 10, 2010 at 12:28 am

    Thanks for documenting the problem with the SecurityException caused by the CBAPP Spring class loading @Resource ...

    A couple of GAE app devs including myself were encountering a problem where @PostConstruct was working locally and not working when uploaded ... none of us seemed to be sufficiently smart to spot the exception in the logs ...

    So thanks for this!

    - Larry Cable

  4. Andrew Phillips - Reply

    March 2, 2010 at 12:26 pm

    @Zzz: Thanks for the heads-up about the 500 server error. Down to a change of the Atmos API - should be fixed now...

Add a Comment