JerseyMVCの使い方メモ

  • 52
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

JAX-RS の仕様に組み込んで欲しいという意見をちらほら見かける、噂の Jersey MVC を試す。

テンプレートには JSP を使い、とりあえず GlassFish で ver 2.0 を試してから、 Tomcat で 2014/01/26 現在最新の ver 2.5.1 を試してみる。

環境

AP サーバ

GlassFish

4.0

Tomcat

7.0.42

GlassFish で ver 2.0 を試す

Jersey MVC JSP はバンドルされている

GlassFish 4.0 には、 Jersey MVC JSP の 2.0 がバンドルされているので、それを利用する。

glassfish\modules>dir /b jersey-mvc*
jersey-mvc-connector.jar
jersey-mvc-jsp.jar          ← MVC JSP がバンドルされてる
jersey-mvc.jar

依存関係の宣言

build.gradle
dependencies {
    providedCompile 'javax:javaee-api:7.0'
    providedCompile 'org.glassfish.jersey.ext:jersey-mvc-jsp:2.0'
}

ビルドには Gradle を使うので、その依存関係の宣言。

HelloWorld

とりあえず Hello World する。

Application クラスを作成する

MyApplication.java
package sample.jersey.mvc.jsp;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.mvc.MvcFeature;
import org.glassfish.jersey.server.mvc.jsp.JspMvcFeature;

public class MyApplication extends ResourceConfig {

    public MyApplication() {
        this.packages(MyApplication.class.getPackage().getName())
            .register(JspMvcFeature.class);
    }
}

web.xml に Filter を登録する

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>Jersey MVC JSP Sample</display-name>

  <filter>
    <filter-name>Jersey MVC JSP Filter</filter-name>
    <filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
    <init-param>
      <param-name>javax.ws.rs.Application</param-name>
      <param-value>sample.jersey.mvc.jsp.MyApplication</param-value>
    </init-param>
    <!-- pass to next filter if Jersey/App returns 404 -->
    <init-param>
      <param-name>jersey.config.servlet.filter.forwardOn404</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>Jersey MVC JSP Filter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

リソースクラスを作成する

MyResource.java
package sample.jersey.mvc.jsp;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.glassfish.jersey.server.mvc.Viewable;

@Path("hello-mvc")
public class MyResource {

    @GET
    public Viewable helloWorld() {
        return new Viewable("/hello-mvc");
    }
}

View(JSP)を作成する

hello-mvc.jsp
<html>
  <head>
    <title>Hello Jersey MVC</title>
  </head>
  <body>
    <h1>Hello Jersey MVC !!</h1>
  </body>
</html>

配置する場所は、 Web コンテンツフォルダ直下。

動作確認する

アプリを GlassFish に配備して、ブラウザから http://localhost:8080/jersey-mvc-jsp-sample/hello-mvc にアクセスする。

※ホスト名、ポート、コンテキストルートは適宜読み替えてください。

JerseyMVCHelloWorld.jpg

説明

Application クラスで MVC JSP を有効にする

単純に JAX-RS を使う場合は、 javax.ws.rs.core.Application を継承したクラスを作成するが、 Jersey MVC を使う場合は Jersey が提供している Application クラスのサブクラスである ResourceConfig を継承して Application クラスを作成する。

ResourceConfig には JAX-RS や Jersey 用の設定を簡単に定義するためのメソッドとかが用意されている。

Jersey MVC を有効にするには、このクラスのコンストラクタで以下のように実装する。

MyApplication.java
    public MyApplication() {
        this.register(JspMvcFeature.class);
    }

JSP 以外にも標準だと Mustache と Freemarker がサポートされているみたいだけど、今はとりあえず JSP で。

MVC JSP を使う場合は web.xml が必須

18.6.3. JSP | Chapter 18. MVC Templates

Limitations of Jersey JSP MVC Templates

Jersey web applications that want to use JSP templating support should be registered as Servlet filters rather than Servlets in the application's web.xml. The web.xml-less deployment style introduced in Servlet 3.0 is not supported at the moment for web applications that require use of Jersey MVC templating support.

【訳】
Jersey JSP MVC テンプレートの制約

Jersey Web アプリケーションで JSP テンプレートを使いたい場合は、 ServletFilter をアプリケーションの web.xml に登録しないといけません。Servlet 3.0 で紹介されている web.xml 無しのデプロイスタイルは、 Jersey MVC テンプレートサポートを使用する Web アプリケーションでは今のところサポートされていません。

ということで、 Filter を web.xml に定義する必要がある。

リソースメソッドで Viewable を返すようにする

表示する View を指定するため、リソースメソッドで Viewable クラスのインスタンスを返すようにしている。

MyResource.java
    @GET
    public Viewable helloWorld() {
        return new Viewable("/hello-mvc");
    }

