Перейти к основному содержимому

Кэшируемые статические активы с Spring MVC

· 6 мин. чтения

1. Обзор

В этой статье основное внимание уделяется кэшированию статических ресурсов (таких как файлы Javascript и CSS) при их обслуживании с помощью Spring Boot и Spring MVC.

Мы также коснемся концепции «идеального кэширования», по существу гарантируя, что при обновлении файла старая версия не будет неправильно обслуживаться из кэша.

2. Кэширование статических ресурсов

Чтобы сделать статические ресурсы кэшируемыми, нам нужно настроить соответствующий обработчик ресурсов.

Вот простой пример того, как это сделать — установка заголовка Cache-Control в ответе на max-age=31536000 , что заставляет браузер использовать кешированную версию файла в течение одного года:

@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/js/**")
.addResourceLocations("/js/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
}
}

Причина, по которой у нас такой длительный период действия кэша, заключается в том, что мы хотим, чтобы клиент использовал кэшированную версию файла до тех пор, пока файл не будет обновлен, а 365 дней — это максимум, который мы можем использовать в соответствии с RFC для Cache-Control . заголовок .

И так, когда клиент впервые запрашивает foo.js , он получает по сети весь файл (37 байт в данном случае) с кодом состояния 200 OK. Ответ будет иметь следующий заголовок для управления поведением кэширования:

Cache-Control: max-age=31536000

Это указывает браузеру кэшировать файл с истечением срока годности в результате следующего ответа:

./824ff8b2d7ac18c559cc438c2e3c4c3d.png

Когда клиент запрашивает один и тот же файл во второй раз , браузер не будет делать еще один запрос к серверу. Вместо этого он будет напрямую обслуживать файл из своего кеша и избегать сетевого обмена, поэтому страница будет загружаться намного быстрее:

./196577c89d885df3d80c071030b60b6d.png

Пользователи браузера Chrome должны быть осторожны при тестировании, потому что Chrome не будет использовать кеш, если вы обновите страницу, нажав кнопку обновления на экране или нажав клавишу F5. Вам нужно нажать Enter в адресной строке, чтобы наблюдать за кэшированием. Подробнее об этом здесь .

2.1. Весенний ботинок

Чтобы настроить заголовки Cache-Control в Spring Boot, мы можем использовать свойства в пространстве имен свойств spring.resources.cache.cachecontrol . Например, чтобы изменить максимальный возраст на один год, мы можем добавить следующее в наш application.properties :

spring.resources.cache.cachecontrol.max-age=365d

Это относится ко всем статическим ресурсам , обслуживаемым Spring Boot . Поэтому, если мы просто хотим применить стратегию кэширования к подмножеству запросов, мы должны использовать простой подход Spring MVC.

В дополнение к max-age также можно настроить другие параметры Cache-Control , такие как no-store или no-cache с аналогичными свойствами конфигурации.

3. Управление версиями статических ресурсов

Использование кеша для обслуживания статических ресурсов делает загрузку страницы очень быстрой, но здесь есть важная оговорка. Когда вы обновляете файл, клиент не получит самую последнюю версию файла, поскольку он не проверяет на сервере актуальность файла, а просто выдает файл из кеша браузера.

Вот что нам нужно сделать, чтобы браузер получал файл с сервера только при обновлении файла:

  • Отправьте файл по URL-адресу, в котором есть версия. Например, foo.js следует обслуживать в /js/foo-46944c7e3a9bd20cc30fdc085cae46f2.js.
  • Обновить ссылки на файл с новым URL
  • Обновляйте часть версии URL всякий раз, когда файл обновляется. Например, после обновления foo.js он теперь должен обслуживаться в /js/foo-a3d8d7780349a12d739799e9aa7d2623.js.

Клиент будет запрашивать файл с сервера при его обновлении, потому что страница будет иметь ссылку на другой URL-адрес, поэтому браузер не будет использовать свой кеш. Если файл не обновляется, его версия (следовательно, его URL-адрес) не изменится, и клиент продолжит использовать кеш для этого файла.

Обычно нам нужно было бы делать все это вручную, но Spring поддерживает их из коробки, включая вычисление хэша для каждого файла и добавление их к URL-адресам. Давайте посмотрим, как мы можем настроить наше приложение Spring, чтобы сделать все это за нас.

3.1. Подавать по URL-адресу с версией

Нам нужно добавить VersionResourceResolver к пути, чтобы обслуживать файлы под ним с обновленной строкой версии в его URL-адресе:

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/js/**")
.addResourceLocations("/js/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
.resourceChain(false)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}

Здесь мы используем стратегию версии контента. Каждый файл в папке /js будет обслуживаться по URL-адресу, версия которого вычисляется на основе его содержимого. Это называется снятие отпечатков пальцев. Например, foo.js теперь будет обслуживаться по URL-адресу /js/foo-46944c7e3a9bd20cc30fdc085cae46f2.js.

В этой конфигурации, когда клиент делает запрос на http://localhost:8080/js/ 46944c7e3a9bd20cc30fdc085cae46f2.js:

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

Сервер ответит заголовком Cache-Control, чтобы указать клиентскому браузеру кэшировать файл на год:

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, нам просто нужно использовать несколько конфигураций в пространстве имен свойств spring.resources.chain.strategy.content . Например, мы можем добиться того же результата, что и раньше, добавив следующие конфигурации:

spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**

Подобно конфигурации Java, это позволяет управлять версиями на основе содержимого для всех ресурсов, соответствующих шаблону пути /** .

3.3. Обновить ссылки с новым URL

Прежде чем мы вставили версию в URL-адрес, мы могли использовать простой тег скрипта для импорта foo.js :

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

Теперь, когда мы обслуживаем тот же файл по URL-адресу с версией, нам нужно отразить его на странице:

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

Становится утомительно иметь дело со всеми этими длинными путями. Spring предлагает лучшее решение этой проблемы. Мы можем использовать ResourceUrlEncodingFilter и тег url JSTL для перезаписи URL-адресов ссылок с версионными.

ResourceURLEncodingFilter можно зарегистрировать в web.xml как обычно:

<filter>
<filter-name>resourceUrlEncodingFilter</filter-name>
<filter-class>
org.springframework.web.servlet.resource.ResourceUrlEncodingFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>resourceUrlEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Библиотеку основных тегов JSTL необходимо импортировать на нашу страницу JSP, прежде чем мы сможем использовать тег url :

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

Затем мы можем использовать тег url для импорта foo.js следующим образом:

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

Когда эта JSP-страница отображается, URL-адрес файла переписывается правильно, чтобы содержать в себе версию:

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

3.4. Обновить версию Часть URL

Всякий раз, когда файл обновляется, его версия вычисляется снова, и файл обслуживается по URL-адресу, содержащему новую версию. Нам не нужно делать для этого никакой дополнительной работы, VersionResourceResolver сделает это за нас.

4. Исправьте ссылки CSS

Файлы CSS могут импортировать другие файлы CSS с помощью директив @import . Например, файл myCss.css импортирует другой файл.css :

@import "another.css";

Обычно это вызывает проблемы с версионными статическими активами, потому что браузер делает запрос на другой файл.css , но файл обслуживается по версионному пути, например, other-9556ab93ae179f87b178cfad96a6ab72.css.

Чтобы исправить эту проблему и сделать запрос на правильный путь, нам нужно ввести CssLinkResourceTransformer в конфигурацию обработчика ресурсов:

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

Это изменяет содержимое myCss.css и заменяет оператор импорта следующим:

@import "another-9556ab93ae179f87b178cfad96a6ab72.css";

5. Вывод

Использование HTTP-кеширования значительно повышает производительность веб-сайта, но может быть обременительно избегать обслуживания устаревших ресурсов при использовании кэширования.

В этой статье мы реализовали хорошую стратегию использования кэширования HTTP при обслуживании статических ресурсов с помощью Spring MVC и очистке кеша при обновлении файлов.

Вы можете найти исходный код этой статьи на GitHub .