Java EE (JAX-RS) + JavaScript (Backbone.js) でクロスオリジン通信する方法
クライアント側とサーバサイドとで異なるオリジン(プロトコル+ドメイン+ポート番号)間での通信方法メモ
今回の方針
jsonp を使用するやり方もあるが、今回は XMLHttpRequest Level2 で可能となった Cross-Origin Resource Sharing という仕組みを使用する。
具体的に何をどうするのか
Wikipedia の XMLHttpRequest のクロスドメインについての記述によると…
Access-Control-Allow-Origin: * をサーバサイドから返すレスポンスのヘッダに追加してやればいけるらしい。
実際にヘッダに Access-Control-Allow-Origin: * を入れる
ヘッダに Access-Control-Allow-Origin: * を追加する処理を書いていく。 サーバーサイドには Java EE の JAX-RS の使用する。
package foo.bar.jot_down.resources; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; import foo.bar.jot_down.data.SampleData; @Path("sample.json") public class Sample { @GET @Produces( MediaType.APPLICATION_JSON + "; charset=UTF-8" ) public Response getIt() { SampleData data = new SampleData(); data.setValue( "ほげほげ" ); ResponseBuilder responseBuilder = Response.status( Status.OK ); responseBuilder.header( "Access-Control-Allow-Origin", "*" ); responseBuilder.entity( data ); Response response = responseBuilder.build(); return response; } }
クライアントサイドも実装する
クライアントサイドは JavaScript フレームワークに Backbone を使用する想定。 以下のような model を作成して fetch() する。
module.exports = (function() { 'use strict'; var $ = require('jquery'); var _ = require('underscore'); var Backbone = require('backbone'); Backbone.$ = $; return Backbone.Model.extend({ url: 'http://localhost:8080/jot_down2/resources/sample.json', parse: function (response) { alert( response.value ); return response; } }); }());
実際に通信できているか確認する
200 OK が帰ってきているし、ヘッダに Access-Control-Allow-Origin: * が追加されていることもある。
何が嬉しいか
たぶん、今後デスクトップアプリは Electron とかで HTML CSS JavaScript 等のWeb の技術を使って作る手法が主流になる(と個人的には楽だな)と思っていて、その場合 サーバーサイドはクロスオリジン通信を考慮したAPIを作成する機会が出てくると思う。 で、とりあえず今回は Javaでそれができたからめでたしめでたし。
ファイルの読み込みを Java SE7 から追加された try-with-resources文 を使って書き直す
Java SE7 から追加された try-with-resources文 の使い方を、SE6 以前の書き方との比較も含めてメモっとく。 普段の業務では SE6 しか使ってないから、資格の勉強で1度覚えても忘れちゃうんよな。 (サンプルのソースは Java SE6 以前の方も SE7 以降の try-with-resources文使用ヴァージョンも テキストファイル「test.txt」の内容をすべて出力するという物 )
まず、Java SE6 以前での書き方。 ポイントは finally 句で close() すること。 また、その際に BufferedReader のインスタンスが null である場合を考慮して if で処理を分岐すること。 これしとかないと NullPointerException が発生してしまう。
public static void main( String[] args ) { BufferedReader br = null; try { br = new BufferedReader( new FileReader( "./test.txt" ) ); String text; while ( (text = br.readLine()) != null ) { System.out.println( text ); } } catch ( IOException e ) { e.printStackTrace(); } finally { if ( br != null ) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } }
で、Java SE7 から追加された try-with-resources文 を使用したヴァージョン。 見た目での一番の違いは
try の後いきなり () 小かっこが登場しているところ。 この中かっこの中には、 ・java.lang.AutoCloseable ・java.io.Closeable のどちらかのインタフェースの実装クラスのリソースの生成処理を書く。
ここ、Oracle 認定 Java Programmer SE7 GOLD で必ず試験に出ます。(個人の感想です。)
また、この小かっこの中では複数のリソースの生成処理を書くこともできる。(ここに関してはまた今度書く。) で、小かっこの中に生成したリソースは自動的に close() が呼ばれ、解放される。
次に、なんやこれ?ってところは finnaly 句でやってる
e.getSuppressed();
getSuppressed() は Java SE7 で Throwable クラスに追加されたメソッドで、抑制された例外の配列を取得するメソッドであり、ここれは、自動的に呼び出された close() メソッド実行中に発生した例外を取得している。
Java SE6 以前のソースと比較すると、それまで finnaly 句でやっていた close するための分岐やら何やらが無くなってすっきりしている気がする。 自前で書かなきゃいけない内容があると、それだけいらんミスする可能性が増えるしね。
public static void main ( String[] args ) { try ( BufferedReader br = new BufferedReader( new FileReader("./text.txt") ); ) { String text; while ( (text = br.readLine()) != null ) { System.out.println( text ); } } catch ( IOException e ) { e.printStackTrace(); Throwable[] suppressedArr = e.getSuppressed(); for ( Throwable throwable : suppressedArr ) { throwable.printStackTrace(); } } }
Java EE で アプリケーション起動時になんか処理するやり方
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency>
あとはServletContextListenerを実装したクラスを追加する。 例では、起動時にアプリケーションの名前とバージョンを設定から引っ張ってきて表示している。
@WebListener public class InitListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent arg0) { System.out.println( " == Application start == " ); System.out.println( Config.getInstance().getAppName() ); System.out.println( Config.getInstance().getVersion() ); System.out.println( " ======================= " ); } @Override public void contextDestroyed(ServletContextEvent arg0) { // do nothing } }