Spring Context Mix-Ups

While trying to add caching to a bean in my client’s project, I ran into some strange behaviour. The project already used caching on several components and used the @Cacheable annotation with an EhCache backing store. I added a cache definition to the ehcache.xml file and put the @Cacheable annotation on the method, recompiled, tested… No caching.

Update 2015-01-26: I added a little trick to mitigate this situation if you run into it. See below for the fix.

Confused, I cleaned, recompiled again, restarted, tested. Still no caching. My first instinct was: maybe caching is improperly configured and the other beans also have no active caching, even though we think they do. So I started debugging, to see if the calls to the other beans did use caching. To my surprise, they did. Refreshing the page did not cause those methods to be called again, but mine were.

Some further debugging showed that the SpringCacheAnnotationParser did process my bean and was able to see the @Cacheable annotation. However, when debugging the method that was supposed to be cached, I noticed there were no references to any CacheInterceptor instances in the stack trace. Even though during startup my bean was replaced by a generated proxy.

This raised a suspicion that maybe I was looking at two instances of my bean. A breakpoint on my bean’s constructor revealed that indeed, it was called twice. The cause became pretty obvious when I examined the Spring configuration files:

applicationContext.xml

<beans ...>
  ...
  <context:component-scan base-package="com.our.app"/>
  ...
</beans>
servlet-context.xml

<beans ...>
  ...
  <context:component-scan base-package="com.our.app"/>
  ...
</beans>

Aha! It appears our Spring context is loading all classes annotated with @Component, @Service, etc. twice! Once on the startup of the application and again when the first request comes in for the dispatcher servlet. Now, the reason that caching worked on some, but not all beans, lied in the fact that caching was configured in the applicationContext.xml file, which applied caching wrappers to all beans in that context. There was no caching configured in servlet-context.xml, causing beans loaded there to not have caching wrappers.

Our Java EE Servlet Filters were loaded from the application-context.xml, which contained caching. Those were fine. But any requests coming from the dispatcher servlet would reach beans in the servlet-context.xml context, which did not have caching.

The solution was quite a lot of work, since we had to re-evaluate all annotated classes and decide whether they should live in the application context, or the dispatcher servlet context. The rule of thumb we used here was:

Classes annotated with @Component and @Service will be loaded in the application context. Classes annotated with @Controller and its derivatives will be loaded in the dispatcher servlet context.

I realize there may be more subtle cases where this rule does not fully apply, but it’s a good starting point. Generally you want any potentially shared beans in the application context and anything specifically tied to the dispatcher servlet in its context. At least take this advice to heart:

Never, ever use component-scan on the same package tree from different contexts

Update 2015-01-26: If you have this same problem and are unable to move your controllers to a separate package tree, you can use a little configuration option of Spring’s component scan. You can use include and exclude filters to filter the @Controller annotations out of the application context and only include them in the servlet context. The snippet below filters out the controllers, you should use this in you application context:

<beans ...>
  ...
  <context:component-scan base-package="com.our.app">
    <:context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
  </context:component-scan>
  ...
</beans>

The same with annotations:

@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)})
@Configuration
public class MyConfiguration {}

Then reverse the filter on the servlet-config and you have separated your stereotypes. Note that I believe this is more of a hack than a sound application architecture, but in legacy applications you may not have much of a choice…

–JH

PS: This post talks about @Cacheable, but the same behaviour might be triggered by things like @Transaction and other bean post processing features in a context.

Posted in Java, Java EE, Spring, Tips and Tricks.