CLOVER🍀

That was when it all began.

Servlet 3.0からJSPをJARファイルの中に置けるようになっていたという話

全然知らなかったので、メモ的に。

Servlet 3.0から、JSPや静的ファイルをJARファイルの中に含めることができるようになっていたみたいです。

Tomcat 7も対応したServlet 3.0の6つの主な変更点 (3/3):Tomcat 7の新機能で何ができるようになるのか?(1) - @IT

JSP Container Pluggability-プラグ可能性 ← ただいまパキり中

サーブレット3.0仕様書邦訳版

サーブレット仕様書、第 3.0 最終版

10.5 ディレクトリ構造(Directory Structure)

ウェブ・アプリケーションはディレクトリたちの構造化された階層として存在する。この階層のルートがそのアプリケーションの要素であるファイルたちのドキュメント・ルートになる。例えば、あるウェブ・コンテナのなかで/catalog というコンテキスト・パスを持ったあるウェブ・アプリケーションでは、このアプリケーション階層のベースにある index.html ファイル、あるいは META-INF/resources ディレクトリ
のなかで index.html を含む WEB-INF/lib のなかの JAR ファイルのなかにある index.html ファイルは、/catalog/index.html からの要求を満足させるために使われる。もし index.html がルート・コンテキストの中、及びこのアプリケーションの WEB-INF/lib ディレクトリのなかの JAR ファイルの METAINF/resourcesディレクトリの中の双方に存在する場合は、そのルート・コンテキストの中で得られるファイルが使われねばならない。

https://www.cresc.co.jp/tech/java/Servlet_Specifications/Servlet_Specification_3_0_Japanese.pdf

なるほど?どうやら、JARファイル内の「META-INF/resources」配下にファイルを置き、WARファイル側の「WEB-INF/lib」ディレクトリに
配置すると、コンテナが中身のファイルを認識してくれる、と。パスがかぶった場合は、WARファイル側の方が優先される、という
ことみたいですね。

JSRも。

The Java Community Process(SM) Program - JSRs: Java Specification Requests - detail JSR# 340

The Java Community Process(SM) Program - JSRs: Java Specification Requests - detail JSR# 315

使うかな?どうかな?という気はしますが、とりあえず動かしてみるとしましょう。

サンプルコード

確認のために、簡単なMavenのマルチプロジェクトを作成します。ひとつは、JSPやテキストファイルのみを含めたJARファイル、
もうひとつはWebアプリケーション。WEB-INF配下にファイルを置くことも含めて、確認してみましょう。

Webアプリケーション側には、一部JARファイル内のパスと重複させたものも配置してみます。

親プロジェクト。
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>jsp-in-jar</artifactId>
    <packaging>pom</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <modules>
        <module>jsp-files</module>
        <module>web-app</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>
</project>

JARファイル側のプロジェクト。
jsp-files/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jsp-in-jar</artifactId>
        <groupId>org.littlewings</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>jsp-files</artifactId>
</project>

配置しているファイルは、こんな感じです。

jsp-files/src/main/resources/META-INF/resources/hello.jsp
jsp-files/src/main/resources/META-INF/resources/hello-duplicate.jsp
jsp-files/src/main/resources/META-INF/resources/hello.txt
jsp-files/src/main/resources/META-INF/resources/hello-duplicate.txt
jsp-files/src/main/resources/META-INF/resources/WEB-INF/view/hello-webinf.jsp
jsp-files/src/main/resources/META-INF/resources/WEB-INF/view/hello-webinf-duplicate.jsp

WEB-INF配下のファイルは、Webアプリケーション側に作成するServletからフォワードして使う感じで用意。

中身は、動作確認の時に記載します。

Webアプリケーション側。
web-app/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jsp-in-jar</artifactId>
        <groupId>org.littlewings</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>web-app</artifactId>
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.littlewings</groupId>
            <artifactId>jsp-files</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>


    <build>
        <finalName>ROOT</finalName>
    </build>
</project>

依存関係は、Sevlet API 3.1と先ほど作成しているJSPなどを含めたJARファイルです。

こちらのモジュールで作成したファイルは、こんな感じ。

web-app/src/main/webapp/hello-duplicate.txt
web-app/src/main/webapp/hello-duplicate.jsp
web-app/src/main/webapp/WEB-INF/view/hello-webinf-duplicate.jsp
web-app/src/main/java/org/littlewings/servlet/HelloWebinfJspServlet.java
web-app/src/main/java/org/littlewings/servlet/HelloWebinfDuplicateJspServlet.java

JARファイル側とパスを重複させたテキストファイルに、JSP、あとServletを2つ。

Servletは、Requestスコープに値を入れてWEB-INF配下にフォワードするだけのものです。

