Maven
Eclipse
rest
JAX-RS
Liberty

LibertyによるWebサービスアプリ開発メモ: (2)MavenプロジェクトによるJAX-RSアプリ開発

はじめに

Webサービス(REST)アプリを開発してLiberty上で動かすまでの流れについて、試したことを備忘録として記載していきます。
最終的にはEclipse, Liberty, JAX-RS, GitHub, Maven, JUnit, ...あたりを使ってRESTアプリの開発、テスト、デプロイの流れを作れるようにすることが目標。
今回はMavenプロジェクトにて単純なJAX-RSアプリを作って動かす所まで。

関連記事

LibertyによるWebサービスアプリ開発メモ: (1)環境構築
LibertyによるWebサービスアプリ開発メモ: (2)MavenプロジェクトによるJAX-RSアプリ開発
LibertyによるWebサービスアプリ開発メモ: (3)JPA経由でのDBアクセス
LibertyによるWebサービスアプリ開発メモ: (4)ロギング
LibertyによるWebサービスアプリ開発メモ: (5)JUnitによる単体テスト
LibertyによるWebサービスアプリ開発メモ: (6)Eclipse-GitHub連携

サンプルアプリ作成

ここでは、REST形式でアクセスできるWebサービスをJAX-RSを用いて実装することを想定します。また、実行環境はLibertyを想定しています。

JAX-RSによる実装についてはこの辺を参考に...
参考:Getting started with IBM JAX-RS

Mavenプロジェクト作成

依存関係の解決、テストなどを見据えて、ここではMavenを使ってみることにします。

[File] - [New] - [Maven Project]を選択
Next
image.png

webApp-jee7-libertyを選択
image.png

GroupID, ArtifactID, libertyVersionを指定
image.png

MavenLibertyTest01というプロジェクトが作成される。
image.png

このプロジェクトのpom.xmlを開くと、Javaのバージョンがsource,targetともに1.7になっているので、1.8に変更する。

pom.xml
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.0.2</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>

また、dependencyにjavaee-apiを追加する。(デフォルトではliberty-targetのみ)

pom.xml
  <dependencies>
        <dependency>
        <groupId>net.wasdev.maven.tools.targets</groupId>
        <artifactId>liberty-target</artifactId>
        <version>17.0.0.2</version>
        <type>pom</type>
        <scope>provided</scope>
    </dependency>
    <dependency>
            <groupId>javax</groupId> 
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>        
            <scope>provided</scope>
    </dependency>
  </dependencies>

pom.xmlを変更したら、プロジェクトを右クリック - [Maven] - [Update Project]を選択し、project configurationのアップデートを行う。
image.png
=>BuildPathなどが更新される。

Webサービスアプリ作成

Java Resourcesの下のsrc/main/javaにcom.ibm.jaxrs.sampleというパッケージを作成し、HelloWorldResource01.java, HelloWorldAppConfig.javaを作成する。
image.png

まず、サービスのロジック部分を実装するHelloWorldResource01.javaを作成

HelloWorldResource01.java
package com.ibm.jaxrs.sample;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import com.ibm.json.java.JSONObject;

@Path("/helloworld")
public class HelloWorldResource01 {

  @GET
  @Path("/sayHelloWorld")
  public String sayHelloWorld() {
    return "Hello World!";
  }

  @GET
  @Path("/getQueryParam")
  @Produces("application/json")
  public Response getQueryParam(@DefaultValue("XXX") @QueryParam("name1") final String name1,
      @DefaultValue("YYY") @QueryParam("name2") final String name2) {

    Response response = null;
    String strData = "Hello " + name1 + " " + name2;

    System.out.println(strData);

    response = Response.status(Response.Status.OK).entity(new JSONObject()).build();

    return response;
  }
}

ここでは、sayHelloWorld()、getQueryParam()という2つのメソッドを定義し、それぞれ「xxx/helloworld/sayHelloWorld」, 「xxx/helloworld/getQueryParam」 というURLでアクセスする想定です。
1つ目は単純にHello World!という文字列を返すだけのもの。
2つ目は、Queryパラメーターとしてname1, name1を受け取ってメッセージを標準出力に出すもの。(戻りは空のJSONデータ)

