1. 概要

この記事では、静的アセット(JavascriptやCSSファイルなど)をSpringBootおよびSpringMVCで提供する際のキャッシュに焦点を当てています。

また、「完全なキャッシュ」の概念についても触れます。基本的に、ファイルが更新されたときに、古いバージョンがキャッシュから誤って提供されないようにします。

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によると、365日が使用できる最大値です。キャッシュ制御ヘッダー

したがって、クライアントが初めてfoo.jsを要求したとき 、彼はネットワーク経由でファイル全体(この場合は37バイト)をステータスコードで受信します 200OK。 応答には、キャッシュ動作を制御するための次のヘッダーがあります。

Cache-Control: max-age=31536000

これは、次の応答の結果として、有効期限が1年のファイルをキャッシュするようにブラウザに指示します。

クライアントが2回目に同じファイルを要求した場合、ブラウザはサーバーに対して別の要求を行いません。 代わりに、キャッシュからファイルを直接提供し、ネットワークのラウンドトリップを回避するため、ページの読み込みがはるかに速くなります。

画面の更新ボタンを押すか、F5キーを押してページを更新すると、Chromeはキャッシュを使用しないため、Chromeブラウザのユーザーはテスト中に注意する必要があります。 キャッシュの動作を確認するには、アドレスバーのEnterキーを押す必要があります。 そのここの詳細。

2.1. スプリングブーツ

SpringBootのCache-Controlヘッダーをカスタマイズするには、spring.resources.cache.cachecontrolプロパティ名前空間の下のプロパティを使用できます。 たとえば、 max-age を1年に変更するには、application.propertiesに次を追加します。

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

これは、SpringBootによって提供されるすべての静的リソースに適用されます。 したがって、リクエストのサブセットにキャッシング戦略を適用したいだけの場合は、プレーンなSpringMVCアプローチを使用する必要があります。

max-ageに加えて、同様の構成プロパティでno-storeやno-cacheなどの他のCache-Controlパラメーターをカスタマイズすることもできます。

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ヘッダーで応答し、クライアントブラウザーにファイルを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. スプリングブーツ

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にバージョンを挿入する前に、単純なscriptタグを使用してfoo.jsをインポートできます。

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

バージョンのあるURLで同じファイルを提供するようになったので、それをページに反映する必要があります。

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

これらすべての長い道のりに対処するのは面倒になります。 Springがこの問題に対して提供するより良い解決策があります。 ResourceUrlEncodingFilterおよびJSTLのurlタグを使用して、リンクの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>

url タグを使用する前に、JSTLコアタグライブラリをJSPページにインポートする必要があります。

<%@ 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ファイルは、@importディレクティブを使用して他のCSSファイルをインポートできます。 たとえば、myCss.cssファイルはanother.cssファイルをインポートします。

@import "another.css";

これは通常、バージョン管理された静的アセットで問題を引き起こします。これは、ブラウザが another.css ファイルですが、ファイルは次のようなバージョン管理されたパスで提供されます別の-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にあります。