こちらは、JARファイル内にあるJSPにフォワードさせます。
web-app/src/main/java/org/littlewings/servlet/HelloWebinfJspServlet.java

package org.littlewings.servlet;

import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/webinf/jsp")
public class HelloWebinfJspServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        request.setAttribute("word", "***Hello***");

        RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/view/hello-webinf.jsp");
        rd.forward(request, response);
    }
}

こちらは、WARファイル側とJARファイル側の両方にあるJSPにフォワードさせます。
web-app/src/main/java/org/littlewings/servlet/HelloWebinfDuplicateJspServlet.java

package org.littlewings.servlet;

import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/webinf/duplicate-jsp")
public class HelloWebinfDuplicateJspServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        request.setAttribute("word", "***Hello***");

        RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/view/hello-webinf-duplicate.jsp");
        rd.forward(request, response);
    }
}

動作確認

それでは、パッケージングして動作確認してみましょう。

$ mvn package

Webアプリケーション側のモジュールに、「ROOT.war」というファイルができあがります。WARファイルの中には、「WEB-INF/lib」
ディレクトリ内にJSPやテキストファイルを入れたJARファイルが置かれています。

$ jar -tvf web-app/target/ROOT.war
  2941 Sat Jan 20 17:52:12 JST 2018 WEB-INF/lib/jsp-files-0.0.1-SNAPSHOT.jar

これを、今回はTomcatとWildFlyで確認してみます。

まずはTomcat 8.5.24を使って確認していきましょう。

JARファイル内にのみ存在するテキストファイル。

対象のファイル。
jsp-files/src/main/resources/META-INF/resources/hello.txt

Hello World!!

確認。

$ curl http://localhost:8080/hello.txt
Hello World!!

表示できました。

JARファイルとWARファイルの両方に存在するテキストファイル。

JARファイル側。
jsp-files/src/main/resources/META-INF/resources/hello-duplicate.txt

Hello World!!!!

WARファイル側。
web-app/src/main/webapp/hello-duplicate.txt

Hello World!!!!!!!!

確認。

$ curl http://localhost:8080/hello-duplicate.txt
Hello World!!!!!!!!

WARファイル側の方が優先されていますね。

ルート配下にあり、かつJARファイル内にのみあるJSPファイル

対象のファイル。
jsp-files/src/main/resources/META-INF/resources/hello.jsp

Hello JSP in JAR

確認。

$ curl http://localhost:8080/hello.jsp
Hello JSP in JAR

JSPを認識して動きました。

ルート配下にあり、かつJARファイルにもWARファイルにも両方存在するJSPファイル

JARファイル側。
jsp-files/src/main/resources/META-INF/resources/hello-duplicate.jsp

Hello JSP!! in JAR

WARファイル側。
web-app/src/main/webapp/hello-duplicate.jsp

Hello JSP!!!! in WAR

確認。

$ curl http://localhost:8080/hello-duplicate.jsp
Hello JSP!!!! in WAR

WARファイル側が表示されています。

WEB-INF配下にあり、JARファイル側に存在するJSPファイル

jsp-files/src/main/resources/META-INF/resources/WEB-INF/view/hello-webinf.jsp

${word} JSP in JAR(WEB-INF/view)

EL式を使いつつ、Servletからフォワードして使います。

確認。

$ curl http://localhost:8080/webinf/jsp
***Hello*** JSP in JAR(WEB-INF/view)

ちゃんと動きました。

WEB-INF配下にあり、JARファイルとWARファイルの両方に存在するJSPファイル

JARファイル側。
jsp-files/src/main/resources/META-INF/resources/WEB-INF/view/hello-webinf-duplicate.jsp

${word} JSP!! in JAR(WEB-INF/view)

WARファイル側。
web-app/src/main/webapp/WEB-INF/view/hello-webinf-duplicate.jsp

${word} JSP!!!! in WAR(WEB-INF/view)

確認。

$ curl http://localhost:8080/webinf/duplicate-jsp
***Hello*** JSP!!!! in WAR(WEB-INF/view)

WARファイル側が表示されました、と。

最後に、WildFly 11.0.0.Finalで確認してみます。こちらは、結果だけ。

$ curl http://localhost:8080/hello.txt
Hello World!!


$ curl http://localhost:8080/hello-duplicate.txt
Hello World!!!!!!!!


$ curl http://localhost:8080/hello.jsp
Hello JSP in JAR


$ curl http://localhost:8080/hello-duplicate.jsp
Hello JSP!!!! in WAR


$ curl http://localhost:8080/webinf/jsp
***Hello*** JSP in JAR(WEB-INF/view)


$ curl http://localhost:8080/webinf/duplicate-jsp
***Hello*** JSP!!!! in WAR(WEB-INF/view)

Tomcatと、同じ結果になりましたね。

ちょっと覚えておきましょう。