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
##依存関係の宣言
dependencies {
providedCompile 'javax:javaee-api:7.0'
providedCompile 'org.glassfish.jersey.ext:jersey-mvc-jsp:2.0'
}
ビルドには Gradle を使うので、その依存関係の宣言。
##HelloWorld
とりあえず Hello World する。
###Application クラスを作成する
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 を登録する
<?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>
###リソースクラスを作成する
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)を作成する
<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
にアクセスする。
※ホスト名、ポート、コンテキストルートは適宜読み替えてください。
###説明
####Application クラスで MVC JSP を有効にする
単純に JAX-RS を使う場合は、 javax.ws.rs.core.Application
を継承したクラスを作成するが、 Jersey MVC を使う場合は Jersey が提供している Application クラスのサブクラスである ResourceConfig
を継承して Application クラスを作成する。
ResourceConfig
には JAX-RS や Jersey 用の設定を簡単に定義するためのメソッドとかが用意されている。
Jersey MVC を有効にするには、このクラスのコンストラクタで以下のように実装する。
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 クラスのインスタンスを返すようにしている。
@GET
public Viewable helloWorld() {
return new Viewable("/hello-mvc");
}
Viewable のコンストラクタに渡している文字列が、 View を指定するためのパス。
/
で始まっているので、 Web コンテンツフォルダのルートからの絶対パスになる。
末尾に拡張子 .jsp
が無い場合は Jersey が勝手に .jsp
を末尾に付けてテンプレートファイルを検索するので、 /hello-mvc
は、 Web コンテンツフォルダ直下の hello-mvc.jsp
ファイルを表すことになる。
##View に値を渡す
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);
}
}
<html>
<head>
<title>Hello Jersey MVC</title>
</head>
<body>
<h1>Model : ${it.hoge}, ${it.fuga}, ${it.piyo}</h1>
</body>
</html>
Viewable のコンストラクタの第二引数に指定した値は、 JSP 上で it
で参照できるようになる。
##View の解決方法
###絶対パス指定
/
で始まるパスは 絶対パス と判断され、 Web コンテンツフォルダのルートからのパスとして解釈される。
###相対パス指定
/
で始まらないパスは 相対パス と判断され、 <Web コンテンツフォルダ>/<リソースクラスのパッケージと同じ階層>/<リソースクラス名>/
からの相対パスとして解釈される。
例えば次のようなリソースクラスがあったとする。
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 を指定する
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 アノテーションを設定する
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";
}
}
<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
アノテーションを付与されたリソースクラスには、 Jersey が自動的に以下のリソースメソッドを追加する。
@GET
public Viewable get() {
return new Viewable("index", this);
}
このため、リソースメソッドを定義することなく画面遷移が実現できている。
Model にはリソースクラスのインスタンスが指定されている(this
)ので、 JSP 上での it
はリソースインスタンスを指している。
さらに、 Jersey は以下のようなリソースメソッドも自動で追加する。
@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 ファイルを配置し、
<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 テンプレートを検索するときのルートを変更する
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 と連携できる。
package sample.jersey.mvc.jsp;
public class MyService {
public void method() {
System.out.println("MyService method.");
}
}
package sample.jersey.mvc.jsp;
import javax.ejb.Stateless;
@Stateless
public class MyEjb {
public void method() {
System.out.println("MyEjb method.");
}
}
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 ではサポートされてなかった使い方について試す。
##依存関係の宣言
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");
}
}
<html>
<head>
<title>Error Page</title>
</head>
<body>
<h1>Error Page</h1>
</body>
</html>
リソースメソッドにアクセスした結果
@ErrorTemplate
アノテーションを使えば、例外が発生したときの遷移先を指定できる。
@ErrorTemplate
アノテーションは、 ver 2.3 で利用できる。
##Model の参照
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);
}
}
<html>
<head>
<title>Jersey MVC</title>
</head>
<body>
<h1>Model : ${model.hoge}, ${model.fuga}, ${model.piyo}</h1>
</body>
</html>
View に渡したモデルは、 it
だけでなく、 model
でも参照できるようになっている。
#2.6 より前の JSP モジュールには文字コードの処理でバグがある
どうも 2.6 より前の JSP モジュールはレスポンスを書き出すときに文字コードを明示していないため、環境デフォルトの文字コードを使ってしまっているらしい。
このため、例えば 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 の文字コード>
を指定する。
#参考