次に、このクラスをWebアプリケーションとして登録するためのクラスを定義します。

HelloWorldAppConfig.java
package com.ibm.jaxrs.sample;

import java.util.HashSet;
import java.util.Set;

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

@ApplicationPath("/rest/*")
public class HelloWorldAppConfig extends Application {

  @Override
  public Set<Class<?>> getClasses() {
    Set<Class<?>> classes = new HashSet<Class<?>>();
    classes.add(com.ibm.jaxrs.sample.HelloWorldResource01.class);
    return classes;
  }
}

javax.ws.rs.core.Applicationを継承したクラスとして作成し、OverrideしたgetClasses()メソッドで対象となるサービスを実装したクラス(HelloWorldResource01)を追加します。
@ApplicationPathアノテーションでは、このアプリケーションで管理するURLの先頭部分を指定することになります。
すなわち、実際には「xxx/rest/helloworld/sayHelloWorld」というURLでアクセスすることになります。
(@ApplicationPathアノテーションを指定しない場合は、web.xmlでservlet-mappingにてPathを指定する必要があります。)

web.xmlの設定

(@ApplicationPathアノテーションを指定している場合は不要)
実装したWebサービス用のクラスは、SIBM JAX-RS REST servletとして登録する必要があります。MavenプロジェクトのDeployed Resources - webapp - WEB-INF 以下にあるweb.xmlに、以下の指定を追加します。
image.png

web.xml
    <servlet>
        <servlet-name>HelloWorldApp</servlet-name>
        <servlet-class>com.ibm.websphere.jaxrs.server.IBMRestServlet</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.ibm.jaxrs.sample.HelloWorldAppConfig</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

サービスの確認

ここまで設定すると、プロジェクトのServices-REST以下に作成したサービスの情報が確認できます。
image.png

サービスを右クリック-[Show URI]を選択
image.png

このサービスにアクセスするためのURLが確認できます。
image.png

稼働確認

Libertyへのデプロイ

※単体テストの自動化なども想定すると、ビルド/デプロイ辺りもMaven使ってやれるとよいですが、一旦ここでは手動でデプロイして動作確認します。(Mavenの活用はおいおい...)

ServerビューでLibertyサーバー右クリック - [Add and Remove]選択
image.png

対象のプロジェクトを選択してAdd, Finish
image.png

サービス呼び出し

Liberty Serverを起動

ブラウザから、「http://localhost:9080/MavenLibertyTest01/rest/helloworld/sayHelloWorld 」にアクセスしてみる。
image.png
Hello World!が表示された!

ブラウザから、「http://localhost:9080/MavenLibertyTest01/rest/helloworld/getQueryParam?name1=aaa&name2=bbb 」 にアクセスしてみる。
image.png
結果は空。
Libertyのコンソールを見ると、Queryパラメーターで指定した値がきちんと表示されている。

Libertyコンソール
Hello aaa bbb

一通り動作確認できました。

アプリの拡張

さらに、JSONデータを返すサービスを追加してみます。

返信するJSONデータの構造を表すJavaBeanを作成します。

ResponseData01.java
package com.ibm.jaxrs.sample.dao;

public class ResponseData01 {
  private String strResult = "";
  private String strData = "";

  public ResponseData01(String strResult, String strData) {
    this.strResult = strResult;
    this.strData = strData;
  }

  public String getStrResult() {
    return strResult;
  }

  public void setStrResult(String strResult) {
    this.strResult = strResult;
  }

  public String getStrData() {
    return strData;
  }

  public void setStrData(String strData) {
    this.strData = strData;
  }

}

strResultとstrDataという文字列型の2つのフィールドを持つだけのJavaBeanです。
それぞれprivateのフィールド定義とgetter,setterを定義します。

次に、これを結果として返すサービスを追加します。今回は別のクラスを新たに追加してみます。

