Cachable Static Assets with Spring MVC – 使用Spring MVC的可缓存静态资产

最后修改: 2016年 8月 27日


1. Overview


This article focuses on caching static assets (such as Javascript and CSS files) when serving them with Spring Boot and Spring MVC.

本文主要讨论在使用Spring Boot和Spring MVC提供静态资产(如Javascript和CSS文件)时的缓存。

We’ll also touch on the concept of “perfect caching”, essentially making sure that – when a file is updated – the old version isn’t incorrectly served from the cache.

我们还将触及 “完美缓存 “的概念,本质上是确保–当一个文件被更新时–旧版本不会被错误地从缓存中提供。

2. Caching Static Assets


In order to make static assets cacheable, we need to configure its corresponding resource handler.


Here’s a simple example of how to do that – setting the Cache-Control header on the response to max-age=31536000 which causes the browser to use the cached version of the file for one year:


public class MvcConfig implements WebMvcConfigurer {
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
                .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));

The reason we have such a long time period for cache validity is that we want the client to use the cached version of the file until the file is updated, and 365 days is the maximum we can use according to the RFC for the Cache-Control header.


And so, when a client requests foo.js for the first time, he will receive the whole file over the network (37 bytes in this case) with a status code of 200 OK. The response will have the following header to control the caching behavior:

因此,当客户端第一次请求foo.js,他将通过网络收到整个文件(本例中为37字节),状态代码为200 OK。响应将有以下头,以控制缓存行为。

Cache-Control: max-age=31536000

This instructs the browser to cache the file with an expiration duration of a year, as a result of the following response:



When the client requests the same file for the second time, the browser will not make another request to the server. Instead, it will directly serve the file from its cache and avoid the network round-trip so the page will load much faster:



Chrome browser users need to be careful while testing because Chrome will not use the cache if you refresh the page by pressing the refresh button on the screen or by pressing F5 key. You need to press enter on the address bar to observe the caching behavior. More info on that here.


2.1. Spring Boot


To customize the Cache-Control headers in Spring Boot, we can use properties under the spring.resources.cache.cachecontrol property namespace. For example, to change the max-age to one year, we can add the following to our

要自定义Spring Boot中的Cache-Control头文件,我们可以使用spring.resources.cache.cachecontrol属性命名空间下的属性。例如,为了将最大年龄改为一年,我们可以在我们的application.properties中添加以下内容。


This applies to all static resources served by Spring Boot. Therefore, if we just want to apply a caching strategy to a subset of requests, we should use the plain Spring MVC approach.

这适用于由Spring Boot提供的所有静态资源。因此,如果我们只是想将缓存策略应用于一个请求子集,我们应该使用普通的Spring MVC方法。

In addition to max-age, it’s also possible to customize other Cache-Control parameters such as no-store or no-cache with similar configuration properties.


3. Versioning Static Assets


Using a cache for serving the static assets makes the page load really fast, but it has an important caveat. When you update the file, the client will not get the most recent version of the file since it does not check with the server if the file is up-to-date and just serves the file from the browser cache.


Here’s what we need to do to make the browser get the file from the server only when the file is updated:


  • Serve the file under a URL that has a version in it. For example, foo.js should be served under /js/foo-46944c7e3a9bd20cc30fdc085cae46f2.js
  • Update links to the file with the new URL
  • Update version part of the URL whenever the file is updated. For example, when foo.js is updated, it should now be served under /js/foo-a3d8d7780349a12d739799e9aa7d2623.js.

The client will request the file from the server when it’s updated because the page will have a link to a different URL, so the browser will not use its cache. If a file is not updated, its version (hence its URL) will not change and the client will keep using the cache for that file.


Normally, we would need to do all of these manually, but Spring supports these out of the box, including calculating the hash for each file and appending them to the URLs. Let’s see how we can configure our Spring application to do all of this for us.


3.1. Serve Under a URL With a Version


