TERASOLUNA 5.x(= Spring MVC)のブランクプロジェクトでWebJarsを使えるようにする

今回は、TERASOLUNA 5.x(=実体はSpring Framework)が提供しているブランクプロジェクトから生成した開発プロジェクトにて、WebJarsの仕組みを利用してクライアント系ライブラリ(CSSライブラリ、JSライブラリ)のファイルをアプリケーションに組み込む方法を紹介します（=メモしておきます）。

# Waht's TERASOLUNA 5.x ?

TERASOLUNA 5.xが何者かについては、[ここ](http://terasolunaorg.github.io/)に記載があります。
コンフィギュレーション方法がXMLベースだったり、Spring Bootをサポートしていない点が残念ではありますが、Springを使用してWebアプリケーションを開発するためのノウハウが詰まっており、メンテナンスも継続的（年１回のマイナーバージョンアップ＋α）に行われています。

# 検証バージョン

* TERASOLUNA Server Framework for Java (5.x) 5.3.0.RELEASE
* Spring Framework 4.3.5.RELEASE

# 開発プロジェクトの生成

TERASOLUNA 5.xでは「[シングルプロジェクト構成](https://github.com/terasolunaorg/terasoluna-gfw-web-blank)」と「[マルチプロジェクト(マルチモジュール)構成](https://github.com/terasolunaorg/terasoluna-gfw-web-multi-blank)」の２種類のブランクプロジェクト(Mavne Archetype)を提供していますが、今回はシングルプロジェクト構成のプロジェクトを作ります。

```:コマンド
$ mvn archetype:generate -B\
 -DarchetypeGroupId=org.terasoluna.gfw.blank\
 -DarchetypeArtifactId=terasoluna-gfw-web-blank-archetype\
 -DarchetypeVersion=5.3.0.RELEASE\
 -DgroupId=com.example\
 -DartifactId=demo-webjars\
 -Dversion=1.0.0-SNAPSHOT
```

```:コマンド実行結果
$ mvn archetype:generate -B\
>  -DarchetypeGroupId=org.terasoluna.gfw.blank\
>  -DarchetypeArtifactId=terasoluna-gfw-web-blank-archetype\
>  -DarchetypeVersion=5.3.0.RELEASE\
>  -DgroupId=com.example\
>  -DartifactId=demo-webjars\
>  -Dversion=1.0.0-SNAPSHOT
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] >>> maven-archetype-plugin:2.3:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO] 
[INFO] <<< maven-archetype-plugin:2.3:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO] 
[INFO] 
[INFO] --- maven-archetype-plugin:2.3:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Batch mode
[INFO] Archetype repository not defined. Using the one from [org.terasoluna.gfw.blank:terasoluna-gfw-web-blank-archetype:5.3.0.RELEASE] found in catalog remote
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: terasoluna-gfw-web-blank-archetype:5.3.0.RELEASE
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.example
[INFO] Parameter: artifactId, Value: demo-webjars
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Parameter: package, Value: com.example
[INFO] Parameter: packageInPathFormat, Value: com/example
[INFO] Parameter: package, Value: com.example
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Parameter: groupId, Value: com.example
[INFO] Parameter: artifactId, Value: demo-webjars
[INFO] project created from Archetype in dir: /usr/local/apps/demo-webjars
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.617 s
[INFO] Finished at: 2017-09-03T13:01:31+09:00
[INFO] Final Memory: 16M/222M
[INFO] ------------------------------------------------------------------------
$
```

```:プロジェクト内の構成
$ tree demo-webjars
demo-webjars
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           ├── app
    │   │           │   └── welcome
    │   │           │       └── HelloController.java
    │   │           └── domain
    │   │               ├── model
    │   │               ├── repository
    │   │               └── service
    │   ├── resources
    │   │   ├── META-INF
    │   │   │   ├── dozer
    │   │   │   └── spring
    │   │   │       ├── applicationContext.xml
    │   │   │       ├── demo-webjars-codelist.xml
    │   │   │       ├── demo-webjars-domain.xml
    │   │   │       ├── demo-webjars-infra.xml
    │   │   │       ├── spring-mvc.xml
    │   │   │       └── spring-security.xml
    │   │   ├── ValidationMessages.properties
    │   │   ├── dozer.properties
    │   │   ├── i18n
    │   │   │   └── application-messages.properties
    │   │   └── logback.xml
    │   └── webapp
    │       ├── WEB-INF
    │       │   ├── views
    │       │   │   ├── common
    │       │   │   │   ├── error
    │       │   │   │   │   ├── accessDeniedError.jsp
    │       │   │   │   │   ├── businessError.jsp
    │       │   │   │   │   ├── dataAccessError.jsp
    │       │   │   │   │   ├── invalidCsrfTokenError.jsp
    │       │   │   │   │   ├── missingCsrfTokenError.jsp
    │       │   │   │   │   ├── resourceNotFoundError.jsp
    │       │   │   │   │   ├── systemError.jsp
    │       │   │   │   │   ├── transactionTokenError.jsp
    │       │   │   │   │   └── unhandledSystemError.html
    │       │   │   │   └── include.jsp
    │       │   │   └── welcome
    │       │   │       └── home.jsp
    │       │   └── web.xml
    │       └── resources
    │           └── app
    │               └── css
    │                   └── styles.css
    └── test
        ├── java
        └── resources

28 directories, 25 files
$ 
```

# プロジェクトのビルド

プロジェクトの作成が終わったら、Mavneコマンドを使用してwarファイルをビルドします。

```
$ cd demo-webjars
$ mvn package
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Building TERASOLUNA Server Framework for Java (5.x) Web Blank Project 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ demo-webjars ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 11 resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ demo-webjars ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /usr/local/apps/demo-webjars/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ demo-webjars ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ demo-webjars ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ demo-webjars ---
[INFO] 
[INFO] --- maven-war-plugin:2.5:war (default-war) @ demo-webjars ---
[INFO] Packaging webapp
[INFO] Assembling webapp [demo-webjars] in [/usr/local/apps/demo-webjars/target/demo-webjars-1.0.0-SNAPSHOT]
[INFO] Processing war project
[INFO] Copying webapp resources [/usr/local/apps/demo-webjars/src/main/webapp]
[INFO] Webapp assembled in [324 msecs]
[INFO] Building war: /usr/local/apps/demo-webjars/target/demo-webjars.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.454 s
[INFO] Finished at: 2017-09-03T13:07:22+09:00
[INFO] Final Memory: 32M/258M
[INFO] ------------------------------------------------------------------------
$
```

# Webアプリの起動

ビルドしたwarファイルを、Carog Mavne Pluginを使用してアプリケーションサーバ(Tomcat)にデプロイして起動します。


```
$ mvn cargo:run
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Building TERASOLUNA Server Framework for Java (5.x) Web Blank Project 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- cargo-maven2-plugin:1.6.2:run (default-cli) @ demo-webjars ---
[INFO] [en2.ContainerRunMojo] Resolved container artifact org.codehaus.cargo:cargo-core-container-tomcat:jar:1.6.2 for container tomcat8x
[INFO] [talledLocalContainer] Tomcat 8.x starting...
[INFO] [stalledLocalDeployer] Deploying [/usr/local/apps/demo-webjars/target/demo-webjars.war] to [/usr/local/apps/demo-webjars/target/cargo/configurations/tomcat8x/webapps]...
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: Server version:        Apache Tomcat/8.0.39
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: Server built:          Nov 9 2016 08:48:39 UTC
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: Server number:         8.0.39.0
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: OS Name:               Mac OS X
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: OS Version:            10.10.5
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: Architecture:          x86_64
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: Java Home:             /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: JVM Version:           1.8.0_131-b11
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: JVM Vendor:            Oracle Corporation
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: CATALINA_BASE:         /usr/local/apps/demo-webjars/target/cargo/configurations/tomcat8x
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: CATALINA_HOME:         /usr/local/apps/demo-webjars/target/cargo/installs/apache-tomcat-8.0.39/apache-tomcat-8.0.39
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: Command line argument: -Xms512m
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: Command line argument: -Xmx1024m
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: Command line argument: -Dcatalina.home=/usr/local/apps/demo-webjars/target/cargo/installs/apache-tomcat-8.0.39/apache-tomcat-8.0.39
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: Command line argument: -Dcatalina.base=/usr/local/apps/demo-webjars/target/cargo/configurations/tomcat8x
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: Command line argument: -Djava.io.tmpdir=/usr/local/apps/demo-webjars/target/cargo/configurations/tomcat8x/temp
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.VersionLoggerListener log
[INFO] [talledLocalContainer] 情報: Command line argument: -Djava.util.logging.config.file=/usr/local/apps/demo-webjars/target/cargo/configurations/tomcat8x/conf/logging.properties
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.core.AprLifecycleListener lifecycleEvent
[INFO] [talledLocalContainer] 情報: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: /Users/shimizukazuki/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.coyote.AbstractProtocol init
[INFO] [talledLocalContainer] 情報: Initializing ProtocolHandler ["http-nio-8080"]
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.tomcat.util.net.NioSelectorPool getSharedSelector
[INFO] [talledLocalContainer] 情報: Using a shared selector for servlet write/read
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.coyote.AbstractProtocol init
[INFO] [talledLocalContainer] 情報: Initializing ProtocolHandler ["ajp-nio-8009"]
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.tomcat.util.net.NioSelectorPool getSharedSelector
[INFO] [talledLocalContainer] 情報: Using a shared selector for servlet write/read
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.Catalina load
[INFO] [talledLocalContainer] 情報: Initialization processed in 595 ms
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.core.StandardService startInternal
[INFO] [talledLocalContainer] 情報: サービス Catalina を起動します
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.core.StandardEngine startInternal
[INFO] [talledLocalContainer] 情報: Starting Servlet Engine: Apache Tomcat/8.0.39
[INFO] [talledLocalContainer] 9 03, 2017 1:08:56 午後 org.apache.catalina.startup.HostConfig deployWAR
[INFO] [talledLocalContainer] 情報: Webアプリケーションアーカイブ /usr/local/apps/demo-webjars/target/cargo/configurations/tomcat8x/webapps/cargocpc.war を配備します
[INFO] [talledLocalContainer] 9 03, 2017 1:08:57 午後 org.apache.catalina.startup.HostConfig deployWAR
[INFO] [talledLocalContainer] 情報: Deployment of web application archive /usr/local/apps/demo-webjars/target/cargo/configurations/tomcat8x/webapps/cargocpc.war has finished in 295 ms
[INFO] [talledLocalContainer] 9 03, 2017 1:08:57 午後 org.apache.catalina.startup.HostConfig deployWAR
[INFO] [talledLocalContainer] 情報: Webアプリケーションアーカイブ /usr/local/apps/demo-webjars/target/cargo/configurations/tomcat8x/webapps/demo-webjars.war を配備します
[INFO] [talledLocalContainer] 9 03, 2017 1:08:58 午後 org.apache.jasper.servlet.TldScanner scanJars
[INFO] [talledLocalContainer] 情報: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
[INFO] [talledLocalContainer] 9 03, 2017 1:08:58 午後 org.apache.catalina.core.ApplicationContext log
[INFO] [talledLocalContainer] 情報: No Spring WebApplicationInitializer types detected on classpath
[INFO] [talledLocalContainer] 9 03, 2017 1:08:59 午後 org.apache.catalina.core.ApplicationContext log
[INFO] [talledLocalContainer] 情報: Initializing Spring root WebApplicationContext
[INFO] [talledLocalContainer] 9 03, 2017 1:09:00 午後 org.apache.catalina.core.ApplicationContext log
[INFO] [talledLocalContainer] 情報: Initializing Spring FrameworkServlet 'appServlet'
[INFO] [talledLocalContainer] date:2017-09-03 13:09:00	thread:localhost-startStop-1	X-Track:	level:INFO 	logger:o.springframework.web.servlet.DispatcherServlet 	message:FrameworkServlet 'appServlet': initialization started
[INFO] [talledLocalContainer] date:2017-09-03 13:09:00	thread:localhost-startStop-1	X-Track:	level:INFO 	logger:o.s.w.s.m.m.a.RequestMappingHandlerMapping      	message:Mapped "{[/],methods=[GET || POST]}" onto public java.lang.String com.example.app.welcome.HelloController.home(java.util.Locale,org.springframework.ui.Model)
[INFO] [talledLocalContainer] date:2017-09-03 13:09:01	thread:localhost-startStop-1	X-Track:	level:INFO 	logger:o.s.w.s.m.m.a.RequestMappingHandlerAdapter      	message:Looking for @ControllerAdvice: WebApplicationContext for namespace 'appServlet-servlet': startup date [Sun Sep 03 13:09:00 JST 2017]; parent: Root WebApplicationContext
[INFO] [talledLocalContainer] date:2017-09-03 13:09:01	thread:localhost-startStop-1	X-Track:	level:INFO 	logger:o.s.w.s.m.m.a.RequestMappingHandlerAdapter      	message:Looking for @ControllerAdvice: WebApplicationContext for namespace 'appServlet-servlet': startup date [Sun Sep 03 13:09:00 JST 2017]; parent: Root WebApplicationContext
[INFO] [talledLocalContainer] date:2017-09-03 13:09:01	thread:localhost-startStop-1	X-Track:	level:INFO 	logger:o.s.web.servlet.handler.SimpleUrlHandlerMapping 	message:Mapped URL path [/**] onto handler 'org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler#0'
[INFO] [talledLocalContainer] date:2017-09-03 13:09:01	thread:localhost-startStop-1	X-Track:	level:INFO 	logger:o.s.web.servlet.handler.SimpleUrlHandlerMapping 	message:Mapped URL path [/resources/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'
[INFO] [talledLocalContainer] date:2017-09-03 13:09:01	thread:localhost-startStop-1	X-Track:	level:INFO 	logger:o.springframework.web.servlet.DispatcherServlet 	message:FrameworkServlet 'appServlet': initialization completed in 1222 ms
[INFO] [talledLocalContainer] 9 03, 2017 1:09:01 午後 org.apache.catalina.startup.HostConfig deployWAR
[INFO] [talledLocalContainer] 情報: Deployment of web application archive /usr/local/apps/demo-webjars/target/cargo/configurations/tomcat8x/webapps/demo-webjars.war has finished in 4,615 ms
[INFO] [talledLocalContainer] 9 03, 2017 1:09:01 午後 org.apache.catalina.startup.HostConfig deployDirectory
[INFO] [talledLocalContainer] 情報: Webアプリケーションディレクトリ /usr/local/apps/demo-webjars/target/cargo/configurations/tomcat8x/webapps/host-manager を配備します
[INFO] [talledLocalContainer] 9 03, 2017 1:09:01 午後 org.apache.catalina.startup.HostConfig deployDirectory
[INFO] [talledLocalContainer] 情報: Deployment of web application directory /usr/local/apps/demo-webjars/target/cargo/configurations/tomcat8x/webapps/host-manager has finished in 19 ms
[INFO] [talledLocalContainer] 9 03, 2017 1:09:01 午後 org.apache.catalina.startup.HostConfig deployDirectory
[INFO] [talledLocalContainer] 情報: Webアプリケーションディレクトリ /usr/local/apps/demo-webjars/target/cargo/configurations/tomcat8x/webapps/manager を配備します
[INFO] [talledLocalContainer] 9 03, 2017 1:09:01 午後 org.apache.catalina.startup.HostConfig deployDirectory
[INFO] [talledLocalContainer] 情報: Deployment of web application directory /usr/local/apps/demo-webjars/target/cargo/configurations/tomcat8x/webapps/manager has finished in 12 ms
[INFO] [talledLocalContainer] 9 03, 2017 1:09:01 午後 org.apache.coyote.AbstractProtocol start
[INFO] [talledLocalContainer] 情報: Starting ProtocolHandler ["http-nio-8080"]
[INFO] [talledLocalContainer] 9 03, 2017 1:09:01 午後 org.apache.coyote.AbstractProtocol start
[INFO] [talledLocalContainer] 情報: Starting ProtocolHandler ["ajp-nio-8009"]
[INFO] [talledLocalContainer] 9 03, 2017 1:09:01 午後 org.apache.catalina.startup.Catalina start
[INFO] [talledLocalContainer] 情報: Server startup in 5012 ms
[INFO] [talledLocalContainer] Tomcat 8.x started on port [8080]
[INFO] Press Ctrl-C to stop the container...
```

アプリケーションルートにアクセスすると、以下のようなトップ画面(HTML)が応用されます。

```
$ curl -D - http://localhost:8080/demo-webjars/
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Track: c061e8853a3f49c2919c8ac6f46071cf
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Content-Type: text/html;charset=UTF-8
Content-Language: ja-JP
Content-Length: 320
Date: Sun, 03 Sep 2017 04:11:03 GMT









<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet"
    href="/demo-webjars/resources/app/css/styles.css">
</head>
<body>
    <div id="wrapper">
        <h1>Hello world!</h1>
        <p>The time on the server is 2017/09/03 13:11:03 JST.</p>
    </div>
</body>
</html>
$
```

# WebJarsを使う

今回はCSSライブラリの[Bootstrap](http://getbootstrap.com/)を[WebJars](https://www.webjars.org/)の仕組みを利用してアプリケーションに取り込んでみます。

```xml:pom.xml
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>bootstrap</artifactId>
    <version>3.3.7-1</version>
</dependency>
```

warをビルドし直して再度デプロイ・起動します。

```
$ mvn package cargo:run
...
$
```

WebJarsのbootstrapのcssファイルにアクセスしてみます。


```
$ curl -D - http://localhost:8080/demo-webjars/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Track: cda63d914b0142c5bd36c0e85dd8e22d
Accept-Ranges: bytes
ETag: W/"121200-1469407916000"
Last-Modified: Mon, 25 Jul 2016 00:51:56 GMT
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Content-Type: text/css;charset=UTF-8
Content-Length: 121200
Date: Sun, 03 Sep 2017 04:24:44 GMT

/*!
 * Bootstrap v3.3.7 (http://getbootstrap.com)
 * Copyright 2011-2016 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{fo....
...
```

Servlet 3.0+をサポートしているアプリケーションサーバの場合は、クラスパス(WEB-INF/libなど)にWebJarsのjarファイルがあれば、特別な設定を行うことなくアクセスすることができます。

ということで、単純にWebJarsを使うだけなら、

```jsp:src/main/webapp/WEB-INF/views/welcome/home.jsp
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet"
    href="${pageContext.request.contextPath}/resources/app/css/styles.css">
<link rel="stylesheet"
      href="${pageContext.request.contextPath}/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css"> <!-- ★ 追加 -->
</head>
<body>
    <div id="wrapper">
        <h1>Hello world!</h1>
        <p>The time on the server is ${serverTime}.</p>
    </div>
</body>
</html>
```
とすればOKです。

# webjar-locatorを使用してバージョン番号を排除する

おそらく・・・多くのエンジニアの方はcssファイルにアクセスする際のURLに、バージョン番号(本例だと`3.3.7-1`)を含めたくないな〜と思うのではないでしょうか？
Spring MVCは、`webjar-locator`というライブラリと連携することで、アプリケーションコード(JSPなど)からバージョン番号を排除する仕組みを提供しています。

```xml:pon.xml
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>webjars-locator</artifactId>
</dependency>
```

```xml:src/main/resources/META-INF/spring/spring-mvc.xml
<mvc:resources mapping="/webjars/**" location="/webjars/">
    <mvc:resource-chain resource-cache="true"/> <!-- このタグを指定するとSpringがwebjars-locatorとの連携機能を自動で有効化してくれる -->
</mvc:resources>
```

warをビルドし直して再度デプロイ・起動し、バージョン番号を抜いてアクセスしてみます。

```
$ curl -D - http://localhost:8080/demo-webjars/webjars/bootstrap/css/bootstrap.min.css
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Track: adb21119739e422bb3faf020ff5e9e3f
Last-Modified: Sat, 17 Sep 2016 02:28:22 GMT
Accept-Ranges: bytes
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Content-Type: text/css;charset=UTF-8
Content-Length: 121200
Date: Sun, 03 Sep 2017 04:53:18 GMT

/*!
 * Bootstrap v3.3.7 (http://getbootstrap.com)
 * Copyright 2011-2016 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{
...
```

お〜アクセスできましたね。

ということで、読み込むcssファイルを指定する部分は・・・

```jsp:src/main/webapp/WEB-INF/views/welcome/home.jsp
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet"
    href="${pageContext.request.contextPath}/resources/app/css/styles.css">
<link rel="stylesheet"
      href="${pageContext.request.contextPath}/webjars/bootstrap/css/bootstrap.min.css"> <!-- ★ パスからバージョン番号を削除 -->
</head>
<body>
    <div id="wrapper">
        <h1>Hello world!</h1>
        <p>The time on the server is ${serverTime}.</p>
    </div>
</body>
</html>
```

とすればOKです。

# バージョンアップ時のブラウザキャッシュ対策を行う

パスにバージョン番号を含めなくてOKということは、ライブラリのバージョンをあげてもパスが変わらないということです。
ということは、Cache-Controlの設定次第によっては、ブラウザがキャッシュしているファイル（バージョンアップ前のファイル）が使われてしまうことがあります。かといってこれらのファイルをキャッシュしないと、無駄なネットワークトラフィックやサーバ側でのリソースアクセスが発生するため、「システムへの負荷が高くなる」「画面の描画速度が遅くなる」ことが予想されます。

この問題を解決するには、

* アプリケーションコード上で指定するパスにはバージョンは含めない
* アプリケーションコードから生成するHTMLにはパスの中にバージョンを含める

ようにすればいいわけですが・・・なんと（ちゃんと）・・・Springがこの機能を提供してくれています！！

まず、Spring提供の`ResourceUrlEncodingFilter`をサーブレットフィルタとして登録します。

> **重要: サーブレットフィルタの定義順**
>
> Spring Securityのサーブレットフィルタ（springSecurityFilterChain）より**後に**定義してください。

```xml:src/main/webapp/WEB-INF/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>
```

でもって、JSPではパスを指定する際にJSTLまたはSpring提供のタグライブラリを使いようにします。

```jsp:src/main/webapp/WEB-INF/views/welcome/home.jsp
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet"
    href="${pageContext.request.contextPath}/resources/app/css/styles.css">
<link rel="stylesheet"
      href="${pageContext.request.contextPath}/webjars/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet"
      href="<c:url value='/webjars/bootstrap/css/bootstrap.min.css'/>"> <!-- ★ JSTLのタグライブラリを使う -->
<link rel="stylesheet"
      href="<spring:url value='/webjars/bootstrap/css/bootstrap.min.css'/>"> <!-- ★ Springのタグライブラリを使う -->
</head>
<body>
    <div id="wrapper">
        <h1>Hello world!</h1>
        <p>The time on the server is ${serverTime}.</p>
    </div>
</body>
</html>
```

すると・・・

```
$ curl -D - http://localhost:8080/demo-webjars/
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Track: 5576191e3ed54e82a83b67236bc98b24
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Content-Type: text/html;charset=UTF-8
Content-Language: ja-JP
Content-Length: 609
Date: Sun, 03 Sep 2017 05:28:08 GMT









<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet"
    href="/demo-webjars/resources/app/css/styles.css">
<link rel="stylesheet"
      href="/demo-webjars/webjars/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet"
      href="/demo-webjars/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css">
<link rel="stylesheet"
      href="/demo-webjars/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css">
</head>
<body>
    <div id="wrapper">
        <h1>Hello world!</h1>
        <p>The time on the server is 2017/09/03 14:28:08 JST.</p>
    </div>
</body>
</html>
$
```

JSPから生成されたHTMLには、パスの中にバージョン番号が含まれるようになりました。


# ブラウザキャッシュ対策を行う

TERASOLUNA 5.xのブランクプロジェクトは、デフォルトでSpring Securityのサーブレットフィルタが提供されており、`/resources/**`以外のパスへのアクセスがキャッシュされないように設定されています。つまり、`/webjars/**`へのアクセスはキャッシュされないため、画面を描画するたびにCSSファイルを取得しなおすことになります。

この挙動をかえるためには、`/webjars/**`へのアクセスをSpring Securityの処理対象から除外すればOKです。

```xml:src/main/resources/META-INF/spring/spring-security.xml
<sec:http pattern="/resources/**" security="none"/>
<sec:http pattern="/webjars/**" security="none"/> <!-- ★ 追加 -->
<sec:http>
    <!-- ... -->
</sec:http>
```

この状態でアクセスすると・・・

```
$ curl -D - http://localhost:8080/demo-webjars/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Track: a27dba531c8f4c79af0657ceaf5b430b
Last-Modified: Sat, 17 Sep 2016 02:28:22 GMT
Accept-Ranges: bytes
Content-Type: text/css;charset=UTF-8
Content-Length: 121200
Date: Sun, 03 Sep 2017 05:48:20 GMT

/*!
 * Bootstrap v3.3.7 (http://getbootstrap.com)
 * Copyright 2011-2016 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{...
...
```

Spring Securityが付与していたCache-Controlのヘッダがなくなっていることが確認できます。
ChromeとSafariで確認したところ、

* アドレスバーにURLを入れた時は共にローカルキャッシュを使う
* ChromeはF5で再描画した時もサーバには問い合わせずにローカルキャッシュを使う
* SafariはF5で再描画した時はサーバに問い合わせて304が返却されてローカルキャッシュを使う

という動きになっている感じでした。（ちょっとしたらウソかもしれません・・・）


# HanderInterceptorの処理対象から除外する

TERASOLUNA 5.xのブランクプロジェクトでは、以下の`HandlerInterceptor`がデフォルトで適用されているので、WebJarsからリソースを取得する際に適用されないようにします。

* `TraceLoggingInterceptor`
* `TransactionTokenInterceptor`
* `CodeListInterceptor`
* `OpenEntityManagerInViewInterceptor` (JPA用のブランクプロジェクトの場合のみ)

```xml:src/main/resources/META-INF/spring/spring-mvc.xml
<mvc:interceptor>
    <mvc:mapping path="/**" />
    <mvc:exclude-mapping path="/resources/**" />
    <mvc:exclude-mapping path="/webjars/**" /> <!-- ★ 追加 -->
    <mvc:exclude-mapping path="/**/*.html" />
    <bean
        class="org.terasoluna.gfw.web.logging.TraceLoggingInterceptor" />
</mvc:interceptor>
```

# まとめ

WebJarsを使うと、

* Javaのライブラリと同じ方式(Maven)で依存関係＋バージョンを管理できる
* Spring MVC提供の機能 + `webjars-locator`を使うことでアプリのコード(JSPやHTML)からライブラリのバージョン番号を意識する必要がなくなる

というメリットがあるので、積極的使いたいな〜と思います。

# 参考サイト

* [Spring MVC(+Spring Boot)上での静的リソースへのアクセスを理解する](http://qiita.com/kazuki43zoo/items/e12a72d4ac4de418ee37)
* [Spring Framework Reference -22.16.9 Serving of Resources-](https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-config-static-resources)