Java EE 7(GlassFish V4)でJAX-RSのMVCテンプレートを動かす
GlassFish V4でJersyのMVCテンプレートを動かしてみました。
基本は下のブログで書いたGlassFish V3の時と大きく変わりません。
Javaを知らない世代が今からはじめるJava EE開発
http://den2sn.hatenablog.com/entry/2012/12/19/001146
V4で注意する点は2つ
Viewableのクラスパスが変わっている
Jerseyのパッケージが「com.sun」から「org.glassfish」に変わったこともあり以前のパッケージは
com.sun.jersey.api.view.Viewable
となっていましたが、GlassFish V4で使用しているJersy2.0では
org.glassfish.jersey.server.mvc.Viewable
に変わっています。
Mavenプロジェクトの場合
<dependency> <groupId>org.glassfish.jersey.ext</groupId> <artifactId>jersey-mvc</artifactId> <version>2.0</version> <scope>provided</scope> </dependency>
を追加すれば大丈夫でしょう。
ApplicationConfigクラスでJspMvcFeatureを追加する
GlassFish V4ではそのままではJSPのプロバイダが登録されていないのでApplicationConfigでJspMvcFeatureを追加します。
Class jspProvider = Class.forName("org.glassfish.jersey.server.mvc.jsp.JspMvcFeature"); resources.add(jspProvider);
ApplicationConfigの全ソースはこんな感じ。
package com.den2sn.mavenproject1; import java.util.Set; import javax.ws.rs.core.Application; @javax.ws.rs.ApplicationPath("webresources") public class ApplicationConfig extends Application { @Override public Set<Class<?>> getClasses() { Set<Class<?>> resources = new java.util.HashSet<Class<?>>(); try { Class jsonProvider = Class.forName("org.glassfish.jersey.jackson.JacksonFeature"); resources.add(jsonProvider); } catch (ClassNotFoundException ex) { java.util.logging.Logger.getLogger(getClass().getName()).log(java.util.logging.Level.SEVERE, null, ex); } // ここから try { Class jspProvider = Class.forName("org.glassfish.jersey.server.mvc.jsp.JspMvcFeature"); resources.add(jspProvider); } catch (ClassNotFoundException ex) { java.util.logging.Logger.getLogger(getClass().getName()).log(java.util.logging.Level.SEVERE, null, ex); } // ここまで addRestResourceClasses(resources); return resources; } private void addRestResourceClasses(Set<Class<?>> resources) { resources.add(com.den2sn.javaee7test.TestResource.class); } }
これで快適なMVCライフを送る事が出来ます。
JavaからCacoo APIを簡単に呼び出せるCacoo4jを作った
Cacoo(https://cacoo.com/)のAPIをJavaから簡単に呼び出すライブラリを作りました。
・Cacoo4J
https://github.com/den2sn/cacoo4j
クラスパスにzip内の「cacoo4j-1.0.0.jar」を通すと使えるようになると思います。
認証にOAuthを使用する場合はさらに「signpost-core-1.2.1.1.jar」が必要ですがAPI Keyで認証する場合は不要です。
ソースとか汚いですがなんとなく動くと思うので気にしないで下さい。
JSFのFaceletsでxhtmlに直接アクセスさせない方法
JSFでFaceletsを作成すると/facesでアクセスする場合は良いのですが、直接xhtmlファイルにアクセスするとソースがそのまま表示されて微妙な感じですよね。xhtmlファイルに直接アクセスさせないようにするには基本JSPで行う対処法と変わりません。
ただし、JSPでもっともよく使われているWEB-INF配下にファイルを配置する方法が、faceletsの場合テンプレートファイルでは可能ですが普通のファイルでは使えないと思うのでその他2つの方法を試してみたいと思います。
web.xmlのsecurity-constraintを使用する方法
特にアプリケーションの認証でsecurity-constraintを使用していない場合、security-constraintでxhtmlにアクセス制限を付加する事で直接アクセスを防ぐ事ができます。ただし、web.xmlのURLパターンマッチングは簡単なマッチングしか記載出来ないためパスがfacesで始まるかどうかをハンドリングできません。そのためFacesServletのURLパターンマッチングを.jsf等に変えてあげる必要があります。
servlet-mappingを変更
<servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.jsf</url-pattern> </servlet-mapping>
security-constraintを設定
<security-constraint> <display-name>XHTML Security</display-name> <web-resource-collection> <web-resource-name>Protected Area</web-resource-name> <url-pattern>*.xhtml</url-pattern> </web-resource-collection> <auth-constraint> </auth-constraint> </security-constraint>
このような設定を行うことで.jsfへのアクセスは許可し、.xthmlへのアクセスは拒否することができます。パターンマッチングを*.jsfに変える事でURLが変わってしまうのでその点は注意して使用する必要があります。
Filterを使う方法
ServletFilterを使用してxhtmlへのアクセスを制限します。ここでもurl-patternマッチングが貧弱なので*.xhtmlへのアクセスをフィルタで受けてそのパスが/facesで始まっているか確認します。
web.xml
<filter> <filter-name>XHTMLSecurity Filter</filter-name> <filter-class>filter.XHTMLSecurityFilter</filter-class> </filter> <filter-mapping> <filter-name>XHTMLSecurity Filter</filter-name> <url-pattern>*.xhtml</url-pattern> </filter-mapping>
XHTMLSecurityFilter.java
package filter; import java.io.IOException; import javax.servlet.*; import javax.servlet.http.*; public class XHTMLSecurityFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String path = ((HttpServletRequest) request).getServletPath(); if (path.startsWith("/faces")) { chain.doFilter(request, response); } else { ((HttpServletResponse) response).sendError(HttpServletResponse.SC_NOT_FOUND); } } @Override public void destroy() { } }
これでxhtmlファイルが表示されてしまう問題が解消できます。
結論
web.xmlのURLパターンマッチングをどうにかしてほしい。
BootstrapやjQueryをWebJarsで簡単管理
BootstrapやjQuery等のクライアントサイドライブラリを管理するのって意外とめんどくさいですよね。WebJarsはよく使用されるクライアントサイドライブラリをJarに固めてJVMベースのWebアプリケーションで簡単に扱えるようにしようというプロジェクトです。
WebJars
http://www.webjars.org/
クライアントサイドライブラリをJarにすることでMaven等のライブラリ管理ツールでインストールや依存関係を管理する事が出来るようになります。
利用方法は非常に簡単。Mavenの場合、まずWebJarsの必要なライブラリをpom.xmlに記載します。例えばBootstrapを使用したい場合は以下のような感じ。
<dependencies> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>2.3.1</version> </dependency> </dependencies>
JSF2ではlibに配置されたjar内のMETA-INF/resources内のファイルに静的にアクセス可能なのであとはHTMLで参照すればOKです。たとえばJSFであればこんな感じで使用できます。
<h:outputStylesheet name="webjars/bootstrap/2.3.1/css/bootstrap.min.css" /> <h:outputScript name="webjars/bootstrap/2.3.1/js/bootstrap.min.js" /> <h:outputScript library="webjars/jquery/1.9.0" name="jquery.min.js" />
ここで試してみると気づくと思いますが、特に定義していなくてもbootstrapが依存しているjqueryのライブラリもちゃんと一緒にlibに配置されて使用可能になっています。
ドキュメントを見ると、Play 2やServlet3、Grails、Spring MVC等でも利用可能なようなので大抵のJVMベースのWebアプリケーションでは使用できるのではないでしょうか。
やや最新のライブラリの配置が遅かったりするようですが、わざわざ各サイトにいってjsファイル等をダウンロードしてくるよりはとても簡単にライブラリ管理が出来るようになります。
リリース間近!Java EE 7の気になるところ
やっとやっと待ちに待ったJava EE 7のリリースが近づいて来ましたね。
予定では5/13(日本だと14日?)にFinal Releaseのようです。
https://java.net/projects/javaee-spec/pages/Home#Java_EE_7_Schedule
リリースに備えて軽く自分の備忘録も兼ねて予習しておきます。
あんまり追っかけているわけではないので間違ってたらすみません。
各テクノロジーのバージョンはこんな感じ。
Java EE 7で新規で追加されるもの
テクノロジー | バージョン |
---|---|
Java API for JSON Processing (JSR-353) | 1.0 |
Java API for WebSocket (JSR-356) | 1.0 |
Batch Application for the Java Platform (JSR-352) | 1.0 |
Concurrency Utilities for Java EE (JSR-236) | 1.0 |
アップデートされるもの
テクノロジー | EE6バージョン | EE7バージョン |
---|---|---|
Enterprise JavaBeans (EJB) | 3.1 | 3.2 |
Servlet | 3.0 | 3.1 |
JavaServer Pages (JSP) | 2.2 | 2.3 |
Expression Language (EL) | 2.2 | 3.0 |
Java Messaging Service (JMS) | 1.1 | 2.0 |
Java Transaction API (JTA) | 1.1 | 1.2 |
JavaMail API | 1.4 | 1.5 |
Java Connector Architecture (JCA) | 1.6 | 1.7 |
Web Services | 1.3 | 1.4 |
Java API for XML-based Web Services (JAX-WS) | 2.2 | 2.2 |
Java API for RESTful Web Services (JAX-RS) | 1.1 | 2.0 |
Java Architecture for XML Binding (JAXB) | 2.2 | 2.2 |
Java EE Management | 1.1 | 1.1 |
Java Authorization Service Provider Contract for Containers (JACC) | 1.4 | 1.5 |
Java Authentication Service Provider Interface for Containers (JASPIC) | 1.0 | 1.1 |
JSP Debugging | 1.0 | 1.0 |
JavaServer Pages Standard Tag Library (JSTL) | 1.2 | 1.2 |
Web Services Metadata for the Java Platform | 2.1 | 2.1 |
JavaServer Faces (JSF) | 2.0 | 2.2 |
Common Annotations | 1.1 | 1.2 |
Java Persistence API (JPA) | 2.0 | 2.1 |
Bean Validation | 1.0 | 1.1 |
Managed Beans | 1.0 | 1.0 |
Interceptors | 1.1 | 1.2 |
Contexts and Dependency Injection for Java EE (CDI) | 1.0 | 1.1 |
Dependency Injection for Java | 1.0 | 1.0 |
あと削除ものとしてJAX-RPCのような既に使われないであろう仕様は
外されている感じです。
JCache (JSR-107)は残念ながらEE7には間に合わなかったようですね。
まだ標準化は難しいんでしょうか。
注目どころ
注目どころというよりは私が気になっているところをまとめます。
・WebSocket
WebSocketもEE7で導入になります。
これでリアルタイム系のWebアプリも増えてきたりするかもしれないですね。
アノテーションベースでサーバ実装がとても簡単のようです。
・Batch
やっぱり企業系だとバッチ処理系のニーズは多いんでしょうかね。
Springのバッチ機能を踏襲しているようなので
ファーストリリースにしては結構使いやすい感じに
落ち着いているのではないかと思っています。
・JTA
仕様の中に@Transactionalっていうアノテーションがあるようなのですが、
これってSessionBeanもういらないんじゃない的な
POJOにトランザクション指定できる機能なのでしょうか。
そうだとしたらアプリケーションがよりシンプルに
開発出来るようになるのではないかと思っています。
JSF2.0で一覧表示
JAX-RSでデータの一覧表示を作成してみましたが今度は同じ画面をJSFで作成してみます。
JSFはサーバサイドのメモリーを多く使用するため不特定多数ユーザが利用するサービスには不向きですが、クライアントサーバ型のアプリケーションのようにWEBアプリケーションを作成する事ができるので社内アプリケーションや利用者が限定されているアプリケーションを簡単に作成&メンテナンスするにはとても良いアーキテクチャです。JSFはバージョン2.0よりテンプレートにFaceletsが導入されビューがとても簡単に作成できるようになりました。また、ajax対応等現状のWEBのアーキテクチャが多く取り入られています。
バッキングビーンの作成
JSFではバッキングビーンという画面の値や処理を記載するクラスを作成するのが一般的です。まずはそのクラスを作成します。
package com.den2sn.mavenproject1; import java.util.ArrayList; import java.util.List; import javax.enterprise.context.RequestScoped; import javax.inject.Named; @Named @RequestScoped public class HelloBackingBean { private List<String> messages; public void init() { messages = new ArrayList<String>(); messages.add("message1"); messages.add("message2"); messages.add("message3"); } public List<String> getMessages() { return messages; } }
バッキングビーンは単純なJavaのクラスです。今回はinitという初期処理を行うメソッドとメッセージの値を返すgetMessagesメソッドを作成します。このクラスを画面側より参照するためにCDIの@Namedアノテーションをつけています。@Namedアノテーションを付加すると画面側よりデフォルトではバッキングビーンのクラス名でインスタンスを参照する事ができるようになります。またメッセージデータをリクエスト中は保持したいので@RequestScopedアノテーションを付加しています。
画面の作成
次に画面を作成してみます。画面にはFaceletsというものを使用しますが中身はxhtmlでJSPとほぼ変わらないようなイメージで画面開発ができます。
メニューの新規ファイルから
「JavaServer Faces」 > 「JSFページ」 を選択します。
ファイル名を設定します。今回はindexとします。
生成された画面を一覧を表示するように修正します。
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"> <h:head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Facelet Title</title> </h:head> <h:body> <h1>Hello World!</h1> <f:event type="preRenderView" listener="#{helloBackingBean.init}" /> <div id="list"> <ui:repeat var="row" value="#{helloBackingBean.messages}"> <div>#{row}</div> </ui:repeat> </div> </h:body> </html>
h:やf:やui:がついているものがFaceletsのカスタムタグです。まずはf:eventタグでinitの処理を呼び出し、ui:repeatタグでメッセージの一覧を表示しています。Netbeansで画面を作成するとわかりますが、カスタムタグやバッキングビーンの参照を自動補完してくれるためとてもスムーズにコーディング出来ます。
前回のJAX-RSのサンプルでは事後のajaxの部分更新を考慮して一覧部分のみ非同期で画面を取得していましたが、JSFではajaxの部分更新をJSF自体がサポートしているため初期画面表示時に一覧画面も一緒に描画しています。
設定ファイルの修正・作成
Faceletsを使用するにはweb.xmlにFacesServletの設定が必要です。Netbeansを使用して上記手順でFacelets画面を作成していれば自動的に必要な記載をNetbeansが追加してくれるので特に修正は不要です。またCDIを有効にするためにbeans.xmlをWEB-INFフォルダに配置する必要がありますがbeans.xmlが無い状態でCDIのアノテーションを使用すると下記のようにワーニングを表示してくれるので修正候補に沿ってbeans.xmlを生成すれば問題ありません。この辺りのJavaEE開発の利便性がNetbeansを使用するメリットでもあります。
JAX-RSとKnockout.jsでJSON連携
JavaEE Advent Calendar 2012のブログ記事で書いたJAX-RSのサンプルではjersyのViewableクラスを使用したJSPによる画面描画を紹介しました。今回はjsonデータを返却してクライアントサイドで描画する方法を紹介します。
今回はクライアントサイドの描画にシンプルで使い勝手の良いJavaScript MVVMフレームワークであるKnockout.jsを使用しました。
Knockout.jsのダウンロード
Knockout.jsのサイト(http://knockoutjs.com/)より最新版のKnockout.jsをダウンロードし(今回はknockout-2.2.1.jsを使用)プロジェクトのフォルダに配置します。
HTMLファイルの修正
HTMLをKnockout.jsのテンプレートに即して修正します。
<%@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> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script type='text/javascript' src='knockout-2.2.1.js'></script> </head> <body> <h1>Hello World!</h1> <div id="list" data-bind="foreach: list"> <div data-bind="text: $data"></div> </div> <script type="text/javascript"> var viewModel = { list: ko.observableArray() }; ko.applyBindings(viewModel); $(document).ready(function(){ $.getJSON("webresources/hello/list", function(data){ viewModel.list(data); }); }); </script> </body> </html>
knockout.jsのライブラリを読み込みviewModelの定義を行います。knockout.jsではJavaScript上のオブジェクトデータと画面上の表示をko.applyBindings()で紐づけることで双方向に同期を取る事ができます。JavaScriptの処理ではjQueryのgetJSONメソッドで取得したJSONデータをリストにセットしているだけですが、データのセットと同時に画面の再描画も行ってくれます。前回の記事ではlist.jspに一覧のテンプレート処理を記載していましたが<div id="list">タグに直接テンプレート処理を記載しています。
JAX-RSの修正
JAX-RSの処理をJSONを返却するように修正します。
package com.den2sn.mavenproject1; import java.util.ArrayList; import java.util.List; import javax.ws.rs.core.Context; import javax.ws.rs.core.UriInfo; import javax.ws.rs.Path; import javax.ws.rs.GET; import javax.ws.rs.Produces; @Path("hello") public class HelloResource { @Context private UriInfo context; public HelloResource() { } @GET @Path("list") @Produces("application/json") public List<String> list() { List<String> messages = new ArrayList<String>(); messages.add("message1"); messages.add("message2"); messages.add("message3"); return messages; } }
@Producesのメディアタイプが"text/html"から"application/json"になり、戻り値がViewableからList<String>を返すように変更しています。JAX-RSはメディアタイプを見て戻り値を自動で適切なデータに変換してくれるためとてもシンプルに処理を記載する事ができます。
web.xmlの追加
JAX-RSのデフォルトではJAXBを使用してJSONデータの変換を行います。しかしJAXBを使用する場合、戻り値のオブジェクトに@XmlRootElementアノテーションをつける必要があるなどなにかとめんどくさい点があります。そこで今回はJSONのパーサにJacksonを使用したいと思います。パーサにJacksonを有効にするにはweb.xmlでinit-paramを指定します。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" 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_3_0.xsd" version="3.0"> <servlet> <display-name>JAX-RS Servlet</display-name> <servlet-name>org.netbeans.rest.application.config.ApplicationConfig</servlet-name> <init-param> <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name> <param-value>true</param-value> </init-param> </servlet> </web-app>
servlet-nameにはjax-rsのパスが定義されているjavax.ws.rs.core.Applicationを継承したクラスのクラス名を記載します。
実行
あとは実行すると前回と同様一覧画面が表示されます。
サーバでJSONを返すようにする事でサーバの処理と画面の処理をより疎結合に開発する事が出来るようになります。どちらが良いとは言えませんがJSONを返却する今回の方式の方がよりRESTサービス的ではあると言えると思います。
JSF2.0で一覧表示に続く