JSON-RPCを触ってみた。

http://oss.metaparadigm.com/jsonrpc/
から、ダウンロード。

JSON-RPCは、JavaScriptからサーバサイドのJavaオブジェクトをリモート呼び出しするライブラリ。

サーバサイドのJavaオブジェクトを以下のようなJavaScriptのコードから呼び出せる。

var jsonrpc = new JSONRpcClient("/jsonrpc/JSON-RPC");
var result = jsonrpc.hello.sayHello("Hoge");

上記コードでは、サーバのセッションに"hello"という名前で設定されているオブジェクト*1sayHelloメソッドを呼び出している。


上記コードを呼び出すための準備は以下のようになる。


まずは、web.xmlで、JSONRPCServletを、/JSON-RPCマッピングする。

  <servlet>
    <servlet-name>JSONRPCServlet</servlet-name>
    <servlet-class>com.metaparadigm.jsonrpc.JSONRPCServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>JSONRPCServlet</servlet-name>
    <url-pattern>/JSON-RPC</url-pattern>
  </servlet-mapping>

次に、実際に呼び出されるJavaのクラスを用意。

package hoge;
public class Hello {
    public String sayHello(String who) {
        return "Hello " + who;
    }
}

最後に、上記コードを含んだJSP(hello.jsp)を用意。

<jsp:useBean id="JSONRPCBridge" scope="session"
     class="com.metaparadigm.jsonrpc.JSONRPCBridge" />
<% JSONRPCBridge.registerObject("hello", new hoge.Hello()); %>
<html>
  <head>
    <script type="text/javascript" src="jsonrpc.js"></script>
  </head>
  <body>
    <script type="text/javascript">
	  jsonrpc = new JSONRpcClient("/jsonrpc/JSON-RPC");
	  var result = jsonrpc.hello.sayHello("Hoge");
	  document.write(result);
    </script>
  </body>
</html>

このJSPの要点は以下の3つ。

  • JSONRPCBridge"JSONRPCBridge"という名前でセッションに設定。
  • JSONRPCBridgeオブジェクトのregisterObject()hoge.Helloオブジェクトを登録。
  • JSON-RPCで用意されているjsonrpc.jsを読込。

こいつらを以下の構成でWebアプリとしてまとめて、

 /
  +--jsonroc.js
  +--hello.jsp
  +--WEB-INF/
     +--web.xml
     +--lib/
        +--jsonrpc.jar

適当にサーバにデプロイして、hello.jspを呼びせば、画面に"Hello Hoge" と表示される。


割と簡単にサーバサイドと連携できる。


このときのサーバとのやり取りを覗くと、リクエストでは

{"method": "hello.sayHello", "params": ["Hoge"]}

がリクエストボディに設定され、レスポンスボディには以下のテキストが設定されて返されている。

{"result":"Hello Hoge"}

*1:実際はセッションに設定されているJSONRPCBridgeにhelloオブジェクトを登録する。

非同期でやる。

非同期でリモートメソッドをコールする。


先のJSPJavaScript部分を以下のように書き換え。

jsonrpc = new JSONRpcClient("/jsonrpc/JSON-RPC");
var callback = function(result, error, profile) {
  document.write(result);
}
jsonrpc.hello.sayHello(callback, "Hoge");


リモートメソッドの第1引数に、コールバックするクロージャを与える。
それだけ。


クロージャの引数は、以下の3つ。

  • result ・・・ リモートメソッドの戻り値
  • error ・・・ エラーが発生したときの、その内容
  • profile ・・・ 非同期呼出にかかった時間を計算するための情報

profileは通常はundefinedであるが、JSONRpcClient.profile_async(jsonrpc.jsの中) をtrueにすることで得ることができる。

JSONRPCBridge

JSONRPCBridgeには、register〜というメソッドがいくつかある。
それぞれを以下に説明する。

void registerObject(Object key, Object o)

oインスタンスメソッド全てをリモートメソッドとして登録する。
これは先の例でも使った。

void registerClass(String name, Class clazz)

clazzのクラスメソッド全てをリモートメソッドとして登録する。

GlobalBridgeオブジェクト

