Java EE 8 ハンズオン
GlassFish 5 EA 版を用いて
Version 1.0
Table of Contents
1.0 はじめに ... 3
2.0 NetBeans と GlassFish の設定と動作確認 ... 7
3.0 Servlet 4.0 アプリケーションの作成 ... 15
3
1.0 はじめに
Java EE 8 は、Java EE 7 をベースに3つの新しいテーマ(開発生産性の向上、
HTML 5 対応、クラウド対応)を提供し、新機能やアップデート等、リリースに向
けて最終調整中です。本ハンズオン・ラボでは、Servlet 4.0 と JAX-RS 2.1 にフ
ォーカスをあて、簡単なアプリケーションの構築を行い、Servlet 4.0 と JAX-RS
2.1 の動きを確認して頂きます。
本ハンズオン・ラボは Java EE 8 に含まれる下記の技術を使用します。
Servlet 4.0 (JSR 369)
JAX-RS 2.1 (JSR 370)
なお、この演習で Java コードや HTML の入力が多い部分については、この PDF
ファイルから随時コピー・ペーストを行って下さい。
この PDF ファイルおよびハンズオンで使用する画像ファイルは下記よりダウン
ロードしてください。
https://tinyurl.com/JDT2017-JavaEE8-HoL
※リンクは 2017/5/31 まで有効です。
必須ソフトウェア
最新の JDK 8 を入手してインストールしてください。
http://www.oracle.com/technetwork/java/javase/downloads/index.ht
ml
下図は Java SE 8u131 のダウンロードページですが、ご利用時点でリリ
ースされている最新の Java SE 8 をご利用下さい。
NetBeans 8.2 以降のバージョンを入手してインストールしてくださ
い。NetBeans のダウンロードサイトより「Java EE」もしくは「すべ
て」のパッケージを選択し「ダウンロード」ボタンを押下してくださ
い。GlassFish v4.1.1 がバンドルされていますが、本ハンズオンでは
使用しません。
5
し、GlassFish v4.1.1 と Apache Tomcat8.0.27 のチェックを外してく
ださい。
※後ほど GlassFish5 をランタイムとして追加します。
※また、下記には NetBeans や GlassFish をインストールしないでくだ
さい。
・Program Files ディレクトリ
・ディレクトリ名に空白やマルチバイトが入っているディレクトリ
GlassFish 5 EA 版の Promoted ビルド「glassfish-5.0-b05」を下記よ
りダウンロードし、任意のディレクトリに展開してください。
http://download.oracle.com/glassfish/5.0/promoted/glassfish-5.0-b05.zip
※下記には NetBeans や GlassFish をインストールしないでください。
・Program Files ディレクトリ
・ディレクトリ名に空白やマルチバイトが入っているディレクトリ
※GlassFish 5 EA 版を展開すると「glassfish4」というフォルダが作
成されますが、問題ありません。
7
2.0 NetBeans と GlassFish の設定と動作確認
NetBeans をインストールしたのち、アイコンをダブル・クリックし NetBeans
を起動してください。起動すると下記のような画面が表示されます。
ここで、NetBeans のランタイムに GlassFish 5 を指定します。新しくプロジ
ェクトを作成してください。メニューから「ファイル (F)」→「新規プロジェ
クト(W)...」を選択してください。
「新規プロジェクト(W)...」を選択すると下記のウィンドウが表示されます。
「カテゴリ(C) :」より「Java Web」を選択し、「プロジェクト(P):」より
「Web アプリケーション」を選択し「次 >」ボタンを押下してください。
「次 >」ボタンを押下すると下記のウィンドウが表示されます。「プロジェク
ト名:」に「JavaEE8-HoL」と入力して、「ライブラリの格納用に専用フォルダ
を使用(D)」にチェックを入れ、「参照(R)」ボタンを押下してください。
9
展開した GlassFish 5 内の「lib」フォルダを指定し、「開く」ボタンを押下
してください。
「glassfish4」> 「glassfish」> 「lib」
※GlassFish 5 EA 版を展開すると glassfish4 というフォルダが作成されます
が、問題ありません。
「次 >」ボタンを押下すると下記の画面が表示されます。「サーバー(S)」の
「追加(D)」ボタンを押下してください。
「サーバーを選択」画面で「GlassFish Server」を選択し、「名前(N)」に
「GlassFish 5 EA」と入力して「次 >」ボタンを押下してください。
「サーバーの場所」で「参照(O)」ボタンを押下ください。
展開した GlassFish 5 の「glassfish4」フォルダを指定し、「選択」ボタンを
押下してください。
※GlassFish 5 EA 版を展開すると glassfish4 というフォルダが作成されます
が、問題ありません。
11
「次 >」ボタンを押下してください。
「ドメインの場所」はデフォルトのまま「終了(F)」ボタンを押下してくださ
い。
「Java EE バージョン(J)」と「コンテキスト・パス(P):」の変更は今回不要な
ので、そのまま「次 >」ボタンを押下してください。
フレームワークは今回不要なので、そのまま「終了(F)」ボタンを押下してくだ
さい。
13
新規プロジェクトを作成したので、このプロジェクトを実行します。プロジェ
クトを実行するために、NetBeans のツールバーより、「プロジェクトを実行
(F6)」ボタンを押下してください。
もしくは、NetBeans のメニューから「実行(R)」>「プロジェクトを実行(R)」
を選択し実行してください。
プロジェクトを実行するとブラウザが自動的に起動し、デフォルトのページが
表示されます。
15
3.0 Servlet 4.0 アプリケーションの作成
本章より実装をはじめていきます。
従来の Servlet API は、HTTP/1.x に対応しており、単一リクエストに対し、
単一レスポンスを返すアーキテクチャになっておりました。この場合、例え
ば、同一クライアントから大量の HTTP リクエストをサーバーに投げるケース
や、特定のリクエストで処理時間が長く要するケース等、後続の処理が待ち状
態になり、全体としてパフォーマンスが低下しまうことがありました。しか
し、 HTTP/2 では、この問題点を改善するために、単一コネクションで、リク
エストとレスポンスを多重化できるようになります。これにより、例え一つの
リクエスト処理に時間を要しても後続の処理に影響が発生しにくくなるため、
より効率的なデータ転送を行う事ができるようになります。
本章では、この HTTP/2 に対応した Servlet 4.0 の PushBuilder インターフェー
スを使用し、Server Push の動きをご確認頂きます。Server Push により、クラ
イアントリクエストを待たないレスポンスが可能となります。
Server Push の基本的な流れは下記のようになります。
このように、クライアントからのリクエストの度にレスポンスを返すのではな
く、(上図の場合、.css や.js ファイルを)サーバー側からプッシュすること
ができます。
今回は、Servlet 4.0 の Server Push の動きを把握頂くため、Servlet を使用し
て複数の画像を表示させるシンプルな Web ページを作成し、従来の HTTP/1.1 と
Server Push を利用した HTTP/2 における処理の違いを確認して頂きます。
まず、HTTP/2 用の Server Push を実装する Servlet ファイルを作成します。
プロジェクトを選択し、右クリックした後、「新規」→「サーブレット...」を
選択してください。
選択すると、下記のウィンドウが表示されます。ここで「クラス名(N):」に
「HTTP2」を入力し、「パッケージ(K):」に「jp.co.oracle.servlets」を入力
した後、「次 > 」ボタンを押下してください。
ボタンを押下すると下記のウィンドウが表示されます。デフォルトのまま、
「終了(F)」ボタンを押下してください。
17
ボタンを押下すると下記のコードが自動的に生成されます。
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates * and open the template in the editor.
*/ package jp.co.oracle.servlets; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * * @author kenhirai */ @WebServlet(name = "HTTP2", urlPatterns = {"/HTTP2"}) public class HTTP2 extends HttpServlet {
/**
* Processes requests for both HTTP <code>GET</code> and <code>POST</code>
* methods. *
* @param request servlet request * @param response servlet response
* @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs
*/
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8"); try (PrintWriter out = response.getWriter()) {
/* TODO output your page here. You may use following sample code. */
out.println("<!DOCTYPE html>"); out.println("<html>");
out.println("<head>"); out.println("<title>Servlet HTTP2</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Servlet HTTP2 at " + request.getContextPath() + "</h1>"); out.println("</body>"); out.println("</html>"); } } ・ ・ ・ 以下略
上記の自動生成されたコードに、複数の画像を Server Push で表示するための
PushBuilder インターフェースを実装します。下記の箇所にメソッドを追加し
てください。(赤字参照)
/** To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates * and open the template in the editor.
*/ package jp.co.oracle.servlets; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.PushBuilder; /** * * @author kenhirai */ @WebServlet(name = "HTTP2", urlPatterns = {"/HTTP2"}) public class HTTP2 extends HttpServlet {
/**
* Processes requests for both HTTP <code>GET</code> and <code>POST</code>
* methods. *
* @param request servlet request * @param response servlet response
* @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs
*/
19
.path("./images/1.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/2.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/3.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/4.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/5.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/6.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/7.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/8.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/9.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/10.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/11.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/12.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/13.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/14.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/15.png") .addHeader("content-type", "image/png") .push(); }/* TODO output your page here. You may use following sample code. */
out.println("<!DOCTYPE html>");
・ ・
・ 以下略
次に、Servlet で直接画像を表示するために、「
/* TODO output your page here.You may use following sample code. */
」の箇所に下記のように「out.println
…」を追加してください。(赤字参照)
<前略> ・ ・ ・
PushBuilder pushBuilder = request.newPushBuilder(); if (pushBuilder != null) { pushBuilder .path("./images/1.png") .addHeader("content-type", "image/png") .push(); <中略> ・ ・ ・ pushBuilder .path("./images/15.png") .addHeader("content-type", "image/png") .push(); }
/* TODO output your page here. You may use following sample code. */ out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet HTTP2</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Servlet HTTP2 at " + request.getContextPath() + "</h1>"); out.println("<img src='./images/1.png'>"); out.println("<img src='./images/2.png'>"); out.println("<img src='./images/3.png'>"); out.println("<img src='./images/4.png'>"); out.println("<img src='./images/5.png'>"); out.println("<img src='./images/6.png'>"); out.println("<img src='./images/7.png'>"); out.println("<img src='./images/8.png'>"); out.println("<img src='./images/9.png'>"); out.println("<img src='./images/10.png'>"); out.println("<img src='./images/11.png'>"); out.println("<img src='./images/12.png'>"); out.println("<img src='./images/13.png'>"); out.println("<img src='./images/14.png'>"); out.println("<img src='./images/15.png'>"); out.println("</body>"); out.println("</html>"); }
21
修正後の全コードは下記のようになります。
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates * and open the template in the editor.
*/ package jp.co.oracle.servlets; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.PushBuilder; /** * * @author xxx */ @WebServlet(name = "HTTP2", urlPatterns = {"/HTTP2"}) public class HTTP2 extends HttpServlet {
/**
* Processes requests for both HTTP <code>GET</code> and <code>POST</code>
* methods. *
* @param request servlet request * @param response servlet response
* @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs
*/
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8"); try (PrintWriter out = response.getWriter()) {
PushBuilder pushBuilder = request.newPushBuilder(); if (pushBuilder != null) { pushBuilder .path("./images/1.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/2.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/3.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/4.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/5.png") .addHeader("content-type", "image/png")
.push(); pushBuilder .path("./images/6.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/7.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/8.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/9.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/10.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/11.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/12.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/13.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/14.png") .addHeader("content-type", "image/png") .push(); pushBuilder .path("./images/15.png") .addHeader("content-type", "image/png") .push(); }
/* TODO output your page here. You may use following sample code. */ out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet HTTP2</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Servlet HTTP2 at " + request.getContextPath() + "</h1>"); out.println("<img src='./images/1.png'>"); out.println("<img src='./images/2.png'>"); out.println("<img src='./images/3.png'>"); out.println("<img src='./images/4.png'>"); out.println("<img src='./images/5.png'>"); out.println("<img src='./images/6.png'>");
23
out.println("<img src='./images/15.png'>"); out.println("</body>"); out.println("</html>"); } }// <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
/**
* Handles the HTTP <code>GET</code> method. *
* @param request servlet request * @param response servlet response
* @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs
*/ @Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { processRequest(request, response);
}
/**
* Handles the HTTP <code>POST</code> method. *
* @param request servlet request * @param response servlet response
* @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs
*/ @Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { processRequest(request, response);
}
/**
* Returns a short description of the servlet. *
* @return a String containing servlet description */
@Override
public String getServletInfo() { return "Short description"; }// </editor-fold>
次に、Server Push を使わず、HTTP/1.1 で画像表示をする Servlet ファイルを
作成します。
同様に、プロジェクトを選択し、右クリックした後、「新規」→「サーブレッ
ト...」を選択します。
選択すると、下記のウィンドウが表示されます。ここで「クラス名(N):」に
「HTTP1」を入力し、「パッケージ(K):」に「jp.co.oracle.servlets」を入力
した後、「次 > 」ボタンを押下してください。
ボタンを押下すると下記のウィンドウが表示されます。デフォルトのまま、
「終了(F)」ボタンを押下してください。
25
ボタンを押下すると下記のコードが自動的に生成されます。
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates * and open the template in the editor.
*/ package jp.co.oracle.servlets; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * * @author kenhirai */ @WebServlet(name = "HTTP1", urlPatterns = {"/HTTP1"}) public class HTTP1 extends HttpServlet {
/**
* Processes requests for both HTTP <code>GET</code> and <code>POST</code>
* methods. *
* @param request servlet request * @param response servlet response
* @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs
*/
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8"); try (PrintWriter out = response.getWriter()) {
/* TODO output your page here. You may use following sample code. */
out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet HTTP1</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Servlet HTTP1 at " + request.getContextPath() + "</h1>"); out.println("</body>"); out.println("</html>"); } } ・ ・ ・ 以下略
Servlet で直接画像を表示するために、「
/* TODO output your page here. Youmay use following sample code. */
」の箇所に下記のように「out.println…」を
追加してください。(赤字参照)
<前略> ・ ・ ・
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8"); try (PrintWriter out = response.getWriter()) {
/* TODO output your page here. You may use following sample code. */ out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet HTTP1</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Servlet HTTP1 at " + request.getContextPath() + "</h1>"); out.println("<img src='./images/1.png'>"); out.println("<img src='./images/2.png'>"); out.println("<img src='./images/3.png'>"); out.println("<img src='./images/4.png'>"); out.println("<img src='./images/5.png'>"); out.println("<img src='./images/6.png'>"); out.println("<img src='./images/7.png'>"); out.println("<img src='./images/8.png'>"); out.println("<img src='./images/9.png'>"); out.println("<img src='./images/10.png'>"); out.println("<img src='./images/11.png'>"); out.println("<img src='./images/12.png'>");
27
・ ・ ・ 以下略修正後の全コードは下記のようになります。
/** To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates * and open the template in the editor.
*/ package jp.co.oracle.servlets; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * * @author xxx */ @WebServlet(name = "HTTP1", urlPatterns = {"/HTTP1"}) public class HTTP1 extends HttpServlet {
/**
* Processes requests for both HTTP <code>GET</code> and <code>POST</code>
* methods. *
* @param request servlet request * @param response servlet response
* @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs
*/
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8"); try (PrintWriter out = response.getWriter()) {
/* TODO output your page here. You may use following sample code. */ out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet HTTP1</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Servlet HTTP1 at " + request.getContextPath() + "</h1>"); out.println("<img src='./images/1.png'>"); out.println("<img src='./images/2.png'>"); out.println("<img src='./images/3.png'>"); out.println("<img src='./images/4.png'>"); out.println("<img src='./images/5.png'>"); out.println("<img src='./images/6.png'>"); out.println("<img src='./images/7.png'>"); out.println("<img src='./images/8.png'>");
out.println("<img src='./images/9.png'>"); out.println("<img src='./images/10.png'>"); out.println("<img src='./images/11.png'>"); out.println("<img src='./images/12.png'>"); out.println("<img src='./images/13.png'>"); out.println("<img src='./images/14.png'>"); out.println("<img src='./images/15.png'>"); out.println("</body>"); out.println("</html>"); } }
// <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
/**
* Handles the HTTP <code>GET</code> method. *
* @param request servlet request * @param response servlet response
* @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs
*/ @Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { processRequest(request, response);
}
/**
* Handles the HTTP <code>POST</code> method. *
* @param request servlet request * @param response servlet response
* @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs
*/ @Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { processRequest(request, response);
}
/**
* Returns a short description of the servlet. *
* @return a String containing servlet description */
@Override
public String getServletInfo() { return "Short description"; }// </editor-fold>
}
ここまでで、HTTP/1.1 と Server Push を使用した HTTP/2 それぞれで画像表示
をする Servlet が作成できました。
29
※まだダウンロードされていない場合は、下記よりダウンロードしてくださ
い。
https://tinyurl.com/JDT2017-JavaEE8-HoL
「imgaes」フォルダ横の「+」アイコンを押下すると、画像ファイルが正しく配
置できたことを確認できます。
最後に自動的に作成された Index.html を下記のコードに置き換えてください。
<!DOCTYPE html> <!--To change this license header, choose License Headers in Project Properties. To change this template file, choose Tools | Templates
and open the template in the editor. -->
<html> <head>
<title>Servlet4.0</title> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <div>Servlet4.0 HandsOn</div> <br> <div><a href="http://localhost:8080/JavaEE8-HoL/HTTP1"><strong>HTTP1</strong></a></div> <br> <div><a href="https://localhost:8181/JavaEE8-HoL/HTTP2"><strong>HTTP2</strong></a></div> </body> </html>
実装した後、NetBeans のプロジェクトを実行してください。
図 40:NetBeans プロジェクトの実行
プロジェクトを実行すると下記の画面が表示されます。
「HTTP1」と「HTTP2」のリンクをクリックすると、それぞれ作成した Servlet
にアクセスできます。
31
※Chrome の場合、メニューアイコンの「その他のツール」から「デベロッパー
ツール」を押下し、「Network」タブを開いてください。
開発者ツールが表示されたら、開発「設定」ボタンを押下して、キャッシュを
無効化してください。
ページを再読み込みして、結果を確認してください。
ブラウザによる違いはありますが、1 つのリクエストごとに1つのレスポンス
を返している HTTP/1.1 と html のレスポンスを返すときに画像ファイルをまと
めてプッシュしている HTTP/2 で応答が違うことが確認できます。
下記に Chrome と FireFox の例を示します。
Chrome
HTTP/1.1
HTTP/2
33
FireFox
HTTP/1.1
4.0 JAX-RS 2.1 Reactive REST クライアント実行
ハンズオン概要
本ハンズオンで参照するコードは以下の GitHub 上にアップされたコー
ドです。(git tag: beforehandson)
https://github.com/nobuhikosekiya/jaxrs21handson/tree/beforehand
son
ハンズオンを完了したときの状態のコードは以下の URL にありますの
で、必要に応じて参考にしてください。(git tag: afterhandson)
https://github.com/nobuhikosekiya/jaxrs21handson/tree/afterhands
on
これらのハンズオンのコードは、下記 URL にある JAX-RS のリファレン
ス実装である Jersey の GitHub リポジトリ上のサンプルコードをベース
にしており、本ハンズオン向けに一部コードを省略しています。
https://github.com/jersey/jersey/tree/2.26-b02/examples/rx-client-webapp
(今回の GlassFish に含まれている Jersey と同じバージョンのタグ番
号 2.26-b02 のサンプルを使います)
アプリケーション構成について
35
ハンズオンプロジェクトの作成
新たにこれ以降のハンズオン用の新規の NetBeans プロジェクトを作成します。
メニューから「ファイル (F)」→「新規プロジェクト(W)...」を選択してくだ
さい。
表示される下記ウィンドウのカテゴリから「Java Web」を選択し、さらにプロ
ジェクトから「Web アプリケーション」を選択し、「次 >」を押してくださ
い。
次に表示される下記ウィンドウの「プロジェクト名」をここでは
「JavaEE8-JaxRS」として設定します。前回のプロジェクトと同様に、
ライブラリ・フォルダを今回インストールした GlassFish 5 内の lib
フォルダを指定し、「次 > 」を押してください。
次に表示される下記ウィンドウの「サーバー」は前回のプロジェクト
で作成した「GlassFish 5 EA」が設定されていることを確認します。
コンテキスト・パスは、URL パスを簡単にするために今回は
37
プロジェクトへのライブラリの追加
この章のハンズオンで利用するライブラリは以下の通りです。
RxJava … 今回のハンズオンで利用するリアクティブプログラミ
ングのフレームワーク
Jersey Reactive Client- RXJava provider … JAX-RS の
Reactive Client で RxJava を利用するための拡張
jersey-server.jar と jersey-guava.jar …今回のプロジェクトの
REST サーバー側の実装に使用しているライブラリです。 JAX-RS
Reactive Client とは直接は関係ありません。
今回のハンズオンで利用する RxJava(バージョン 1)のライブラリ
JAR ファイルを以下のサイトよりダウンロードして、自身の PC の任意
のフォルダに保存してください。
https://mvnrepository.com/artifact/io.reactivex/rxjava/1.2.5
さらに、JAX-RS の Reactive REST クライアントから RxJava を組み込
むための JAX-RS の拡張実装のライブラリ(Jersey Reactive Client-
RXJava provider)もダウンロードしてください。
https://mvnrepository.com/artifact/org.glassfish.jersey.ext.rx/jersey-rx-client-rxjava/2.26-b02
※RxJava と拡張ライブラリのバージョンの組み合わせ( rxjava 1.25
と jersey-rx-client-rxjava 2.26-b02)は、必ずここで指定したもの
を本ハンズオンでは使用してください。組み合わせバージョンが異な
ると、プロジェクトのコンパイルでエラーとなる場合があります。
以降の手順で、ダウンロードしたライブラリをプロジェクトで利用で
きるように設定します。
39
->
表示される下記ウィンドウのカテゴリの「ソース」では、ソースバイ
ナリ形式が「JDK8」に設定されていることをまず確認してください。
次に、カテゴリから「ライブラリ」を選択し、「ライブラリの追加」
ボタンを押してください。
次に表示されるウィンドウで「作成」ボタンを押し、ライブラリ名と
して「rxjava」と入力して「OK」を押してください。
「JAR/フォルダの追加」を押してください。
さきほどダウンロードした「rxjava-1.2.5.jar」を選択して「JAR/フ
ォルダの追加」ボタンを押してください。
41
以下のようなダイアログが表示されますので「はい」を押してくださ
い。
その後、もう一つの jar
ファイル「jersey-rx-client-rxjava-2.26-b02.jar」を選択して同様に追加してください。
その後、戻った下記ウィンドウの「OK」ボタンを押してください。
OK ボタン後に下記のウィンドウに戻るので、先ほど追加したライブラ
リを選択した状態で「ライブラリの追加」を押してください。
43
「作成」ボタンを押し、ライブラリ名を「jersey-libs」とし、「OK」
を押してください。
今回は glassfish/modules フォルダ(lib フォルダと同階層のフォル
ダです)にある jersey-guava.jar と jersey-server.jar を追加してく
ださい。
以下のように、2つの jar を設定したら、「OK」ボタンを押してくだ
さい。
45
OK
押し後に戻ったウィンドウでは、さきほど作成した「jersey-libs」を選択した上で「ライブラリの追加」ボタンを押してくださ
い。
以下のように2つのライブラリ(中には計 4 つの jar ファイル)が登
録されたら「OK」を押して、ライブラリの登録は終了となります。
REST サーバーの作成
以下の GitHub の URL より今回のハンズオンのサンプルコードをダウンロードし
てください。
https://github.com/nobuhikosekiya/jaxrs21handson/archive/beforehandson.zip
ダウンロードした zip ファイルを任意の場所に解凍し、中に含まれる src\java
のディレクトリ配下の org フォルダをプロジェクトのソース・パッケージへコ
ピーペーストしてください。
->
その後、プロジェクトを右クリックし、「デプロイ」を実行してください。
47
デプロイが成功したのち、以下の URL をブラウザのアドレスバーに入力し、
JSON 形式のデータが返ってくることを確認してください。
※実際のブラウザ画面での JSON 文字列は整形されていません。以降も本ドキュ
メント内では見やすいように結果は整形している場合があります。
http://localhost:8080/jaxrs21/rx/remote/destination/visited
[ { "destination": "Zambia" }, { "destination": "Kiribati" }, { "destination": "Japan" }, {"destination": "Saudi Arabia" }, { "destination": "Palau" } ]
http://localhost:8080/jaxrs21/rx/remote/destination/recommended
[ { "destination": "Greece" }, { "destination": "Malta" }, { "destination": "Benin" }, { "destination": "Sweden" }, { "destination": "Uganda" } ]http://localhost:8080/jaxrs21/rx/remote/forecast/Tokyo
<forecast> <destination> Tokyo </destination> </forecast> <forecast> Partly Sunny </forecast>http://localhost:8080/jaxrs21/rx/remote/calculation/from/Moon/to/Tokyo
<calculation> <from> Moon </from> <price> 1680 </price> <to> Tokyo </to> </calculation>JAX-RS sync client の作成
リアクティブな JAX-RS クライアントとの対比のために最初にベーシックな同期
型の JAX-RS クライアントを作成します。
プロジェクトを右クリックし、新規の Java パッケージを作成してください。パ
ッケージ名は「org.glassfish.jersey.examples.rx.agent」とします。
次に、作成したパッケージ「org.glassfish.jersey.examples.rx.agent」を右
クリックし、新規の Java クラスを作成してください。
49
クラス名に「SyncAgentResource」と入力し、「終了」ボタンを押してくださ
い。
クラス作成後、以下の赤字の部分のコードを追加してください。
package org.glassfish.jersey.examples.rx.agent; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import org.glassfish.jersey.examples.rx.domain.AgentResponse; @Path("agent/sync") @Produces("application/json")public class SyncAgentResource {
@GET
public AgentResponse sync() {
final long time = System.nanoTime();
final AgentResponse response = new AgentResponse();
response.setProcessingTime((System.nanoTime() - time) / 1000000); return response; } }
コード変更を保存すると、自動的に変更がデプロイされます。以下の URL にブ
ラウザからアクセスし、現段階の実行結果を確認してください。ここでは単に
空の AgentResponse のオブジェクトを JSON として返却しているだけです。
http://localhost:8080/jaxrs21/rx/agent/sync 実行結果の例
{"processingTime":0,"visited":[]}
次に、以下の赤字のコードを先ほどの SyncAgentResource クラスのソースに追
加で挿入してください。
import java.util.List; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import org.glassfish.jersey.examples.rx.domain.AgentResponse; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.GenericType; import org.glassfish.jersey.examples.rx.domain.Destination; (省略)public class SyncAgentResource {
final private Client client = ClientBuilder.newClient(); final private WebTarget destination =
client.target("http://localhost:8080/jaxrs21/rx").path("remote/destination");
@GET
public AgentResponse sync() {
final long time = System.nanoTime();
final AgentResponse response = new AgentResponse();
final List<Destination> visited = destination.path("visited").request() // Identify the user. .header("Rx-User", "Sync") // Return a list of destinations
.get(new GenericType<List<Destination>>() {}); response.setVisited(visited); (省略)
コードの保存により変更がデプロイされるので、以下の URL の実行結果を確認
してください。REST サーバーから Destination の情報を取得できるようになり
ました。
http://localhost:8080/jaxrs21/rx/agent/sync 実行結果の例
{"processingTime":548,"visited":[{"destination":"Botswana"},{"destina
tion":"Spain"},{"destination":"Lesotho"},{"destination":"Iceland"},{"
destination":"Venezuela"}]}
次に、以下の赤字のコードを同じクラスに追加してください。Recommend のデ
ータを REST サーバーより取得するコードです。
(省略) response.setVisited(visited);51
response.setProcessingTime((System.nanoTime() - time) / 1000000); (省略)
そのまま次に、以下の赤字のコードを挿入してください。Recommend の
destintion データ毎に forecast データを REST サーバーに繰り返し問い合わせ
ています。
import org.glassfish.jersey.examples.rx.domain.Forecast; import java.util.ArrayList;
final private WebTarget destination =
client.target("http://localhost:8080/jaxrs21/rx").path("remote/destination");
final private WebTarget forecast = client. target("http://localhost:8080/jaxrs21/rx").path ("remote/forecast/{destination}");
(省略)
final List<Destination> recommended = (省略)
// Forecasts. (depend on recommended destinations)
final List<Forecast> forecasts = new ArrayList<>(recommended.size()); for (final Destination dest : recommended) {
forecasts.add(forecast.resolveTemplate("destination", dest.getDestination()).request().get(Forecast.class)); } response.setProcessingTime((System.nanoTime() - time) / 1000000);
http://localhost:8080/jaxrs21/rx/remote/calculation/from/Moon/to/Morroc
o
<calculation><from>Moon</from><price>6482</price><to>Morroco</to></ca
lculation>
http://localhost:8080/jaxrs21/rx/remote/calculation/from/Moon/to/Cyprus
<calculation><from>Moon</from><price>8239</price><to>Cyprus</to></cal
culation>
そのまま次に、以下の赤字のコードを挿入してください。Recommend の
destintion データ毎に Moon からその destination までのコストを REST サーバ
ーに繰り返し問い合わせています。
import org.glassfish.jersey.examples.rx.domain.Calculation;
final private WebTarget destination =
client.target("http://localhost:8080/jaxrs21/rx").path("remote/destination"); final private WebTarget forecast = client.
target("http://localhost:8080/jaxrs21/rx").path ("remote/forecast/{destination}");
final private WebTarget calculation = client. target("http://localhost:8080/jaxrs21/rx").path ("remote/calculation/from/{from}/to/{to}");
(省略)
final List<Forecast> forecasts = new ArrayList<>(recommended.size()); for (final Destination dest : recommended) {
forecasts.add(forecast.resolveTemplate("destination", dest.getDestination()).request().get(Forecast.class));
}
final List<Calculation> calculations = new ArrayList<>(recommended.size());
for (final Destination dest : recommended) {
calculations.add(calculation.resolveTemplate("from", "Moon").resolveTemplate("to", dest.getDestination()) .request().get(Calculation.class)); } response.setProcessingTime((System.nanoTime() - time) / 1000000);
最後に以下のコードを追加し、レスポンスとしてセットしてます。
import org.glassfish.jersey.examples.rx.domain.Recommendation; (省略)final List<Calculation> calculations = new ArrayList<>(recommended.size());
for (final Destination dest : recommended) {
calculations.add(calculation.resolveTemplate("from", "Moon").resolveTemplate("to", dest.getDestination())
.request().get(Calculation.class)); }
// Recommendations.
final List<Recommendation> recommendations = new ArrayList<>(recommended.size());
for (int i = 0; i < recommended.size(); i++) { recommendations.add(new Recommendation(recommended.get(i).getDestination(), forecasts.get(i).getForecast(), calculations.get(i).getPrice())); } response.setRecommended(recommendations); response.setProcessingTime((System.nanoTime() - time) / 1000000);
変更をデプロイし、以下の URL の実行結果を確認してください。REST サーバー
から Destination の情報を取得できるようになりました。REST サーバーへのリ
クエスト回数が何回も行われていることにより、レスポンスが返るまでに時間
がかかっていることがわかります。
http://localhost:8080/jaxrs21/rx/agent/sync 実行結果の例
{ "processingTime": 4700, "recommended": [ {"destination": "Sao Tome & Principe", "forecast": "Overcast",
"price": 785 },
{
"destination": "China",
"forecast": "Chance of TStorm", "price": 3647
}, {
53
{
"destination": "St. Kitts & Nevis", "forecast": "Thunderstorm", "price": 2460 } ], "visited": [ { "destination": "Botswana" }, { "destination": "Spain" }, { "destination": "Lesotho" }, { "destination": "Iceland" }, { "destination": "Venezuela" } ] }
JAX-RS reactive client の作成
ここではさきほどの JAX-RS sync client と同等の処理を JAX-RS reactive
client に代え、違いを確認していきます。
前回と同様のパッケージ配下に新規の Java クラスを作成します。クラス名前は
「ObservableAgentResource」としてください。
クラスを新規作成後、以下の赤字のコードを挿入してください。
package org.glassfish.jersey.examples.rx.agent; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.inject.Singleton; import javax.ws.rs.GET; import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.container.Suspended; import org.glassfish.jersey.examples.rx.domain.AgentResponse; @Singleton @Path("agent/observable") @Produces("application/json")public class ObservableAgentResource {
@GET
final long time = System.nanoTime();
final AgentResponse agentResponse = new AgentResponse(); async.resume(agentResponse); } }
変更をデプロイし、以下の URL 実行結果を確認してください。ここでは空のデ
ータを返されました。
http://localhost:8080/jaxrs21/rx/agent/observable 実行結果の例
{"processingTime":0,"visited":[]}
次の赤字のコードに置き換えてください。AgentResponse オブジェクトとに実
行時間のデータをセットし、レスポンスを返しているだけですが、RxJava を使
ったリアクティブプログラミングとなっています。
(省略) import rx.Observable; @GETpublic void observable(@Suspended final AsyncResponse async) { final long time = System.nanoTime();
final AgentResponse agentResponse = new AgentResponse();
Observable.just(agentResponse) .subscribe(response -> { response.setProcessingTime((System.nanoTime() - time) / 1000000); async.resume(response); }); } }
変更をデプロイし、以下の URL 実行結果を確認してください。実行時間がセッ
トされたことがわかります。
http://localhost:8080/jaxrs21/rx/agent/observable 実行結果の例
{"processingTime":164,"visited":[]}
次に、リアクティブプログラミングで visited や recommendation のデータを取
得し、AgentRespons にセットしていくコードを挿入します。ここではまだ REST
サーバーへの問合せは含めず、Destination 等のオブジェクトの取得は固定値
として、Reactive プログラミングによる実装部分に集中します。
55
final long time = System.nanoTime();
final AgentResponse agentResponse = new AgentResponse(); Observable.just(agentResponse)
.zipWith(visited(), (response, visited) -> { response.setVisited(visited); return response; }) .subscribe(response -> { response.setProcessingTime((System.nanoTime() - time) / 1000000); async.resume(response); });
private Observable<List<Destination>> visited() { List<Destination> visitedList = new ArrayList<>(); visitedList.add(new Destination("Chicago")); visitedList.add(new Destination("New York")); return Observable.just(visitedList); } }
変更をデプロイし、以下の URL 実行結果を確認してください。リアクティブプ
ログラミングで visisted のデータをレスポンスに追加できました。
http://localhost:8080/jaxrs21/rx/agent/observable 実行結果の例
{ "processingTime": 149, "visited": [ { "destination": "Chicago" }, {"destination": "New York" } ] }
次に同じように recommended のデータを RxJava によるリアクティブプログラミ
ングで用意して返します。以下の赤字のコードをセットしてください。
(省略) import org.glassfish.jersey.examples.rx.domain.Calculation; import org.glassfish.jersey.examples.rx.domain.Forecast; import org.glassfish.jersey.examples.rx.domain.Recommendation; (省略) @GETpublic void observable(@Suspended final AsyncResponse async) { final long time = System.nanoTime();
final AgentResponse agentResponse = new AgentResponse(); Observable.just(agentResponse)
.zipWith(visited(), (response, visited) -> { response.setVisited(visited);
return response; })
.zipWith(recommended(), (response, recommendations) -> { response.setRecommended(recommendations); return response; }) .subscribe(response -> { response.setProcessingTime((System.nanoTime() - time) / 1000000);
async.resume(response); });
}
private Observable<List<Destination>> visited() { List<Destination> visitedList = new ArrayList<>(); visitedList.add(new Destination("Chicago")); visitedList.add(new Destination("New York")); return Observable.just(visitedList);
}
private Observable<List<Recommendation>> recommended() { List<Destination> recommendedList = new ArrayList<>(); recommendedList.add(new Destination("Hawaii"));
recommendedList.add(new Destination("Tokyo"));
final Observable<Destination> recommended = Observable.just(recommendedList)
// Emit destinations one-by-one. .flatMap(Observable::from)
// Remember emitted items for dependant requests. .cache();
// Forecasts. (depend on recommended destinations) final Observable<Forecast> forecasts =
recommended.flatMap(destination ->
Observable.just(new Forecast(destination.getDestination(), "Sunny"))
);
// Calculations. (depend on recommended destinations) final Observable<Calculation> calculations =
recommended.flatMap(destination ->
Observable.just(new Calculation("Moon", destination.getDestination(), -1))
);
return Observable.zip(recommended, forecasts, calculations, Recommendation::new).toList(); }
http://localhost:8080/jaxrs21/rx/agent/observable 実行結果の例
{ "processingTime": 182, "recommended": [ { "destination": "Hawaii", "forecast": "Sunny", "price": -1 }, { "destination": "Tokyo", "forecast": "Sunny", "price": -1 } ], "visited": [57
最後に、上記で固定値としてデータを設定していたところを、実際の JAX-RS の
REST Client から REST サーバーへの問合せ結果によるデータに代えます。赤字
が変更点となります。
(省略) import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.GenericType; import org.glassfish.jersey.client.rx.rxjava.RxObservableInvoker; import org.glassfish.jersey.client.rx.rxjava.RxObservableInvokerProvider; (省略)public class ObservableAgentResource {
final private Client client = ClientBuilder.newClient(); final private WebTarget destination =
client.target("http://localhost:8080/jaxrs21/rx").path("remote/destination"); final private WebTarget forecast =
client.target("http://localhost:8080/jaxrs21/rx").path("remote/forecast/{dest ination}");
final private WebTarget calculation =
client.target("http://localhost:8080/jaxrs21/rx").path("remote/calculation/fr om/{from}/to/{to}");
(省略)
private Observable<List<Destination>> visited() {
destination.register(RxObservableInvokerProvider.class);
return destination.path("visited").request() // Identify the user.
.header("Rx-User", "RxJava") // Reactive invoker.
.rx(RxObservableInvoker.class) // Return a list of destinations.
.get(new GenericType<List<Destination>>() { });
}
private Observable<List<Recommendation>> recommended() {
destination.register(RxObservableInvokerProvider.class);
final Observable<Destination> recommended =
destination.path("recommended").request() // Identify the user.
.header("Rx-User", "RxJava") // Reactive invoker.
.rx(RxObservableInvoker.class) // Return a list of destinations.
.get(new GenericType<List<Destination>>() { })
// Emit destinations one-by-one. .flatMap(Observable::from)
// Remember emitted items for dependant requests. .cache();
forecast.register(RxObservableInvokerProvider.class);
// Forecasts. (depend on recommended destinations)
final Observable<Forecast> forecasts = recommended.flatMap(destination -> forecast .resolveTemplate("destination", destination.getDestination()) .request().rx(RxObservableInvoker.class).get(Forecast .class)
);
calculation.register(RxObservableInvokerProvider.class);
// Calculations. (depend on recommended destinations)
final Observable<Calculation> calculations = recommended.flatMap(destination -> calculation.resolveTemplate("from", "Moon").resolveTemplate("to", destination.getDestination()) .request().rx(RxObservableInvoker.class).get(Calculat ion.class) );
return Observable.zip(recommended, forecasts, calculations, Recommendation::new).toList(); }
http://localhost:8080/jaxrs21/rx/agent/observable の実行結果の例
{ "processingTime": 1375, "recommended": [ { "destination": "Dominica", "forecast": "Rain", "price": 8696 }, {"destination": "Cape Verde", "forecast": "Sunny", "price": 1731 }, { "destination": "Chad", "forecast": "Flurries", "price": 8800 }, { "destination": "Portugal", "forecast": "Mostly Cloudy", "price": 8045 }, { "destination": "Algeria", "forecast": "Sunny", "price": 8227 } ], "visited": [ {
"destination": "Central African Republic" }, { "destination": "Malawi" }, { "destination": "Ethiopia" },
59
まとめ
同期型の実行と比べ、リアクティブは並列で REST リクエストを発行してお
り、実行時間が短縮されていることがわかります。
JAX-RS の Invocation.Builder に新しく rx メソッドが追加され、これによ
り REST のレスポンスを今回の場合は RxJava の Observable 型オブジェクト
として受け取ることができるようになりました。
final Observable<Destination> recommended = destination.path("recommended").request()
// Identify the user.
.header("Rx-User", "RxJava") // Reactive invoker.
.rx(RxObservableInvoker.class) <――
// Return a list of destinations.
.get(new GenericType<List<Destination>>() { })
// Emit destinations one-by-one. .flatMap(Observable::from)
// Remember emitted items for dependant requests. .cache(); } 日本オラクル株式会社 お問い合わせ:Oracle Digital 東京都港区北青山 2-5-8 0120-155-096 Oracle.com/ jp Oracle.com/jp/direct 本カタログの情報は、2017 年 5 月現在のものです。実際の製品とは内容が異なる場合があります。
*Oracle と Java は、Oracle Corporation およびその子会社、関連会社の米国およびその他の国における登録商標です。 文中の社名、商品名等は各社の商標または登録商標である場合があります。
Copyright © 2017, Oracle and/or its affiliates. All rights reserved. C O N N E C T W I T H U S
facebook.com/oracleJP
twitter.com/oracle