66

In an annotation-based Spring MVC controller, what is the preferred way to set cache headers for a specific path?

D. Wroblewski
  • 7,530
  • 4
  • 27
  • 30

10 Answers10

67

I just encountered the same problem, and found a good solution already provided by the framework. The org.springframework.web.servlet.mvc.WebContentInterceptor class allows you to define default caching behaviour, plus path-specific overrides (with the same path-matcher behaviour used elsewhere). The steps for me were:

  1. Ensure my instance of org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter does not have the "cacheSeconds" property set.
  2. Add an instance of WebContentInterceptor:

    <mvc:interceptors>
    ...
    <bean class="org.springframework.web.servlet.mvc.WebContentInterceptor" p:cacheSeconds="0" p:alwaysUseFullPath="true" >
        <property name="cacheMappings">
            <props>
                <!-- cache for one month -->
                <prop key="/cache/me/**">2592000</prop>
                <!-- don't set cache headers -->
                <prop key="/cache/agnostic/**">-1</prop>
            </props>
        </property>
    </bean>
    ...
    </mvc:interceptors>
    

After these changes, responses under /foo included headers to discourage caching, responses under /cache/me included headers to encourage caching, and responses under /cache/agnostic included no cache-related headers.


If using a pure Java configuration:

@EnableWebMvc
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
  /* Time, in seconds, to have the browser cache static resources (one week). */
  private static final int BROWSER_CACHE_CONTROL = 604800;

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry
     .addResourceHandler("/images/**")
     .addResourceLocations("/images/")
     .setCachePeriod(BROWSER_CACHE_CONTROL);
  }
}

See also: http://docs.spring.io/spring-security/site/docs/current/reference/html/headers.html

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Eric R. Rath
  • 1,939
  • 1
  • 14
  • 16
  • 1
    I tried to use this for images stored in the DB. I can easily retrieve them. They show up, but FireBug always keeps telling me status "200 Ok", so no caching. Any ideas? – Czar Jan 31 '11 at 12:56
  • 1
    This won't work because I don't have a prefix of mvc, or p. What are they supposed to be? – Jesse Jashinsky Nov 16 '12 at 17:48
  • Jesse - check the root element of your context def file; you should have a namespace element like 'xmlns:mvc="http://www.springframework.org/schema/mvc"', and another attribute named 'xsi:schemaLocation' with a value that includes 'http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd'. See http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/validation.html#format-configuring-FormattingConversionService for an example of a typical XML config. – Eric R. Rath Nov 26 '12 at 06:23
  • Can you tell which xml namespace p: refers to ? – BiAiB Jan 21 '14 at 13:17
  • 1
    You should only need to add `xmlns:p="http://www.springframework.org/schema/p"` as long as you're using the 3.1 xsd. – Eric R. Rath Jan 21 '14 at 16:58
  • This setup is not working for me. I did not do the thing I explicity mentioned a 2345 – user590849 Jan 29 '16 at 18:30
33

The answer is quite simple:

@Controller
public class EmployeeController {
@RequestMapping(value = "/find/employer/{employerId}", method = RequestMethod.GET) public List getEmployees(@PathVariable("employerId") Long employerId, final HttpServletResponse response) { response.setHeader("Cache-Control", "no-cache"); return employeeService.findEmployeesForEmployer(employerId); }
}
Code above shows exactly what you want to achive. You have to do two things. Add "final HttpServletResponse response" as your parameter. And then set header Cache-Control to no-cache.
Community
  • 1
  • 1
goroncy
  • 2,053
  • 1
  • 19
  • 16
  • 10
    this would have to be added to every request. not a good solution for adding the header to all requests across the board. – yincrash Jul 28 '10 at 19:15
  • @JesseJ What part didn't work? I tried and it works for me. – Ryan Jul 09 '15 at 23:53
  • @yincrash, quoting OP: _what is the preferred way to set cache headers for a **specific path**_ – Klesun Jul 28 '20 at 13:53
20

org.springframework.web.servlet.support.WebContentGenerator, which is the base class for all Spring controllers has quite a few methods dealing with cache headers:

/* Set whether to use the HTTP 1.1 cache-control header. Default is "true".
 * <p>Note: Cache headers will only get applied if caching is enabled
 * (or explicitly prevented) for the current request. */
public final void setUseCacheControlHeader();

/* Return whether the HTTP 1.1 cache-control header is used. */
public final boolean isUseCacheControlHeader();

/* Set whether to use the HTTP 1.1 cache-control header value "no-store"
 * when preventing caching. Default is "true". */
public final void setUseCacheControlNoStore(boolean useCacheControlNoStore);