Viewable のコンストラクタに渡している文字列が、 View を指定するためのパス。

/ で始まっているので、 Web コンテンツフォルダのルートからの絶対パスになる。

末尾に拡張子 .jsp が無い場合は Jersey が勝手に .jsp を末尾に付けてテンプレートファイルを検索するので、 /hello-mvc は、 Web コンテンツフォルダ直下の hello-mvc.jsp ファイルを表すことになる。

View に値を渡す

MyResource.java
package sample.jersey.mvc.jsp;

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.glassfish.jersey.server.mvc.Viewable;

@Path("hello-mvc")
public class MyResource {
    @GET
    public Viewable helloWorld() {
        Map<String, String> model = new HashMap<>();
        model.put("hoge", "HOGE");
        model.put("fuga", "FUGA");
        model.put("piyo", "PIYO");

        return new Viewable("/hello-mvc", model);
    }
}
hello-mvc.jsp
<html>
  <head>
    <title>Hello Jersey MVC</title>
  </head>
  <body>
    <h1>Model : ${it.hoge}, ${it.fuga}, ${it.piyo}</h1>
  </body>
</html>

model.jpg

Viewable のコンストラクタの第二引数に指定した値は、 JSP 上で it で参照できるようになる。

View の解決方法

絶対パス指定

/ で始まるパスは 絶対パス と判断され、 Web コンテンツフォルダのルートからのパスとして解釈される。

相対パス指定

/ で始まらないパスは 相対パス と判断され、 <Web コンテンツフォルダ>/<リソースクラスのパッケージと同じ階層>/<リソースクラス名>/ からの相対パスとして解釈される。

例えば次のようなリソースクラスがあったとする。

HogeResource.java
package hoge.fuga;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.glassfish.jersey.server.mvc.Viewable;

@Path("piyo")
public class PiyoResource {

    @GET
    public Viewable get() {
        return new Viewable("view-template");
    }
}

get() メソッドが返している Viewable で指定されている view-template は、 <Web コンテンツフォルダ>/hoge/fuga/PiyoResource/view-template.jsp と解釈される。

@Template アノテーションで View を指定する

MyResource.java
package sample.jersey.mvc.jsp;

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.glassfish.jersey.server.mvc.Template;

@Path("hello-mvc")
public class MyResource {

    @GET
    @Template(name="/hello-mvc")
    public Map<String, String> helloWorld() {
        Map<String, String> model = new HashMap<>();
        model.put("hoge", "HOGE");
        model.put("fuga", "FUGA");
        model.put("piyo", "PIYO");

        return model;
    }
}

Viewable を return する代わりに、メソッドに @Template アノテーションを付与して、 name 属性※で View を指定する方法もある。

この場合、リソースメソッドの戻り値が View に渡される。

リソースクラスに @Template アノテーションを設定する

MyResource.java
package sample.jersey.mvc.jsp;

import javax.ws.rs.Path;

import org.glassfish.jersey.server.mvc.Template;

@Path("hello-mvc")
@Template
public class MyResource {

    public String getHoge() {
        return "HOGE";
    }
}
Webコンテンツフォルダ/sample/jersey/mvc/jsp/MyResource/index.jsp
<html>
  <head>
    <title>index.jsp</title>
  </head>
  <body>
    <h1>${it.hoge}</h1>
  </body>
</html>

@Template アノテーションをクラスに付与すると、暗黙的なルールがいくつも追加される。

リソースメソッドが1つも定義されていないが、この状態で http://localhost:8080/jersey-mvc-jsp-sample/hello-mvc にアクセスすると、次のように画面が開く。

templateをクラスに指定.jpg

@Template アノテーションを付与されたリソースクラスには、 Jersey が自動的に以下のリソースメソッドを追加する。

暗黙的に追加されるリソースメソッド
@GET
public Viewable get() {
    return new Viewable("index", this);
}

このため、リソースメソッドを定義することなく画面遷移が実現できている。

Model にはリソースクラスのインスタンスが指定されている(this)ので、 JSP 上での it はリソースインスタンスを指している。

さらに、 Jersey は以下のようなリソースメソッドも自動で追加する。

暗黙的に追加されるリソースメソッド2
@GET
@Path("{implicit-view-path-parameter}")
public Viewable get(@PathParameter("{implicit-view-path-parameter}") String template) {
    return new Viewable(template, this);
}

つまり、例えば Webコンテンツフォルダ/sample/jersey/mvc/jsp/MyResource/ に次のような jsp ファイルを配置し、

Webコンテンツフォルダ/sample/jersey/mvc/jsp/MyResource/implicit-view.jsp
<html>
  <head>
    <title>implicit-view.jsp</title>
  </head>
  <body>
    <h1>Implicit View</h1>
  </body>
</html>

