Preventing Browsers from Caching Resources with Spring 3...Sort of...

Developing Web apps can be very frustrating sometimes since there are so many different browsers and environments that you need to consider just to get simple stuff working at times.  Not too long ago I had to tell a client to hold down the control key and hit F5 to force her browser to fetch a new file from the server instead of from her browser's cache.  She wasn't seeing the UI changes I made, but she was seeing the content. It looked horrible. That's terribly embarrassing to say to a client and entirely preventable. With Spring, you can use a WebContentInterceptor to modify properties of the request to help govern how a browser will handle resources that don't change very often. Here's a snippet from a config file that I've used:

<mvc:interceptors>
 <bean id="webContentInterceptor" class="org.springframework.web.servlet.mvc.WebContentInterceptor">
 <property name="cacheSeconds" value="0"/>
 <property name="useExpiresHeader" value="true"/>
 <property name="useCacheControlHeader" value="true"/>
 <property name="useCacheControlNoStore" value="true"/>
 </bean>
</mvc:interceptors>

Fairly straight forward. I'm saying to browser, don't cache stuff.  In an actual production system, you'll want to be a little more specific about what you want cached and not cached. In development though, it's nice to never cache.

The problem with this is the browser can still do whatever it wants. So how do we control the browser? We don't. Instead, we spoof the location of the resource and trick the browser into thinking it's a brand new file - obviously not cached. This is the recommended technique and Spring handles it quite nicely. Check it out.

We use a resource mapping.

<mvc:resources location="/static,classpath:/META-INF/" mapping="/static-2.1.0/**"/>

This configuration says, for all requests that come in that have the URL pattern /static-app-version followed by anything, look for the resource in folder named static in the web root or in a jar on the classpath within its META-INF directory.

Now, a web browser that requests a resource called /static-2.1.0/css/app.css will serve the app.css file from the same old location but since the browser doesn't know that, it must download the app.css file regardless of whether or not a previous version was cached.

Cool.

We can improve upon this by making the version dynamic so you don't need to modify your Spring config file each time you deploy.

<mvc:resources location="/static,classpath:/META-INF/" mapping="/static-${application.version}/**"/>

You can add a variable to a properties file and read that file into your Spring config file. Then you'll be able to use the variable in a SpEL expression.

<util:properties id='appProperties' location='classpath:default.properties'/>
<context:property-placeholder properties-ref="appProperties"/>

Your default.properties file might look like this.

application.version=2.1.0

Just update this file on each deploy and all your static resources will be forced to be loaded from browsers.

You have much more control over what resources should be cached and the expiration of those caches than I've talked about here. For more information, I encourage you to explore the Spring documentation which talks about this very technique along with several other cache control strategies.

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-interceptors