HTTP-кэширование может значительно повысить производительность веб-приложения. HTTP-кэширование строится вокруг заголовка ответа Cache-Control и, впоследствии, условных заголовков запроса (таких как Last-Modified и ETag). Cache-Control указывает приватным (например, браузеру) и публичным (например, прокси) кэшам, как кэшировать и повторно использовать ответы. Заголовок ETag используется для условного запроса, который может привести к ответу 304 (NOT_MODIFIED) без тела, если содержимое не изменилось. ETag можно считать более совершенным преемником заголовка Last-Modified.

В этом разделе описаны опции, связанные с HTTP-кэшированием, которые доступны в Spring Web MVC.

CacheControl

CacheControl предоставляет средства поддержки для конфигурирования параметров, связанных с заголовком Cache-Control, и принимается в качестве аргумента в ряде мест:

В то время как RFC 7234 описывает все возможные директивы для заголовка ответа Cache-Control, тип CacheControl использует подход, ориентированный на конкретные случаи использования, который фокусируется на общих сценариях:

Java
// Кэширование в течение часа - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);
// Предотвращение кэширования - "Cache-Control: no-store".
CacheControl ccNoStore = CacheControl.noStore();
// Кэширование в течение десяти дней в публичных и приватных кэшах,
// публичный кэш не должен преобразовывать ответ
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
Kotlin
// Кэширование в течение часа - "Cache-Control: max-age=3600"
val ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS)
// Предотвращение кэширования - "Cache-Control: no-store".
val ccNoStore = CacheControl.noStore()
// Кэширование в течение десяти дней в публичных и приватных кэшах,
// публичный кэш не должен преобразовывать ответ
// "Cache-Control: max-age=864000, public, no-transform"
val ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic()

WebContentGenerator также принимает более простое свойство cachePeriod (определенное в секундах), которое работает следующим образом:

  • Значение -1 не генерирует заголовок ответа Cache-Control.

  • Значение 0 предотвращает кэширование при помощи директивы "Cache-Control: no-store".

  • При значении n > 0 предоставленный ответ кэшируется на n секунд при помощи директивы "Cache-Control: max-age=n".

Контроллеры

Контроллеры могут добавлять явную поддержку HTTP-кэширования. Мы рекомендуем делать это, поскольку значение lastModified или ETag для ресурса нужно вычислять, прежде чем его можно будет сравнить с условными заголовками запроса. Контроллер может добавлять заголовок ETag и параметры Cache-Control к ResponseEntity, как показано в следующем примере:

Java
@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {
    Book book = findBook(id);
    String version = book.getVersion();
    return ResponseEntity
            .ok()
            .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
            .eTag(version) // lastModified is also available
            .body(book);
}
Kotlin
@GetMapping("/book/{id}")
fun showBook(@PathVariable id: Long): ResponseEntity<Book> {
    val book = findBook(id);
    val version = book.getVersion()
    return ResponseEntity
            .ok()
            .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
            .eTag(version) // lastModified is also available
            .body(book)
}

В предыдущем примере ответ 304 (NOT_MODIFIED) отправляется с пустым телом, если сравнение с условными заголовками запроса показывает, что содержимое не изменилось. В противном случае в ответ добавляются заголовки ETag и Cache-Control.

Вы также можете осуществить проверку на условные заголовки запроса в контроллере, как показано в следующем примере:

Java
@RequestMapping
public String myHandleMethod(WebRequest request, Model model) {
    long eTag = ... 
    if (request.checkNotModified(eTag)) {
        return null; 
    }
    model.addAttribute(...); 
    return "myViewName";
}
  1. Вычисление для конкретного приложения.
  2. Ответ был установлен на 304 (NOT_MODIFIED) – дальнейшая обработка запрещена.
  3. Продолжаем обработку запроса.
Kotlin
@RequestMapping
fun myHandleMethod(request: WebRequest, model: Model): String? {
    val eTag: Long = ... 
    if (request.checkNotModified(eTag)) {
        return null 
    }
    model[...] = ... 
    return "myViewName"
}
  1. Вычисление для конкретного приложения.
  2. Ответ был установлен на 304 (NOT_MODIFIED) – дальнейшая обработка запрещена.
  3. Продолжаем обработку запроса.

Существует три варианта проверки условных запросов на соответствие значениям eTag, значениям lastModified или обоим. Для условных запросов GET и HEAD можно установить ответ на 304 (NOT_MODIFIED). Для условных POST, PUT и DELETE вы можете вместо этого установить ответ на 412 (PRECONDITION_FAILED), чтобы предотвратить одновременную модификацию.

Статические ресурсы

Для оптимальной производительности статические ресурсы следует обрабатывать с использованием Cache-Control и условных заголовков ответа.

Фильтр ETag

ShallowEtagHeaderFilter можно использовать для добавления "поверхностных" значений eTag, которые вычисляются из содержимого ответа и, таким образом, экономят пропускную способность, но не процессорное время.