We need to add a VersionResourceResolver to a path in order to serve the files under it with an updated version string in its URL:


public void addResourceHandlers(ResourceHandlerRegistry registry) {
            .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
            .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));

Here we use a content version strategy. Each file in the /js folder will be served under a URL that has a version computed from its content. This is called fingerprinting. For example, foo.js will now be served under the URL /js/foo-46944c7e3a9bd20cc30fdc085cae46f2.js.


With this configuration, when a client makes a request for http://localhost:8080/js/46944c7e3a9bd20cc30fdc085cae46f2.js:


curl -i http://localhost:8080/js/foo-46944c7e3a9bd20cc30fdc085cae46f2.js

The server will respond with a Cache-Control header to tell the client browser to cache the file for a year:


HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Last-Modified: Tue, 09 Aug 2016 06:43:26 GMT
Cache-Control: max-age=31536000

3.2. Spring Boot

3.2 Spring Boot

To enable the same content-based versioning in Spring Boot, we just have to use a few configurations under the spring.resources.chain.strategy.content property namespace. For example, we can achieve the same result as before by adding the following configurations:

要在Spring Boot中启用同样的基于内容的版本管理,我们只需在spring.resources.chain.strategy.content属性命名空间下使用一些配置。例如,我们可以通过添加以下配置来实现与之前相同的结果。


Similar to the Java configuration, this enables the content-based versioning for all assets matching with the /** path pattern.

与Java配置类似,这使得所有与/** 路径模式相匹配的资产都可以进行基于内容的版本管理。

3.3. Update Links With the New URL


Before we inserted version into the URL, we could use a simple script tag to import foo.js:


<script type="text/javascript" src="/js/foo.js">

Now that we serve the same file under a URL with a version, we need to reflect it on the page:


<script type="text/javascript" 

It becomes tedious to deal with all those long paths. There’s a better solution that Spring provides for this problem. We can use ResourceUrlEncodingFilter and JSTL’s url tag for rewriting the URLs of the links with versioned ones.


ResourceURLEncodingFilter can be registered under web.xml as usual:



JSTL core tag library needs to be imported on our JSP page before we can use the url tag:


<%@ taglib uri="" prefix="c" %>

Then, we can use the url tag to import foo.js as follows:


<script type="text/javascript" src="<c:url value="/js/foo.js" />">

When this JSP page is rendered, the URL for the file is rewritten correctly to contain the version in it:


<script type="text/javascript" src="/js/foo-46944c7e3a9bd20cc30fdc085cae46f2.js">

3.4. Update Version Part of the URL


Whenever a file is updated, its version is computed again and the file is served under a URL that contains the new version. We don’t have to do any additional work for this, VersionResourceResolver handles this for us.


4. Fix CSS Links


CSS files can import other CSS files by using @import directives. For example, myCss.css file imports another.css file:


@import "another.css";

This would normally cause problems with versioned static assets because the browser will make a request for another.css file, but the file is served under a versioned path such as another-9556ab93ae179f87b178cfad96a6ab72.css.


To fix this problem and to make a request to the correct path, we need to introduce CssLinkResourceTransformer to the resource handler configuration:


public void addResourceHandlers(ResourceHandlerRegistry registry) {
            .addResourceLocations("/resources/", "classpath:/other-resources/")
            .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
            .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
            .addTransformer(new CssLinkResourceTransformer());

This modifies the content of myCss.css and swaps the import statement with the following:


@import "another-9556ab93ae179f87b178cfad96a6ab72.css";

5. Conclusion


Taking advantage of HTTP caching is a huge boost to web site performance, but it might be cumbersome to avoid serving stale resources while using caching.


In this article, we have implemented a good strategy to use HTTP caching while serving static assets with Spring MVC and busting the cache when the files are updated.

在这篇文章中,我们实现了一个很好的策略,在用Spring MVC服务静态资产时使用HTTP缓存,并在文件更新时破坏缓存。

You can find the source code for this article on GitHub.