今回は、Spring MVC上で静的リソース(HTML、JavaScript、CSS、画像など)にアクセスする方法について説明します。ここでは、「Jakarta EE(Java EE)のアプリケーションサーバーの機能を使用してアクセスする方法」と「Spring MVCの独自機能を使用してアクセスする方法」について説明します。また、最後にSpring Bootアプリでアクセスする方法についても紹介します。
動作検証バージョン
- Spring Framework 5.3.6
(4.2.5.RELEASE) - Spring Boot 2.4.5
(1.3.3.RELEASE) - Tomcat 9.0.45
Note:
[2021/5/5]
投稿から5年くらいたっても一定のViewが継続してあるので、最新のSpring(Spring Boot)バージョンの内容に更新しました。今回の更新では、web.xml
は使わずにJava Configを使うサンプルを修正しています。また検証コードをGitHubで公開するようにしました。
検証アプリケーション
GitHub上で公開するようにしました。
アプリケーションサーバーの機能を使用した静的リソースへのアクセス
まずは、Jakarta EE(Java EE)準拠のWebアプリケーションでどのように静的リソースを扱うか説明(おさらい)しておきましょう。Jakarta EE(Java EE)準拠のWebアプリケーションでは、静的リソースはドキュメントルート上の任意のディレクトリに格納します。 ドキュメントルートは、MavenやGradleプロジェクトではあればsrc/main/webapp
になります。
- Webアプリケーションのドキュメントルート上への格納例
Project Root
└─src
└─ main
└─ webapp # Webアプリケーションのドキュメントルート
└─ static
└─ css
└─ app.css
例えば、上記のようなディレクトリにCSSファイルを格納した場合は、http://localhost:8080/(context-path/)static/css/app.css
というパスでアクセスすることができます。
使用するアプリケーションサーバーがTomcatの場合は、org.apache.catalina.servlets.DefaultServlet
というサーブレットを介してWebアプリケーション内の静的リソースにアクセスします。Tomcat以外の主要なアプリケーションサーバーでも類似のサーブレットが用意されており、これらのサーブレットのことを「デフォルトサーブレット」と呼びます。なお、「デフォルトサーブレット」はルートパス(「/
」)にマッピングされています。
Note: Servlet 3.0+ でのドキュメントルート
Servlet 3.0より、warファイル内のjarファイルの中にある「META-INF/resources
」ディレクトリもWebアプリケーションのドキュメントルートとして扱われるようになっています。warファイル └─ WEB-INF └─ lib └─ foo.jar └─ META-INF └─ resources # Webアプリケーションのドキュメントルート └─ static └─ css └─ foo.css
例えば、上記のようなディレクトリにCSSファイルを格納した場合は、
http://localhost:8080/(context-path/)static/css/foo.css
というURLでアクセスすることができます。
Spring MVCのDispatcherServletとデフォルトサーブレットの関係
Spring MVCでは、org.springframework.web.servlet.DispatcherServlet
で全てのリクエストを受け、リクエスト内容に対応するHandler(ControllerのHandlerメソッド)にディスパッチする仕組みになっており、多くのSpring MVCアプリケーションでDispatcherServlet
をルートパスにマッピングしています。
package com.example.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MyDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{AppConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebMvcConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"}; // ルートパスにマッピング
}
}
package com.example.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@EnableWebMvc // Spring MVCの機能を有効化
public class WebMvcConfig {
}
DispatcherServlet
をルートパスにマッピングすると、アプリケーションサーバーが提供している「デフォルトサーブレット」が呼び出されなくなり、その結果として、Webアプリケーションのドキュメントルート上から静的リソースを取得できなくなります。
DispatcherServletとデフォルトサーブレットの併用
DispatcherServlet
をルートパスにマッピングしつつ、デフォルトサーブレット経由でドキュメントルート上の静的リソースにアクセスすることはできないのでしょうか?
(もちろん)できます!! DispatcherServlet
とデフォルトサーブレットを併用したい場合は、DispatcherServlet
で受けたリクエストをデフォルトサーブレットにフォワードする機能(org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler
)を有効にしてください。
package com.example.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc // Spring MVCの機能を有効化 (注意:Spring Bootの場合は、@EnableWebMvcはつけちゃダメ!!)
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable(); // デフォルトサーブレットへの転送機能を有効化
}
}
DispatcherServlet
で受けたリクエストをデフォルトサーブレットにフォワードする機能を有効にすると、以下のような流れで静的リソースにアクセスすることができます。
% curl -D - http://localhost:8080/pure-spring-demo/static/css/app.css
HTTP/1.1 200
Accept-Ranges: bytes
ETag: W/"37-1620131018000"
Last-Modified: Tue, 04 May 2021 12:23:38 GMT
Content-Type: text/css
Content-Length: 37
Date: Tue, 04 May 2021 13:37:19 GMT
body {
background-color: black;
}
% curl -D - http://localhost:8080/pure-spring-demo/static/css/foo.css
HTTP/1.1 200
Accept-Ranges: bytes
ETag: W/"35-1620135326000"
Last-Modified: Tue, 04 May 2021 13:35:26 GMT
Content-Type: text/css
Content-Length: 35
Date: Tue, 04 May 2021 13:37:24 GMT
body {
background-color: red;
}
Warning: Spring BootでWebMvcConfigurerを利用する際の注意点
Spring Bootで
WebMvcConfigurer
の実装クラスを作成する場合は、@EnableWebMvc
は絶対につけないでください。@EnableWebMvc
をつけてしまうと、Spring BootのAutoConfigureのコンフィギュレーションが一部無効になってしまいます。これはSpring Bootのリファレンスにも記述されています。
Spring MVCの独自機能を利用した静的リソースへのアクセス
ここまではアプリケーションサーバーの機能を使用して静的リソースへアクセスする方法を紹介しましたが、ここからは、Spring MVCが独自に提供する機能を使用して静的リソースへアクセスする方法を紹介していきます。
Spring MVCは、静的リソースにアクセスするためのHandlerとしてorg.springframework.web.servlet.resource.ResourceHttpRequestHandler
というクラスを提供しています。ResourceHttpRequestHandler
を利用すると、
-
DispatcherServlet
を経由での静的リソースへのアクセス - リソース毎(リソースのパターン毎)のキャッシュ制御
- クラスパスや任意のディレクトリに格納されているファイルへのアクセス
- バージョン付きのパス経由での静的リソースへのアクセス
- Gzipなどで圧縮された静的リソースへのアクセス
- WebJar内の静的リソースへのアクセス
などを実現することができます。
以下は、クラスパス上のファイルを静的リソースとして扱う場合のBean定義例です。
@Configuration
@EnableWebMvc // 注意:Spring Bootの場合は、@EnableWebMvcはつけちゃダメ!!
public class WebMvcConfig implements WebMvcConfigurer {
// ...(省略)...
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 「/resources/**」配下へのアクセスを受けた際にはクラスパス配下の「/static/**」から該当リソースを見つける
registry.addResourceHandler("/resources/**").addResourceLocations("classpath:/static/");
}
}
Project Root
└─src
└─ main
└─ resources # クラスパス
└─ static
└─ css
└─ bar.css
例えば、上記のようなディレクトリにCSSファイルを格納した場合は、http://localhost:8080/(context-path/)resources/css/bar.css
というパスでアクセスすることができ、以下のようなレスポンスヘッダーが設定されます。
% curl -D - http://localhost:8080/pure-spring-demo/resources/css/bar.css
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Last-Modified: Tue, 04 May 2021 13:35:28 GMT
Accept-Ranges: bytes
Content-Type: text/css
Content-Length: 36
Date: Tue, 04 May 2021 13:37:29 GMT
body {
background-color: blue;
}
キャッシュの有効期間の設定
デフォルトの動作では、キャッシュの有効期間は設定されないため、キャッシュに関する動作はブラウザの仕様に依存します。キャッシュの有効期間を設定したい場合は、明示的に有効期間を指定してください。有効期間を指定すると、Cache-Control
ヘッダーのmax-age
属性に値が設定されます。
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(604800); // 有効期間(秒単位)の設定 604800=1週間
}
該当リソースにアクセスすると、以下のようなレスポンスヘッダーが設定されます。
% curl -D - http://localhost:8080/pure-spring-demo/resources/css/bar.css
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Last-Modified: Tue, 04 May 2021 13:46:30 GMT
Cache-Control: max-age=604800
Accept-Ranges: bytes
Content-Type: text/css
Content-Length: 36
Date: Tue, 04 May 2021 13:47:27 GMT
body {
background-color: blue;
}
キャッシュの有効期間を設定すると、有効期間内であればブラウザにキャッシュされているコンテンツが利用され、有効期間を超えている場合は、サーバー(ResourceHttpRequestHandler
)への問い合わせが行われます。
ResourceHttpRequestHandler
は、コンテンツが更新されていれば更新後のコンテンツを応答し、更新されていなければ304(Not Modified)を応答します。
Note: 有効期間に0を指定すると・・・
有効期間に0を設定すると、Cache-Controlにno-store
属性が設定されます。% curl -D - http://localhost:8080/pure-spring-demo/resources/css/bar.css HTTP/1.1 200 Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers Last-Modified: Tue, 04 May 2021 13:51:30 GMT Cache-Control: no-store Accept-Ranges: bytes Content-Type: text/css Content-Length: 36 Date: Tue, 04 May 2021 13:51:45 GMT body { background-color: blue; }
Cache-Controlの設定
有効期間(max-age
)以外のキャッシュ制御の設定値を指定することもできます。
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(
CacheControl.maxAge(Duration.ofDays(7))
.sMaxAge(Duration.ofDays(14))
.staleIfError(Duration.ofDays(1))
.cachePrivate()
.cachePublic()
.mustRevalidate()
.noTransform()
.proxyRevalidate());
}
該当リソースにアクセスすると、以下のようなレスポンスヘッダーが設定されます。
% curl -D - http://localhost:8080/pure-spring-demo/resources/css/bar.css
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Last-Modified: Tue, 04 May 2021 14:00:14 GMT
Cache-Control: max-age=604800, must-revalidate, no-transform, public, private, proxy-revalidate, s-maxage=1209600, stale-if-error=86400
Accept-Ranges: bytes
Content-Type: text/css
Content-Length: 36
Date: Tue, 04 May 2021 14:00:31 GMT
body {
background-color: blue;
}
Last-Modifiedヘッダの無効化
Spring 5.3よりレスポンスヘッダに「Last-Modified」を付与しないオプションがサポートされました。これは、どうやらSpring Initializrの静的ファイルを変更したのにもかかわらず、キャッシュが使われてしまう問題を解決するために追加されたみたいです。
- https://github.com/spring-io/start.spring.io/issues/519
- https://github.com/spring-projects/spring-framework/issues/25845
- https://github.com/spring-projects/spring-boot/issues/24099
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(604800)
.setUseLastModified(false); // Last-Modifiedヘッダの出力を無効化
}
% curl -D - http://localhost:8080/pure-spring-demo/resources/css/bar.css
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Cache-Control: max-age=604800
Accept-Ranges: bytes
Content-Type: text/css
Content-Length: 36
Date: Tue, 04 May 2021 14:56:07 GMT
body {
background-color: blue;
}
静的リソースのバージョニング
Spring MVCの機能を利用すると、静的リソースにバージョンを付与することができます。
Built-inでサポートされているバージョニング方法は、以下の2種類です。
コンテンツデータのMD5ハッシュ値によるバージョニング
この方法は、静的リソースのコンテンツデータ(バイト配列)をMD5のハッシュ値にした値をバージョン番号として扱い、バージョン番号をファイル名に含めます。つまり、ファイルの中身がかわると別のバージョンとして認識されます。たとえば、オリジナルのリソースパスが/resources/css/fuga-content.css
の場合は、/resources/css/fuga-content-bb5ea6c2162e206832eda9e911dc6406.css
という感じのリソースパスになります。
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(604800)
.resourceChain(true) // プロダクション環境ではtrue/開発環境ではfalseがおすすめ
.addResolver(new VersionResourceResolver()
.addContentVersionStrategy("/**/*-content*.css") // コンテンツデータのMD5ハッシュ値によるバージョニング機能の有効化
);
}
Project Root
└─src
└─ main
└─ resources # クラスパス
└─ static
└─ css
└─ fuga-content.css
% curl -D - http://localhost:8080/pure-spring-demo/resources/css/fuga-content-bb5ea6c2162e206832eda9e911dc6406.css
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Last-Modified: Tue, 04 May 2021 15:30:00 GMT
Cache-Control: max-age=604800
ETag: W/"bb5ea6c2162e206832eda9e911dc6406"
Accept-Ranges: bytes
Content-Type: text/css
Content-Length: 42
Date: Tue, 04 May 2021 15:32:53 GMT
body {
background-color: blueviolet;
}
指定した固定バージョンによるバージョニング
この方法は、指定した固定値をバージョン番号として扱い、バージョン番号はディレクトリになります。たとえば、オリジナルのリソースパスが/resources/css/hoge-fix.css
の場合は、/resources/v1.0.0/css/hoge-fix.css
という感じのリソースパスになります。
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(604800)
.resourceChain(true) // プロダクション環境ではtrue/開発環境ではfalseがおすすめ
.addResolver(new VersionResourceResolver()
.addFixedVersionStrategy("v1.0.0", "/**/*-fix.css") // 指定した固定バージョンによるバージョニング機能の有効化
);
}
Project Root
└─src
└─ main
└─ resources # クラスパス
└─ static
└─ css
└─ hoge-fix.css
% curl -D - http://localhost:8080/pure-spring-demo/resources/v1.0.0/css/hoge-fix.css
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Last-Modified: Tue, 04 May 2021 15:30:00 GMT
Cache-Control: max-age=604800
ETag: W/"v1.0.0"
Accept-Ranges: bytes
Content-Type: text/css
Content-Length: 36
Date: Tue, 04 May 2021 15:36:33 GMT
body {
background-color: aqua;
}
Viewとバージョニング機能の連携
バージョニングされたリソースパスをJSPなどのView内で扱うためには、サーブレットフィルターとしてorg.springframework.web.servlet.resource.ResourceUrlEncodingFilter
を登録してください。
ResourceUrlEncodingFilter
を使用すると、オリジナルのリソースパスからバージョニングされたリソースパスを解決することができます。
public class MyDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// ...(省略)...
@Override
protected Filter[] getServletFilters() {
return new Filter[]{new ResourceUrlEncodingFilter()}; // フィルタを登録
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setSessionTrackingModes(Collections.singleton(SessionTrackingMode.COOKIE)); // 本エントリーとは直接関係ないがセッションIDをCookieのみで扱うように設定を変更
super.onStartup(servletContext);
}
}
ViewとしてJSPを使用する場合は、JSTLの<c:url>
やSpringの<spring:url>
を使用してURLを生成すると、バージョニングされたリソースパスを出力することができます。なお、ViewとしてThymeleaf、Freemarker、Velocityなどを使用する場合でも、JSPと同様の仕組みがサポートされています。
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<link href="<c:url value='/static/css/app.css'/>" type="text/css" rel="stylesheet"/>
<link href="<c:url value='/static/css/foo.css'/>" type="text/css" rel="stylesheet"/>
<link href="<c:url value='/resources/css/bar.css'/>" type="text/css" rel="stylesheet"/>
<link href="<c:url value='/resources/css/hoge-fix.css'/>" type="text/css" rel="stylesheet"/>
<link href="<c:url value='/resources/css/fuga-content.css'/>" type="text/css" rel="stylesheet"/>
</head>
<body>
<h2>Hello World!</h2>
</body>
</html>
上記のJSPから、以下のようなHTMLが出力されます。
<html>
<head>
<link href="/pure-spring-demo/static/css/app.css" type="text/css" rel="stylesheet"/>
<link href="/pure-spring-demo/static/css/foo.css" type="text/css" rel="stylesheet"/>
<link href="/pure-spring-demo/resources/css/bar.css" type="text/css" rel="stylesheet"/>
<link href="/pure-spring-demo/resources/v1.0.0/css/hoge-fix.css" type="text/css" rel="stylesheet"/>
<link href="/pure-spring-demo/resources/css/fuga-content-bb5ea6c2162e206832eda9e911dc6406.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<h2>Hello World!</h2>
</body>
</html>
また、CSSファイル内に指定しているURL(別のCSSファイルをインポートするためのURLや、画像ファイルのURLなど)も、バージョニングされたリソースパスに変換されます。
@import url(fw-content.css);
body {
background-color: blueviolet;
}
上記のCSSにアクセスすると、クライアントにはバージョニングされたリソースパスに置き換わってレスポンスされます。
@import url(/pure-spring-demo/resources/css/fw-content-4d1801fd7cdbc0802def3501211925ef.css);
body {
background-color: blueviolet;
}
WebJarの利用
OSSのCSSライブラリやJavaScriptライブラリを使用する場合は、WebJarを使うこともあると思います。ここでは、Spring MVCの機能を使ってBootstrapのWebJarを利用する方法を紹介します。
まず、BootstrapのWebJarを依存ライブラリに追加します。
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.6.0-1</version>
</dependency>
次に、WebJarから静的リソースを取得するための設定を行います。
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// ...
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
最後に、ViewからWebJar内のリソースにアクセスします。
<html>
<head>
<link href="<c:url value='/webjars/bootstrap/4.6.0-1/css/bootstrap.min.css'/>" type="text/css" rel="stylesheet" />
</head>
<body>
<h2>Hello World!</h2>
</body>
</html>
ライブラリのバージョン番号の隠蔽
WebJarから静的リソースにアクセスすることはできましたが、View内でライブラリのバージョン番号を意識してしまっています。このままだとライブラリのバージョンアップした時にViewも修正する必要があるため、メンテナンス性がよくありません。ここでは、View内でライブラリのバージョン番号を意識させない方法を紹介しましょう。
まず、依存ライブラリとしてorg.webjars:webjars-locator
を追加します。
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId>
<version>0.40</version>
</dependency>
次に、WebJarのバージョン番号を隠蔽する機能(org.springframework.web.servlet.resource.WebJarsResourceResolver
)を有効化します。
なお、ResourceUrlEncodingFilter
の登録も必要です。
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// ...(省略)...
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.resourceChain(true); // プロダクション環境ではtrue/開発環境ではfalseがおすすめ + 自動でWebJarsResourceResolverが有効化される
}
最後に、ViewからWebJar内のリソースにバージョンを省いてアクセスすると、
<html>
<head>
<link href="<c:url value='/webjars/bootstrap/css/bootstrap.min.css'/>" type="text/css" rel="stylesheet" />
</head>
<body>
<h2>Hello World!</h2>
</body>
</html>
バージョン付きのパスに変換されます。
<html>
<head>
<link href="/pure-spring-demo/webjars/bootstrap/4.6.0-1/css/bootstrap.min.css" type="text/css" rel="stylesheet" />
</head>
<body>
<h2>Hello World!</h2>
</body>
</html>
Gzipなどで圧縮された静的リソースへのアクセス
OSSのCSSライブラリやJavaScriptライブラリの中には、テキストベースのファイルとは別にGzipなどでエンコーディングされたファイルを同封していることがあります。静的リソースとしてGzip化されたファイルを使用すると、データの転送量を減らすことができるため、画面の表示速度が速くなることが期待できます。Spring MVCでも、Gzip化されたファイルへアクセスする機能(org.springframework.web.servlet.resource.EncodedResourceResolver
)を提供しています。
NOTE:
Spring 5.1より
EncodedResourceResolver
が追加され、GzipResourceResolver
は非推奨APIになっています。
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// ...(省略)...
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.resourceChain(true); // プロダクション環境ではtrue/開発環境ではfalseがおすすめ
.addResolver(new EncodedResourceResolver());
}
以下に、OSSのCSSライブラリのひとつであるBootstrapのWebJarを例に、EncodedResourceResolver
を適用する時の実装例を紹介します。
WebJar(bootstrap-4.6.0-1.jar)
└─ META-INF
└─ resources
└─ webjars
└─ bootstrap
└─ 4.6.0-1
├─ css
| ├─ bootstrap.min.css
| ├─ bootstrap.min.css.gz # Gzip化されたファイル
...
Viewの実装では、Gzipファイルを意識する必要はありません。
<html>
<head>
<link href="<c:url value='/webjars/bootstrap/css/bootstrap.min.css'/>" type="text/css" rel="stylesheet" />
</head>
<body>
<h2>Hello World!</h2>
</body>
</html>
上記のView(JSP)から生成されるHTMLは、以下のようになります。
<html>
<head>
<link href="/pure-spring-demo/webjars/bootstrap/4.6.0-1/css/bootstrap.min.css" type="text/css" rel="stylesheet" />
</head>
<body>
<h2>Hello World!</h2>
</body>
</html>
HTMLだけ見るとGzip化されたファイルが使われているかわかりませんが、HTTPレスポンスのヘッダー情報を見るとGzip化されたファイルが使われていることがわかります。Content-Encoding
ヘッダー値がgzip
、Content-Length
ヘッダー値がGzipファイルのサイズ(約24KB)になっています。
% curl -D - http://localhost:8080/pure-spring-demo/webjars/bootstrap/4.6.0-1/css/bootstrap.min.css -H "Accept-Encoding: gzip"
HTTP/1.1 200
Vary: Accept-Encoding
Content-Encoding: gzip
Last-Modified: Tue, 04 May 2021 17:02:04 GMT
Accept-Ranges: bytes
Content-Type: text/css
Content-Length: 24105
Date: Tue, 04 May 2021 17:27:29 GMT
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
なお、GzipResourceResolver
を使用しない場合のHTTPレスポンスのヘッダー情報は、以下のようになります。Content-Encoding
ヘッダーは出力されず、Content-Length
ヘッダー値がテキストファイルのサイズ(約161KB)になっています。
% curl -D - http://localhost:8080/pure-spring-demo/webjars/bootstrap/4.6.0-1/css/bootstrap.min.css
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Last-Modified: Tue, 04 May 2021 17:02:04 GMT
Accept-Ranges: bytes
Content-Type: text/css
Content-Length: 161409
Date: Tue, 04 May 2021 17:29:55 GMT
...
Spring Boot上での静的リソースへのアクセス
Spring Bootは、Spring MVCの機能を使って静的リソースにアクセスします。
Spring Bootを使用すると、これまで説明してきた設定をSpring Bootの自動コンフィギュレーションの仕組みを使って行うことができます。
静的リソースの格納先
静的リソースは、クラスパス上の「/static
」「/public
」「/resources
」「/META-INF/resources
」の配下に格納することができます。
Project Root
└─src
└─ main
└─ resources # クラスパス
├─ static
| └─ css
| └─ c.css
├─ public
| └─ css
| └─ a.css
└─ resources
└─ css
└─ b.css
Executable JAR
└─ BOOT-INF
└─ lib
└─ foo.jar
└─ META-INF
└─ resources
└─ static
└─ css
└─ foo.css
例えば上記のような構成でファイルを格納すると、それぞれ以下のURLでアクセスすることができます。
http://localhost:8080/css/a.css
http://localhost:8080/css/b.css
http://localhost:8080/css/c.css
http://localhost:8080/static/css/foo.css
静的リソースの格納先を変更したい場合は、以下のような設定をプロパティファイルに指定してください。
spring.web.resources.static-locations=classpath:/contents/
キャッシュの有効期間の設定
キャッシュの有効期間を設定する場合は、以下の設定をプロパティファイルに指定してください。
spring.web.resources.cache.period=7d
Cache-Controlの設定
有効期間(max-age)以外のキャッシュ制御の設定値は、spring.web.resources.cache.cachecontrol
で始まるプロパティで指定することもできます。
spring.web.resources.cache.cachecontrol.max-age=7d
spring.web.resources.cache.cachecontrol.s-max-age=14d
spring.web.resources.cache.cachecontrol.stale-if-error=1d
spring.web.resources.cache.cachecontrol.cache-private=true
spring.web.resources.cache.cachecontrol.cache-public=true
spring.web.resources.cache.cachecontrol.must-revalidate=true
spring.web.resources.cache.cachecontrol.no-transform=true
spring.web.resources.cache.cachecontrol.proxy-revalidate=true
Last-Modifiedヘッダの無効化
Spring Boot 2.4よりレスポンスヘッダに「Last-Modified」を付与しないオプションをプロパティがサポートされています。背景は「こちら」を参照してください。
spring.web.resources.cache.use-last-modified=false
WARNING:
Spring Boot 2.4.2〜2.4.5だと、このプロパティが効かない模様で、これはSpring Boot 2.4.6で改修される予定です。
静的リソースのバージョニング
Spring Bootでも静的リソースのバージョニングが可能です。
コンテンツデータのMD5ハッシュ値によるバージョニング
コンテンツデータのMD5ハッシュ値によるバージョニングを利用する場合は、以下の設定をプロパティファイルに指定してください。
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**/*-content*.css
指定した固定バージョンによるバージョニング
指定した固定バージョンによるバージョニングを利用する場合は、以下の設定をプロパティファイルに指定してください。
spring.web.resources.chain.strategy.fixed.enabled=true
spring.web.resources.chain.strategy.fixed.version=v1.0.0
spring.web.resources.chain.strategy.fixed.paths=/**/*-fix.css
Viewとバージョニング機能の連携
ViewとしてThymeleaf/Velocity/Freemarkerを使う場合は、Spring Bootの自動コンフィギュレーションでResourceUrlEncodingFilter
が登録されます。自動コンフィギュレーション対象になっていないViewを使う場合は、ResourceUrlEncodingFilter
のBeanを明示的に定義してください。
@Bean
ResourceUrlEncodingFilter resourceUrlEncodingFilter() {
return new ResourceUrlEncodingFilter();
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<link th:href="@{/css/a.css}" href="/public/css/a.css" type="text/css" rel="stylesheet"/>
<link th:href="@{/css/b.css}" href="/resources/css/c.css" type="text/css" rel="stylesheet"/>
<link th:href="@{/css/c.css}" href="/static/css/c.css" type="text/css" rel="stylesheet"/>
<link th:href="@{/static/css/foo.css}" href="/static/css/foo.css" type="text/css" rel="stylesheet"/>
<link th:href="@{/css/hoge-fix.css}" href="/static/css/hoge-fix.css" type="text/css" rel="stylesheet"/>
<link th:href="@{/css/fuga-content.css}" href="/static/css/fuga-content.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<h2>Hello World!</h2>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<link href="/css/a.css" type="text/css" rel="stylesheet"/>
<link href="/css/b.css" type="text/css" rel="stylesheet"/>
<link href="/css/c.css" type="text/css" rel="stylesheet"/>
<link href="/static/css/foo.css" type="text/css" rel="stylesheet"/>
<link href="/v1.0.0/css/hoge-fix.css" type="text/css" rel="stylesheet"/>
<link href="/css/fuga-content-b24befa35213dee850d89e8de5d9665c.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<h2>Hello World!</h2>
</body>
</html>
WebJarの利用
Spring Bootを使用すると、WebJar用のコンフィギュレーションを自動で行ってくれるため、使用するライブラリのWebJarを追加するだけです。また、バージョン番号を隠蔽したい場合は、依存ライブラリとしてorg.webjars:webjars-locator
を追加してください(こちらもライブラリを追加するだけで自動で有効化されます )。Viewでの実装は、「Spring MVCの独自機能を使用してアクセスする方法」で紹介した内容と一緒です。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<link th:href="@{/webjars/bootstrap/css/bootstrap.min.css}" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<h2>Hello World!</h2>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<link href="/webjars/bootstrap/4.6.0-1/css/bootstrap.min.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<h2>Hello World!</h2>
</body>
</html>
Gzipなどで圧縮された静的リソースへのアクセス
Spring BootでGzipなどで圧縮された静的リソースへアクセスする場合は、以下の設定をプロパティファイルに指定してください。
spring.web.resources.chain.compressed=true
リソースチェーンのキャッシュの無効化
バージョニング機能やGzip機能などを使う際に、デフォルトの動作だと読み込んだリソース情報をキャッシュします。これは試験環境やプロダクション環境では有効な設定ですが、ローカルの開発環境では開発効率を低下させる可能性があります。ローカルの開発環境では、キャッシュを無効化する方がようでしょう。キャッシュを無効化する場合は、以下の設定をプロパティファイルに指定してください。
spring.web.resources.chain.cache=false
自動コンフィギュレーションの無効化
Spring Bootが行う自動コンフィギュレーションを無効化したい場合は、以下の設定をプロパティファイルに指定してください。
spring.web.resources.add-mappings=false
まとめ
今回は、Spring MVCが提供する静的リソースがらみの機能を紹介しました。Spring Bootでアプリケーションを作っているとあまり意識しない(しなくてもいい)部分かもしれませんが、Spring MVCはこんな仕組みで静的リソースへアクセスしています。
補足
Gzip機能とバージョニング機能の併用について
初回投稿時に、Gzip機能とバージョニング機能の併用の仕方+Spring Bootで併用ができない旨を記載していましたが、どうもSpring Framework的にはサポート外な使い方みたいなので、記載を削除しました(すみません・・・ )。詳しくは 「Spring Bootのgh-5876」をご覧ください。 (2016/5/14 追加)
↓
Spring 5.0で併用できるように改修された模様です (2021/5/5 追加)。
参考サイト
- https://docs.spring.io/spring-framework/docs/5.3.6/reference/html/web.html#mvc-config-static-resources
- https://docs.spring.io/spring-framework/docs/5.3.6/reference/html/web.html#mvc-default-servlet-handler
- https://docs.spring.io/spring-boot/docs/2.4.5/reference/htmlsingle/#boot-features-spring-mvc-static-content