Кэшируемые статические активы с помощью Spring MVC
1. обзор
В этой статье основное внимание уделяется кэшированию статических ресурсов (таких как файлы Javascript и CSS) при их обслуживании в 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 for the Cache-Control header.
And so, when a client requests foo.js for the first time, он получит весь файл по сети (в данном случае 37 байт) с кодом состояния200 OK.. Ответ будет иметь следующий заголовок для управления поведением кэширования:
Cache-Control: max-age=31536000
Это приведет к тому, что браузер кеширует файл со сроком годности в результате следующего ответа:
When the client requests the same file for the second time, браузер не будет делать другого запроса к серверу. Вместо этого он будет напрямую обслуживать файл из своего кэша и избегать обхода по сети, поэтому страница будет загружаться намного быстрее:
Пользователи браузера Chrome должны быть осторожны при тестировании, потому что Chrome не будет использовать кеш, если вы обновляете страницу, нажимая кнопку обновления на экране или клавишу F5. Вам нужно нажать ввод в адресной строке, чтобы наблюдать за поведением кэширования. Подробнее об этомhere.
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. Обновите ссылки с новым URL
Прежде чем мы вставили версию в URL-адрес, мы могли использовать простой тегscript для импортаfoo.js:
Теперь, когда мы предоставляем один и тот же файл под URL-адресом с версией, нам нужно отразить его на странице:
Становится утомительным иметь дело со всеми этими длинными путями. Spring предлагает лучшее решение этой проблемы. Мы можем использоватьResourceUrlEncodingFilter и тег JSTLurl для перезаписи URL-адресов ссылок с версионными.
ResourceURLEncodingFilter можно зарегистрировать вweb.xml как обычно:
resourceUrlEncodingFilter
org.springframework.web.servlet.resource.ResourceUrlEncodingFilter
resourceUrlEncodingFilter
/*
Библиотеку основных тегов JSTL необходимо импортировать на нашу страницу JSP, прежде чем мы сможем использовать тегurl:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
Затем мы можем использовать тегurl для импортаfoo.js следующим образом:
">
Когда рендерится эта страница JSP, URL-адрес файла корректно переписывается, чтобы в нем содержалась версия:
3.3. Обновить версию Часть URL-адреса
Всякий раз, когда файл обновляется, его версия вычисляется снова, и файл обслуживается по URL-адресу, который содержит новую версию. Для этого нам не нужно выполнять никакой дополнительной работы,VersionResourceResolver делает это за нас.
4. Исправить ссылки CSS
Файлы CSS могут импортировать другие файлы CSS с помощью директив@import. Например, файлmyCss.css импортирует файлanother.css:
@import "another.css";
Обычно это вызывает проблемы с версионными статическими активами, потому что браузер будет запрашивать файлanother.css, но файл обслуживается по версированному пути, напримерanother-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.