HelloWorldResource02.java
package com.ibm.jaxrs.sample;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;

import com.ibm.jaxrs.sample.dao.ResponseData01;


@Path("/helloworld02")
public class HelloWorldResource02 {


  @GET
  @Path("/sayHelloWorld02")
  @Produces("application/json")
  public Response sayHelloWorld02(@DefaultValue("XXX") @QueryParam("name1") final String name1,
      @DefaultValue("YYY") @QueryParam("name2") final String name2) {

    Response response = null;

    String strData = "Hello " + name1 + " " + name2;
    ResponseData01 responseData = new ResponseData01("OK", strData);

    response = Response.status(Response.Status.OK).entity(responseData).build();

    return response;

  }
}

クラスが追加されたので、HelloWorldAppConfigのgetClass()にも追加します。

HelloWorldAppConfig.java
package com.ibm.jaxrs.sample;

import java.util.HashSet;
import java.util.Set;

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

@ApplicationPath("/rest/*")
public class HelloWorldAppConfig extends Application {

  @Override
  public Set<Class<?>> getClasses() {
    Set<Class<?>> classes = new HashSet<Class<?>>();
    classes.add(com.ibm.jaxrs.sample.HelloWorldResource01.class);
    classes.add(com.ibm.jaxrs.sample.HelloWorldResource02.class);
    return classes;
  }
}

追加したサービスを実行してみると...
image.png
きちんとJSON形式のデータが返されていることが確認できました。

おまけ

JAX-RSでのJSONのハンドリングについて

JAX-RSではRESTのRequest/ResponseデータとしてJSONを扱うことができる(JavaBeanとのマッピングを行ってくれる)のですが、それが何の仕組みによるものかがちょっと分かりにくかったので補足します。
最初はJAX-RSで規定されているものかと思っていましたがどうもそうでは無いらしい。そもそもJAX-RSは単なる仕様であって、実際に使おうと思ったらそれを実装してくれているモノが必要になります。例えばJAX-RSのリファレンス実装になっているJersey(オープンソース)なんかがあります。今回はLibertyをランタイムとして想定していますが、LibertyもJAX-RSの実装を提供してくれています。ということでLibertyのドキュメントを探していると、以下のような記述がありました。

参考: Using JSON content in JAX-RS application requests and responses

JSON is a popular data format that is programming-language neutral. Multiple web browsers and JavaScript libraries such as Dojo provide support for JSON. WebSphere® supports the Jackson and JavaScript Object Notation (JSON4J) libraries. The Jackson library is a JSON processor (both parser and generator) based on streaming API for XML (StAX) pull parser technology. It provides basic JSON reading and writing (parsing and generating), a full node-based tree model, as well as object-to-JSON data binding. You can use the Jackson library to unmarshal and marshal JSON data to and from Plain Old Java Objects (POJOs) and Java Architecture for XML Binding (JAXB) objects. To use Jackson to process JSON data, you must include the Jackson libraries in the class path. After adding Jackson to the class path, you use POJOs and JAXB objects to represent request and response message bodies. Jackson is included in the server-side run time of this product.

参考: Implementing a JSON representation of a resource with Jackson and JAXB annotated objects

すなわち、LibertyではJackson(Apache License)によりJSONのハンドリングが行われるらしい。従って、JSONバインディングについてはJacksonのスペックを調べる必要があるということのようです。

ということで、Java - JSON マッピング はこの辺を参考にするべし。
first tutorial : Jackson – Java to JSON and JSON to Java

JSON整形ツール

JSON形式のデータを使って色々テストをしていくと、きれいに整形しないと見るのが大変なことがよくあります。
このツールはJavaScriptベースでローカルで動作するので非常に使い勝手がよいです。
https://syncer.jp/json-prettyprint

不定形なJSONデータのハンドリング

JSONの構造が不定の場合、リクエスト用/レスポンス用のデータは、Object型にしておけばよいようです。
POSTでJAX-RS経由で受取ったrequestオブジェクトのクラスを.getClass().getName()で取得してみると、"java.util.LinkedHashMap"となってました。