全てのリクエストで呼出可能なリモートメソッドを登録するときに使う。
GlobalBridgeとは、JSONRPCBridgeクラスのクラス変数。
型は、JSONRPCBridgeなので、今まで説明したregisterXXXXはすべて使える。


いままで説明したメソッドを使った場合と、GlobalBridgeを使った場合で何が違うかというと。
GlobalBridgeでリモートメソッドを登録すると、そのメソッドは全てのクライアントで呼出可能になるということ。


また、同じ名前でリモートメソッドが登録されているときは以下の順で選ばれる。(上にある方が優先度が高い。)

  1. インスタンスメソッド
  2. GlobalBridgeのインスタンスメソッド
  3. クラスメソッド
  4. GlobalBridgeのクラスメソッド

void registerReference(Class clazz)

中身(プロパティ)を見せたくないクラスを登録する。
例えば、セキュリティに関わるようなクライアントに見せちゃいけない情報を持つクラスを登録する。


以下のように、Barクラスを持つFooクラスがある。

public class Foo {
  private Bar bar = new Bar();
  public Bar getBar() { return bar; }
  public void setBar(Bar bar) { this.bar = bar; }
}

public class Bar {
  public String getName() { return "BARID"; }
}

上記のFooクラスのオブジェクトが"foo"という名前で登録されているとき、Barオブジェクトを取得するJavaScriptコードは以下のようになる。

var jsonrpc = new JSONRocClient("/jsonrpc/JSON-RPC");
var result = jsonrpc.foo.getBar();

registerReferenceが呼ばれていない場合、resultには、以下のようなオブジェクトが返される。

{"javaClass":"mypackage.Bar","id":"BARID"};

一方、以下のように、registerReferenceが呼ばれている場合、

JSONRPCBridge.registerReference(mypackage.Bar.class);

resultには、以下のようなオブジェクトが設定される。

{"javaClass":"mypackage.Bar","objectID":33507544,"JSONRPCType":"Reference"}

ただし、直接JSONRPCBridgeに登録されているBarオブジェクトを呼び出す場合は、中身は見えてしまう。

void registerCallableReference(Class clazz)

ネストしたリモート呼出を許可したいクラスを設定する。
このメソッドを使うと、リモート呼出で取得したオブジェクトが持っているメソッドをさらにリモート呼出することが可能になる。


registerReferenceで使用したクラスを用いて説明する。

通常、JavaScriptのコードから、Fooオブジェクトが持つidプロパティを取得するコードは以下のように書く。

var jsonrpc = new JSONRocClient("/jsonrpc/JSON-RPC");
var result = jsonrpc.foo.getBar().id;

JSONRPCのデフォルトでは、リモート呼出(getBarの呼出)の結果が、特定のクラス(String,List,int,Integer等)でない場合は、JavaBeansの規約に従い呼出結果のオブジェクトをJavaScriptのオブジェクトに変換する。


なので、上記のコード〜.getBar().idで望む結果を得ることができる。
これはこれで便利、だけどBarクラスにあるプロパティしか見えない。


そこでregisterCallableReferenceにBarクラスを登録する。

JSONRPCBridge.registerCallableReference(mypackage.Bar.class);

すると、以下の呼出が可能になる。

var jsonrpc = new JSONRocClient("/jsonrpc/JSON-RPC");
var result = jsonrpc.foo.getBar().getId();

ただし、〜.idのような参照はできなくなる。

ポリモれない。。orz

オブジェクトを登録するとこで、Class#getDeclaredMethods()呼んでる。。

例えば、

public Parent {
  public String getLastName() { return "山田"; }
  public String getFirstName() { return "太郎"; }
}

public Child extends Parent {
  public String getFirstName() { return "一郎"; }
}

のとき、Childを"parent"という名前で登録して、JavaScriptで以下のコードを書く。

var firstName = jsonrpc.parent.getFirstName();
var lastName = jsonrpc.parent.getLastName();

lastNameに"山田"って帰ってきて欲しいんだけど。
メソッドがないって怒られる。


これできた方がいいと思うんだけどなぁ。


getDeclaredMetods()呼んでるところを、stopclass=java.lang.ObjectにしたIntrospector#getBeanInfo()にしたら、いい感じで動くようになったけど。


なんか、理由があるのかな。