/* Cache content for the given number of seconds. Default is -1,
 * indicating no generation of cache-related headers.
 * Only if this is set to 0 (no cache) or a positive value (cache for
 * this many seconds) will this class generate cache headers.
 * The headers can be overwritten by subclasses, before content is generated. */
public final void setCacheSeconds(int seconds);

They can either be invoked within your controller prior to content generation or specified as bean properties in Spring context.

ChssPly76
  • 99,456
  • 24
  • 206
  • 195
  • 3
    But, as I wrote in my question, I use an annotation-based controller that doesn't subclass from any spring base class. How will this help me? – D. Wroblewski Sep 01 '09 at 20:07
  • 4
    If you want to alter cache settings depending on **specific path**, extending `AbstractController` is by far the easiest solution. If you want to apply your cache settings to all controllers, you can specify them on `AnnotationMethodHandlerAdapter` instance in Spring context for annotation-based controllers. Here's an example: http://static.springsource.org/spring/docs/2.5.6/reference/mvc.html#mvc-ann-initbinder (disregard the init binder, you don't need it) – ChssPly76 Sep 01 '09 at 21:06
  • 6
    Because my question concerned how to do this with an annotations-based controller. Your solution only applies to controllers that subclass WebContentGenerator. Isn't it so? – D. Wroblewski Sep 07 '09 at 10:13
  • I've adressed that in my above comment. If you think you'll find a better solution I wish you luck. – ChssPly76 Sep 07 '09 at 17:33
  • 1
    that's why I up-voted your comment. Why didn't you edit your answer instead? – D. Wroblewski Sep 07 '09 at 19:49
  • To clarify, AnnotationMethodHandlerAdapter does extend WebContentGenerator which gives you all the cache header options. Remember, you need to set or a positive value for any of the cache headers to show up. – yincrash Jul 28 '10 at 20:23
19

Starting with Spring 4.2 you can do this:

import org.springframework.http.CacheControl;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
public class CachingController {
    @RequestMapping(method = RequestMethod.GET, path = "/cachedapi")
    public ResponseEntity<MyDto> getPermissions() {

        MyDto body = new MyDto();

        return ResponseEntity.ok()
            .cacheControl(CacheControl.maxAge(20, TimeUnit.SECONDS))
            .body(body);
    }
}

CacheControl object is a builder with many configuration options, see JavaDoc

Gian Marco
  • 22,140
  • 8
  • 55
  • 44
11

You could use a Handler Interceptor and use the postHandle method provided by it:

http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/web/servlet/HandlerInterceptor.html

postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 

then just add a header as follows in the method:

response.setHeader("Cache-Control", "no-cache");
Jonathan Holloway
  • 62,090
  • 32
  • 125
  • 150
5

I found WebContentInterceptor to be the easiest way to go.

@Override
public void addInterceptors(InterceptorRegistry registry)
{
    WebContentInterceptor interceptor = new WebContentInterceptor();
    interceptor.addCacheMapping(CacheControl.noCache(), "/users", "admin");
    registry.addInterceptor(interceptor);
}
Liang Zhou
  • 2,055
  • 19
  • 20
4

you can define a anotation for this: @CacheControl(isPublic = true, maxAge = 300, sMaxAge = 300), then render this anotation to HTTP Header with Spring MVC interceptor. or do it dynamic:

int age = calculateLeftTiming();
String cacheControlValue = CacheControlHeader.newBuilder()
      .setCacheType(CacheType.PUBLIC)
      .setMaxAge(age)
      .setsMaxAge(age).build().stringValue();
if (StringUtils.isNotBlank(cacheControlValue)) {
    response.addHeader("Cache-Control", cacheControlValue);
}

Implication can be found here: 优雅的Builder模式

BTW: I just found that Spring MVC has build-in support for cache control: Google WebContentInterceptor or CacheControlHandlerInterceptor or CacheControl, you will find it.

arganzheng
  • 1,294
  • 15
  • 20
3

I know this is a really old one, but those who are googling, this might help:

@Override
protected void addInterceptors(InterceptorRegistry registry) {

    WebContentInterceptor interceptor = new WebContentInterceptor();

    Properties mappings = new Properties();
    mappings.put("/", "2592000");
    mappings.put("/admin", "-1");
    interceptor.setCacheMappings(mappings);

    registry.addInterceptor(interceptor);
}
Vitali Carbivnicii
  • 281
  • 1
  • 5
  • 12
2

In your controller, you can set response headers directly.

response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
hakunami
  • 2,351
  • 4
  • 31
  • 50
1

You could extend AnnotationMethodHandlerAdapter to look for a custom cache control annotation and set the http headers accordingly.