Spring MVCを使用したキャッシュ可能な静的アセット
1. 概要
この記事では、Spring MVCで提供する際の静的アセット(JavascriptやCSSファイルなど)のキャッシュに焦点を当てています。
また、「完全なキャッシュ」の概念についても触れます。基本的に、ファイルが更新されたときに、古いバージョンがキャッシュから誤って提供されないようにします。
2. 静的アセットのキャッシュ
静的アセットをキャッシュ可能にするには、対応するリソースハンドラを構成する必要があります。
これを行う方法の簡単な例を次に示します。max-age=31536000への応答にCache-Controlヘッダーを設定すると、ブラウザはキャッシュされたバージョンのファイルを1年間使用します。
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/js/**")
.addResourceLocations("/js/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
}
}
キャッシュの有効期間が非常に長い理由は、ファイルが更新されるまでクライアントにキャッシュされたバージョンのファイルを使用させたいためです。RFC for the Cache-Control headerによると、365日が使用できる最大値です。
And so, when a client requests foo.js for the first timeの場合、彼はネットワーク経由でファイル全体(この場合は37バイト)をステータスコード200 OK.で受信します。応答には、キャッシュ動作を制御するための次のヘッダーがあります。
Cache-Control: max-age=31536000
これにより、次の応答の結果として、ブラウザーは有効期限が1年のファイルをキャッシュします。
When the client requests the same file for the second timeの場合、ブラウザはサーバーに対して別の要求を行いません。 代わりに、キャッシュからファイルを直接提供し、ネットワークの往復を回避するため、ページの読み込みがはるかに高速になります。
Chromeブラウザのユーザーは、画面の更新ボタンを押すかF5キーを押してページを更新すると、Chromeはキャッシュを使用しないため、テスト中は注意する必要があります。 キャッシュ動作を観察するには、アドレスバーでEnterキーを押す必要があります。 その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で提供
URLに更新されたバージョン文字列を含むパスの下のファイルを提供するには、パスにVersionResourceResolverを追加する必要があります。
@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ヘッダーで応答して、クライアントブラウザーに1年間ファイルをキャッシュするように指示します。
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およびJSTLのurlタグを使用して、リンクのURLをバージョン管理されたものに書き換えることができます。
ResourceURLEncodingFilterは、通常どおりweb.xmlの下に登録できます。
resourceUrlEncodingFilter
org.springframework.web.servlet.resource.ResourceUrlEncodingFilter
resourceUrlEncodingFilter
/*
urlタグを使用する前に、JSTLコアタグライブラリをJSPページにインポートする必要があります。
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
次に、urlタグを使用して、次のようにfoo.jsをインポートできます。
">
このJSPページがレンダリングされると、ファイルのURLはバージョンが含まれるように正しく書き換えられます。
3.3. URLのバージョン部分を更新する
ファイルが更新されるたびに、そのバージョンが再度計算され、新しいバージョンを含むURLの下でファイルが提供されます。 このために追加の作業を行う必要はありません。VersionResourceResolverがこれを処理します。
4. CSSリンクを修正する
CSSファイルは、@importディレクティブを使用して他のCSSファイルをインポートできます。 たとえば、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キャッシュを利用すると、Webサイトのパフォーマンスが大幅に向上しますが、キャッシュの使用中に古いリソースを提供しないようにするのは面倒な場合があります。
この記事では、Spring MVCで静的アセットを提供し、ファイルが更新されたときにキャッシュを無効化しながら、HTTPキャッシングを使用する優れた戦略を実装しました。
この記事のソースコードはGitHubにあります。