JAX-RSのレスポンス

このエントリはJava EE Advent Clanedar2014の16日目の記事です。
昨日は@さんの「Arquillian Cube を試す #javaee」でした。
明日は@さんです。

【概要】

JavaEEというと私の中でJAX-RSが良いと思っているので、
JAX-RSのレスポンスについてまとめてみました。

今回はGlassFish4.1にデプロイして実行します。

【前提条件】

[環境]
[参考資料]

JerseyMVC Template
Jersey

【事前準備】

[pom.xml]

mavendependencyは下記のとおりです。

<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">

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>
[Application]

javax.ws.rs.ApplicationPathの設定はパスの設定だけです。
今回は コンテキストパス + "/api"をJAX-RSのパスの設定とします。

package jp.glory.advent2014;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/api")
public class ApplicationSetting extends Application {

}
[データ用Bean]

レスポンスとして返却するためのBeanです。
XMLとして返すので@XmlRootElementをつけます。

lastNameとfirstNameはエレメントなのでセッターに@XmlElementをつけています。

package jp.glory.advent2014;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class TestData {

    private final String lastName = "Yamada";

    private final String firstName = "Taro";

    @XmlElement
    public String getLastName() {
        return lastName;
    }

    @XmlElement
    public String getFirstName() {
        return firstName;
    }
}

【シンプルな形で返す】

JAX-RSは特に難しい設定をすることなく、JSON/XMLなどで返却することができます。
レスポンスのTypeは@Producesで指定します。

package jp.glory.advent2014.resources;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/hoge")
public class ResponseResource {

    @GET
    @Path("/default1")
    public TestData getDefault1() {

        return new TestData();
    }

    @GET
    @Path("/default2")
    public String getDefault2() {

        return "aaa";
    }

    @GET
    @Path("/json")
    @Produces(MediaType.APPLICATION_JSON)
    public TestData getJson() {

        return new TestData();
    }

    @GET
    @Path("/xml")
    @Produces(MediaType.APPLICATION_XML)
    public TestData getXml() {

        return new TestData();
    }
}

アノテーションを設定する以外は特に必要ありません。

何も指定しなかった場合、型によって判定されます。
今回はBeanに@XmlRootElementを指定しているのでXMLが返却されます。
文字列の場合はHTMLとして返却されます。

http://localhost:8080/${contetPath}/api/hoge/xxx」にアクセスするとレスポンスが帰ってくると思います。
(xxxには「default1」、「default2」などを入れてください)

【JerseyMVC】

ここからはJAX-RSの実装の一つであるJerseyMVCの独自機能性になりますが、
JAX-RSで従来のMVCパターンを実現することができます。

[pom.xml]

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>

    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.ext</groupId>
            <artifactId>jersey-mvc-jsp</artifactId>
            <version>2.12</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
    </dependencies>
</project>

dependenciesをJeseyMVCとServleet2.5に変更しています。

[Application]

先ほど作成したApplicationSettingには少しだけJerseyMVC独自のコードを書きます。

package jp.glory.advent2014;

import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.mvc.jsp.JspMvcFeature;

@ApplicationPath("/api")
public class ApplicationSetting extends ResourceConfig {

    public ApplicationSetting() {

        packages(this.getClass().getPackage().getName());

        register(JspMvcFeature.class);
    }
}

まずは継承するクラスをResourceConfigに変更します。

コンストラクタ内部では初期設定を行います。

packagesメソッドでリソースクラスがどのパッケージにあるかを指定します。
今回は「jp.glory.advent2014」の下全てと言うことになります。

registerメソッドでJerseyMVCのコンポーネントを登録します。
今回はJSPと連携するためのJspMvcFeatureクラスだけを登録しています。

[web.xml]

JerseyMVCを使用する場合にはweb.xmlの設定が必要になります。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <filter>
        <filter-name>AdventSampleFilter</filter-name>
        <filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>jp.glory.advent2014.ApplicationSetting</param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.servlet.filter.contextPath</param-name>
            <param-value>api</param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.server.mvc.templateBasePath.jsp</param-name>
            <param-value>/WEB-INF</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>AdventSampleFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

FilterとしてJerseyのServletContainerを設定します。

初期パラメータは設定値は下のようになります。

パラメータ名 設定内容
javax.ws.rs.Application ResourceConfigクラスを継承したクラスのフルネームを設定します。
jersey.config.servlet.filter.contextPath ApplicationPathで指定した内容を設定します。
jersey.config.server.mvc.templateBasePath.jsp JSPファイルの配置場所を設定します
[JSP]

JSPはWEB-INF直下におきます。

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <dl>
            <dt>LastName</dt>
            <dd>${it.lastName}</dd>
            <dt>FirstName</dt>
            <dd>${it.firstName}</dd>
        </dl>
    </body>
</html>

JAX-RSから渡されるオブジェクトはitと言う名前で登録されるので、
JSPでは${it.hoge}でアクセスするようにします。

[リソースクラス]

最後にリソースクラスの修正です。
JSP用のURLとして「/api/hoge/jsp」を作成します。

package jp.glory.advent2014.resources;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import jp.glory.advent2014.TestData;
import org.glassfish.jersey.server.mvc.Viewable;

@Path("/hoge")
public class ResponseResource {

    // 先ほど作成したメソッドは変わっていないので省略

    @GET
    @Path("/jsp")
    public Viewable getJsp() {

        return new Viewable("/sample.jsp", new TestData());
    }
}

Viewableのオブジェクトに
JSPファイルのパスとモデルオブジェクト(今回はTestDataクラスのオブジェクト)を渡すだけです。

http://localhost:8080/Advent2014/api/hoge/jsp」にアクセスすると
モデルオブジェクトで設定した値が反映され、画面が表示されます。

この状態で「/api/hoge/default1」などにアクセスしても
JSPと連携する前と同じ結果が返ってきます。

【まとめ】

このようにJAX-RSは主な用途であるREST APIを簡単に実装でき、
さらにJerseyMVCを使えばリソースクラスのつくりをほとんど変えずにJSPとも連携ができます。

あくまでJerseyMVCであって、正式なJava EEの仕様ではありませんが、
個人的にはJavaでWebと言った場合に
最初に検討しても良いフレームワークなのかなと思います。

JerseyMVCで提供されているのは Mustache/Freemarker/JSPのみですが、
他のテンプレートエンジンにも適用することができます。
JerseyMVCとThymeleafを組み合わせる - シュンツのつまづき日記

また、MVC1.0(JSR371)でJAX-RSと組み合わせられるようにするかという議論もあるようです。

個人的にはMVC1.0がJAX-RSと組み合わせやすいようになれば
さらにJAX-RSを採用する理由が増えるのかなと思っています。