ブラウザから http://localhost:8080/jersey-mvc-jsp-sample/hello-mvc/implicit-view にアクセスすると、↑の JSP が表示される。

暗黙的なView.jpg

View テンプレートを検索するときのルートを変更する

MyApplication.java
package sample.jersey.mvc.jsp;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.mvc.jsp.JspMvcFeature;
import org.glassfish.jersey.server.mvc.jsp.JspProperties;

public class MyApplication extends ResourceConfig {

    public MyApplication() {
        this.packages(MyApplication.class.getPackage().getName())
            .register(JspMvcFeature.class)
            .property(JspProperties.TEMPLATES_BASE_PATH, "jsp");
    }
}

Application クラスで property(String, String) メソッドを使って JspProperties.TEMPLATES_BASE_PATH の値を設定すれば、 View のテンプレートファイルを検索するときのルートを変更することができる。

上記実装の場合、ルートは <Web コンテンツフォルダ>/jsp に変更される。

CDI, EJB との連携

特に設定などはなしに、リソースクラスは CDI, EJB と連携できる。

MyService.java
package sample.jersey.mvc.jsp;

public class MyService {

    public void method() {
        System.out.println("MyService method.");
    }
}
MyEjb.java
package sample.jersey.mvc.jsp;

import javax.ejb.Stateless;

@Stateless
public class MyEjb {

    public void method() {
        System.out.println("MyEjb method.");
    }
}
MyResource.java
package sample.jersey.mvc.jsp;

import javax.ejb.EJB;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("hello-mvc")
public class MyResource {

    @Inject
    private MyService service;
    @EJB
    private MyEjb ejb;

    @GET
    public void method() {
        this.service.method();
        this.ejb.method();
    }
}

http://localhost:8080/jersey-mvc-jsp-sample/hello-mvc にアクセスしたときの標準出力。

出力結果
情報: MyService method.
情報: MyEjb method.

Tomcat で ver 2.5.1 を試す

Tomcat には Jersey はバンドルされてないので、最新版の 2.5.1 を試す。
基本的な使い方は GlassFish で試したのと同じ実装で問題ない。

ここでは、 ver 2.0 ではサポートされてなかった使い方について試す。

依存関係の宣言

build.gradle
dependencies {
    providedCompile 'org.apache.tomcat:tomcat-servlet-api:7.0.42'
    compile 'org.glassfish.jersey.ext:jersey-mvc-jsp:2.5.1'
}

@ErrorTemplate アノテーションで例外発生時の遷移先を指定する

package sample.jersey.mvc.jsp;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.glassfish.jersey.server.mvc.ErrorTemplate;

@Path("mvc")
public class MyResource {

    @GET
    @ErrorTemplate(name="/error.jsp")
    public void get() throws Exception {
        throw new Exception("test exception");
    }
}
error.jsp
<html>
  <head>
    <title>Error Page</title>
  </head>
  <body>
    <h1>Error Page</h1>
  </body>
</html>

リソースメソッドにアクセスした結果

エラーページ.jpg

@ErrorTemplate アノテーションを使えば、例外が発生したときの遷移先を指定できる。

@ErrorTemplate アノテーションは、 ver 2.3 で利用できる。

Model の参照

MyResource.java
package sample.jersey.mvc.jsp;

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.glassfish.jersey.server.mvc.Viewable;

@Path("mvc")
public class MyResource {

    @GET
    public Viewable get() throws Exception {
        Map<String, String> model = new HashMap<>();
        model.put("hoge", "HOGE");
        model.put("fuga", "FUGA");
        model.put("piyo", "PIYO");

        return new Viewable("/test.jsp", model);
    }
}
test.jsp
<html>
  <head>
    <title>Jersey MVC</title>
  </head>
  <body>
    <h1>Model : ${model.hoge}, ${model.fuga}, ${model.piyo}</h1>
  </body>
</html>

modelで参照.jpg

View に渡したモデルは、 it だけでなく、 model でも参照できるようになっている。

2.6 より前の JSP モジュールには文字コードの処理でバグがある

どうも 2.6 より前の JSP モジュールはレスポンスを書き出すときに文字コードを明示していないため、環境デフォルトの文字コードを使ってしまっているらしい。

Jersey に上げられているバグ報告 > No way to specify an output encoding of jersey-mvc-jsp extension module (Always default encoding is used)

このため、例えば Shift_JIS がデフォルト文字コードの Windows 環境下だと、たとえ JSP で

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

というふうに文字コードをしっかり指定していても、文字化けが発生してしまう。

このバグは 2.6 で fix しているみたいなので、それ以前のバージョンを使用する場合は以下のいずれかの対応が必要になる。

  • 環境のデフォルトエンコーディングに合わせて JSP の文字コードを指定する。
  • AP サーバーを起動するときの JVM オプションで -Dfile.encoding=<JSP の文字コード> を指定する。

参考