Ativos estáticos captáveis com Spring MVC
1. Visão geral
Este artigo se concentra no armazenamento em cache de ativos estáticos (como arquivos Javascript e CSS) ao servi-los com o Spring MVC.
Também abordaremos o conceito de "cache perfeito", essencialmente nos certificando de que - quando um arquivo é atualizado - a versão antiga não é servida incorretamente do cache.
2. Cache de ativos estáticos
Para tornar ativos estáticos armazenados em cache, precisamos configurar seu manipulador de recursos correspondente.
Aqui está um exemplo simples de como fazer isso - definir o cabeçalhoCache-Control na resposta paramax-age=31536000, o que faz com que o navegador use a versão em cache do arquivo por um ano:
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/js/**")
.addResourceLocations("/js/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
}
}
A razão de termos um período de tempo tão longo para a validade do cache é que queremos que o cliente use a versão em cache do arquivo até que o arquivo seja atualizado, e 365 dias é o máximo que podemos usar de acordo comRFC for the Cache-Control header.
And so, when a client requests foo.js for the first time, ele receberá todo o arquivo pela rede (37 bytes neste caso) com um código de status de200 OK. A resposta terá o seguinte cabeçalho para controlar o comportamento do cache:
Cache-Control: max-age=31536000
Isso fará com que o navegador armazene em cache o arquivo com uma duração de expiração de um ano, como resultado da seguinte resposta:
When the client requests the same file for the second time, o navegador não fará outra solicitação ao servidor. Em vez disso, ele servirá o arquivo diretamente a partir do cache e evitará a ida e volta da rede, para que a página seja carregada muito mais rapidamente:
Os usuários do navegador Chrome precisam ter cuidado durante o teste, pois o Chrome não usará o cache se você atualizar a página pressionando o botão de atualização na tela ou pressionando a tecla F5. Você precisa pressionar Enter na barra de endereço para observar o comportamento do cache. Mais informações sobrehere.
3. Controle de versão de ativos estáticos
O uso de um cache para atender aos recursos estáticos torna o carregamento da página muito rápido, mas possui uma ressalva importante. Quando você atualiza o arquivo, o cliente não obtém a versão mais recente do arquivo, pois não verifica com o servidor se o arquivo está atualizado e apenas serve o arquivo no cache do navegador.
Aqui está o que precisamos fazer para que o navegador obtenha o arquivo do servidor apenas quando o arquivo for atualizado:
-
Sirva o arquivo em um URL que possui uma versão. Por exemplo,foo.js deve ser servido em/js/foo-46944c7e3a9bd20cc30fdc085cae46f2.js
-
Atualizar links para o arquivo com o novo URL
-
Atualize parte da versão da URL sempre que o arquivo for atualizado. Por exemplo, quandofoo.js é atualizado, agora deve ser servido em/js/foo-a3d8d7780349a12d739799e9aa7d2623.js.
O cliente solicitará o arquivo do servidor quando ele for atualizado porque a página terá um link para um URL diferente, então o navegador não usará seu cache. Se um arquivo não for atualizado, sua versão (portanto, sua URL) não será alterada e o cliente continuará usando o cache para esse arquivo.
Normalmente, precisaríamos fazer tudo isso manualmente, mas o Spring suporta isso imediatamente, incluindo o cálculo do hash para cada arquivo e o anexamento aos URLs. Vamos ver como podemos configurar nosso aplicativo Spring para fazer tudo isso para nós.
3.1. Veicular em um URL com uma versão
Precisamos adicionar umVersionResourceResolver a um caminho para servir os arquivos sob ele com uma string de versão atualizada em seu URL:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/js/**")
.addResourceLocations("/js/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
.resourceChain(false)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
Aqui usamos uma estratégia de versão de conteúdo. Cada arquivo na pasta/js será servido em um URL que possui uma versão calculada a partir de seu conteúdo. Isso é chamado de impressão digital. Por exemplo,foo.js agora será veiculado no URL/js/foo-46944c7e3a9bd20cc30fdc085cae46f2.js.
Com esta configuração, quando um cliente faz uma solicitação dehttp://localhost:8080/js/_46944c7e3a9bd20cc30fdc085cae46f2.js:_
curl -i http://localhost:8080/js/foo-46944c7e3a9bd20cc30fdc085cae46f2.js
O servidor responderá com um cabeçalho de controle de cache para solicitar ao navegador do cliente que armazene em cache o arquivo por um ano:
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. Atualizar links com o novo URL
Antes de inserirmos a versão no URL, poderíamos usar uma tagscript simples para importarfoo.js:
Agora que servimos o mesmo arquivo em um URL com uma versão, precisamos refleti-lo na página:
Torna-se entediante lidar com todos esses caminhos longos. Existe uma solução melhor que o Spring oferece para esse problema. Podemos usarResourceUrlEncodingFiltere tagurl de JSTL para reescrever os URLs dos links com versões.
ResourceURLEncodingFilter pode ser registrado emweb.xml como de costume:
resourceUrlEncodingFilter
org.springframework.web.servlet.resource.ResourceUrlEncodingFilter
resourceUrlEncodingFilter
/*
A biblioteca de tags principais JSTL precisa ser importada em nossa página JSP antes que possamos usar a tagurl:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
Então, podemos usar a tagurl para importarfoo.js da seguinte maneira:
">
Quando esta página JSP é renderizada, a URL do arquivo é reescrita corretamente para conter a versão:
3.3. Atualizar parte da versão do URL
Sempre que um arquivo é atualizado, sua versão é computada novamente e o arquivo é exibido em uma URL que contém a nova versão. Não precisamos fazer nenhum trabalho adicional para isso,VersionResourceResolver cuida disso para nós.
4. Corrigir links CSS
Os arquivos CSS podem importar outros arquivos CSS usando as diretivas@import. Por exemplo, o arquivomyCss.css importa o arquivoanother.css:
@import "another.css";
Isso normalmente causaria problemas com ativos estáticos com versão, porque o navegador fará uma solicitação para o arquivoanother.css, mas o arquivo é servido em um caminho com versão, comoanother-9556ab93ae179f87b178cfad96a6ab72.css.
Para corrigir esse problema e fazer uma solicitação para o caminho correto, precisamos introduzirCssLinkResourceTransformer na configuração do manipulador de recursos:
@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());
}
Isso modifica o conteúdo demyCss.csse troca a declaração de importação com o seguinte:
@import "another-9556ab93ae179f87b178cfad96a6ab72.css";
5. Conclusão
Aproveitar o cache HTTP é um grande impulso para o desempenho do site, mas pode ser complicado evitar fornecer recursos obsoletos ao usar o cache.
Neste artigo, implementamos uma boa estratégia para usar o cache HTTP ao atender ativos estáticos com o Spring MVC e interromper o cache quando os arquivos são atualizados.
Você pode encontrar o código-fonte deste artigo emGitHub.