- simple json encoder/decoder for java
SourceForge.jp
 

基本的な説明

JSONICには、JSONを使ったWebサービスが簡単に構築できるサーブレットが二種類用意されています。

サーブレット説明
RPCServletJSON-RPC 1.0/2.0 仕様に対応したRPC(Remote Procedure Call)サービスを構築できます。
RESTServletGET/POST/PUT/DELETEなどHTTP Methodをベースに操作を行うRESTfullなWebサービスを構築できます。
WebServiceServletは、JSONIC 1.2以降は非推奨となりました。互換性維持のために以前同様の形で利用可能ですが、今後はRESTServletおよびRPCServletを利用してください。

RPCサーブレット

RPCサーブレットは、JSON-RPC 1.0 および JSON-RPC 2.0 の両方をサポートしたWEBサービス構築用サーブレットです。

■ RPCサーブレットの概要

RPCサーブレットを使うと、指定したパスに対しJSONをPOSTすることで、対象クラスのメソッドを呼び出すことができます(GET/PUT/DELETEは無効です)。paramsに指定された配列の値はメソッドの引数に指定された型に従い自動的に変換されます。なお、クラス名はUpperCamel、メソッド名はLowerCamelに自動的に変換されます。そして、実行後、戻り値がJSONに変換されクライアントに返されます。

POST /rpc.json HTTP/1.0
...
Content-Type: application/json

{ "method": "class.method", "params": [ arg1, arg2, ... ], "id": request_id }

class, methodにはそれぞれ変数の値、argNにはメソッドの引数を設定してください。requesst_idには送受信の同期確認用のキーとしてnull以外の任意の値を設定してください(HTTPでは、送信と受信は同期処理ですのでほとんど意味はありませんが、省略すると通知(Notification)モードとなりレスポンスのメッセージボディが返されませんので必ず値を指定してください)。

例えば、mappingsに "/{package}/{class}.{ext}": "boo.${package}.${class}Service" という指定があった場合、 /foo/woo/rpc.jsonというパスに次のJSONがPOSTすると、boo.foo.woo.CalcServiceクラスのplusメソッドが呼び出されます。

POST /foo/woo/rpc.json HTTP/1.0
...
Content-Type: application/json

{ "method": "calc.plus", "params": [1,2], "id": 1 }

boo.foo.woo.CalcServiceは次のように実装されていたとします。

package boo.foo.woo;

public class CalcService {
    public int plus(int a, int b) {
        return a + b;
    }
}

この時、レスポンスのメッセージボディとしては次のような結果が返されます。

HTTP/1.0 200 OK
...
Content-Type: application/json

{ "result": 3, "error": null, "id": 1 }

各パラメータの意味は次の通りです。

方向パラメータ説明
リクエストjsonrpcJSON-RPC 2.0で接続する時のみ"2.0"を指定します。1.0で接続する時は指定しません。
methodメソッド名を指定します。パス変数でclassが指定されている場合はメソッド名を、指定していない場合はクラス名とメソッド名を「class.method」の形式で指定する必要があります。なお、クラス名はパッケージを含んだ完全名ではなく、後述で設定するマッピングに対応した名前を指定してください。
paramsパラメータを指定します。JSON arrayを指定した場合はメソッドの引数に指定された型に変換され引渡されます。JSON-RPC 2.0ではJSON objectを指定できますが、その場合は、第一引数に指定された型に変換され引渡されます。
id設定するとレスポンスのid値として同じ値が戻されます。なお、JSON-RPC 1.0ではidがnullの時、2.0ではidを省略すると通知(Notification)モードとなりレスポンスのメッセージボディが返されませんので必ず値を指定してください。
レスポンスjsonrpcJSON-RPC 2.0で接続した時のみ"2.0"が返されます。1.0では設定されません。
result成功した時には、結果が設定されます。JSON-RPC 2.0では成功した時のみ設定されます。
errorエラーの時にはエラー情報が設定されます。JSON-RPC 2.0ではエラーの時のみ設定されます。
idリクエストで設定されたidの値がそのまま戻されます。

■ エラーオブジェクト

RPCサーブレットでエラーが発生した場合にはレスポンスのメッセージボディでクライアントに通知されます。ステータスコードは、エラーの有無に関わらず200 OKが返されます。

HTTP/1.0 200 OK
...
Content-Type: application/json

{ "result": null, "error": { "code": -32600, "message": "Invalid Request.", "data": {} }, "id": 1 }

errorの値には code, message, dataの三つのキーを持つJSON objectが設定されます。codeとmessageについては次表を参照してください。dataには投げられた例外のプロパティがセットされます(ただし、Throwableクラスのプロパティは除外されます)。

エラー内容HTTP Status CodeMessage Body
JSONリクエストがJSON-RPCのリクエストとして不正 200 OK
{
  "code": -32600,
  "message": "Invalid Request."
}
methodで指定したクラス/メソッドが見つからない(※1) 200 OK
{
  "code": -32601,
  "message": "Method not found."
}
paramsが不適切(※2) 200 OK
{
  "code": -32602,
  "message": "Invalid params."
}
JSONの解析に失敗した 200 OK
{
  "code": -32700,
  "message": "Parse error.",
  "data": {
    "columnNumber": エラーが発生した列番号,
    "errorOffset": エラーが発生した位置,
    "lineNumber": エラーが発生した行番号
  }
}
errorsに設定された例外が発生した 200 OK
{
  "code": errorsで設定した値,
  "message": 例外オブジェクトの単純クラス名 + ": " + getMessage()の値,
  "data": 例外オブジェクト(ただし、Throwableクラスに定義されているプロパティは除く)
}
その他の例外が発生した 200 OK
{
  "code": -32603,
  "message": "Internal error."
}
(※1) クラス/メソッドが見つからなかった時だけでなく、メソッドからIllegalStateExceptionやUnsupportedOperationExceptionが投げられた場合も同じエラーが返されます。
(※2) Convertに失敗した場合だけでなく、メソッドからIllegalArgumentExceptionが投げられた場合も同じエラーが返されます。

■ 設定方法

RPC サーブレットは、web.xmlにRPCServletを指定し、パスとClassのマッピングなどの設定を行うだけです。

<servlet>
    <servlet-name>rpcServlet</servlet-name>
    <servlet-class>net.arnx.jsonic.web.RPCServlet</servlet-class>
    <init-param>
        <param-name>config</param-name>
        <param-value>
        {
            "debug": true,
            "mappings": {
                "/{package}/{class}.json": "sample.web.${package}.service.${class}Service",
                "/rpc.json": "sample.${class}Service"
            }
        }
        </param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>rpcServlet</servlet-name>
    <url-pattern>*.json</url-pattern>
</servlet-mapping>

configで設定できる値は次の通りです(errorsを除き、RESTServletと同じです)。

キー値型説明
containernet.arnx.jsonic.web.Containerクラスのインスタンスを取得するためのコンテナを設定します。デフォルトは、net.arnx.jsonic.web.Containerです。
encodingjava.lang.StringRequest/Responseの文字エンコーディングを設定します。デフォルトはUTF-8です。
expirejava.lang.Booleanクライアントキャッシュを抑制するHTTPヘッダを出力します(Cache-Control: no-cache, Pragma: no-cache, Expires: Tue, 29 Feb 2000 12:00:00 GMT)。デフォルトはtrueです。
debugjava.lang.Booleanデバッグモードの有効/無効を切り替えます。デフォルトはfalseです。
mappingsjava.util.Map<String, String>URLパスとクラスのマッピングを行います。 パス中の{name}で囲まれた部分はパス変数として、クラス名の${name}に置換されたりメソッドの引数に設定されます(※3)。また、{name:regex}と記載することで、変数の定義を設定することができます(definitionsより優先します)。
definitionsjava.util.Map<String, Pattern>mappings中の変数の定義を正規表現で設定します。設定されない場合は[^/().]+が設定されたものと扱われます。
initjava.lang.String処理の実行前に呼び出されるメソッド名を設定します。デフォルトは"init"です。
destroyjava.lang.String処理の実行後に呼び出されるメソッド名を設定します。デフォルトは"destroy"です。
processornet.arnx.jsonic.JSON処理に使用するJSONクラスを設定します。デフォルトではThrowableのメソッドのみ無視するJSONクラスが設定されます。
namingConversionboolean呼び出し時のクラス名、メソッド名の変換を行うか否か設定します。デフォルトはtrueです。
errorsjava.util.Map<Class< extends Exception>, Integer>Exceptionクラスとエラーコードのマッピングを行います(継承したクラスも対象になります)。
(※3) 変数名のうち、classとpackageだけは特殊な扱いがされます。デフォルトでは、class変数中の文字列はUpperCamelに変換され、package変数中の「/」は「.」に変換されます。 また、URLパスにはコンテキストパスを含める必要はありません。

JSONIC 1.1 RPCモードからの変更点

RESTサーブレット

RESTサーブレットは、GET/POST/PUT/DELETEなどHTTP Methodをベースに操作を行うRESTfullなWebサービス構築用サーブレットです。

■ RESTサーブレットの概要

RESTサーブレットを使うと、GET/POST/PUT/DELETEなどのHTTP Methodに従って、対象となったクラスのメソッドが呼び出されます。その後、戻り値がJSONに変換されクライアントに返されます。

HTTP MethodとJava メソッド名のデフォルトのマッピングは次の通りです(※4)

HTTP MethodJava メソッド名引数
GETfindリクエストパラメータを.あるいは[]で区切られた階層構造とみなし引数の型に従い変換し設定されます。
POSTcreateContent-Typeが「application/json」の時は、メッセージボディのJSON文字列を引数の型に従い変換し設定されます。
Content-Typeが「application/x-www-form-urlencoded」の時はリクエストパラメータを.あるいは[]で区切られた階層構造とみなし引数の型に従い変換し設定されます。
PUTupdate
DELETEdelete
(※4) ブラウザなどでは、PUT/DELETEが使えない場合があります。そのような場合の代替手段として、クエリ変数に「_method=HTTP Method名」を指定することもできます。

例えば、mappingsに "/{package}/{class}.{ext}": "boo.${package}.${class}Service" という指定があった場合、 /foo/woo/resource.jsonというパスをGETすると、boo.foo.woo.ResourceServiceクラスのfindが呼び出されます。

GET /foo/woo/resource.json HTTP/1.0
...

boo.foo.woo.ResourceServiceは次のように実装されていたとします。

package boo.foo.woo;

public class ResourceService {
	public Object find(Map params) {
	    List<Map> list = Database.select("select * from resource", params);
	    return list;
	}
}

この時、レスポンスのメッセージボディとしては次のような結果が返されるかもしれません(※5)

HTTP/1.0 200 OK
...
Content-Type: application/json

[
  { "id": 1, "name": "boo", "age": 10 },
  { "id": 2, "name": "foo", "age": 12 },
  { "id": 3, "name": "woo", "age": 14 }
]
(※5) JSONはobjectかarrayより始まる必要があるため、それ以外の要素に変換される型の戻り値(例えば、boolean/int/Dateなど)の場合にはSC_NO_CONTENTが返されます。

引数には送信されたデータが指定された型に従い変換され設定されます。引数への設定は、データの送信方法によって次のような違いがあります。

Content TypeRequest Type説明
application/jsonJSON objectパス変数、リクエストパラメータの順に追加されたJSON objectが設定されます(同じキーが複数出現した場合は配列化されます)。
JSON array送信されたJSON arrayを引数リストとして扱います。なお、第一引数がJSON objectである場合には、上記と同様にパス変数、リクエストパラメータ、第一引数の順でデータが追加されます。 その他の型や第二引数以降はそのまま設定されます。
URLパラメータ
application/x-www-form-urlencoded
リクエストパラメータを.あるいは[]で区切られた階層構造とみなし変換したオブジェクトが設定されます。

さきほどの例でidが3のデータを指定する場合は次のようにします。

GET /foo/woo/resource.json?id=3 HTTP/1.0
...

この時、レスポンスのメッセージボディとしては次のような結果が返されるかもしれません。

HTTP/1.0 200 OK
...
Content-Type: application/json

[
  { "id": 3, "name": "woo", "age": 14 }
]

よりREST的にしたいのであれば、mappingsのパスに /{package}/{class}/{id}.{ext} を定義するなどしてパラメータをURLに含めることなどもできます。

■ エラーオブジェクト

エラーの発生はHTTP Status Codeによりクライアントに通知されます。

エラー内容HTTP Status CodeMessage Body
クラス/メソッドが見つからない(※6) 404 Not found  
送信されたJSONの解析/変換に失敗した 400 Bad request  
errorsに設定された例外が発生した errorsで設定したステータスコード
{
  "name": 例外オブジェクトの単純クラス名
  "message": 例外オブジェクトのgetMessage()の値,
  "data": 例外オブジェクト(ただし、Throwableクラスに定義されているプロパティは除く)
}
その他の例外が発生した 500 Internal Server Error  
(※6) クラス/メソッドが見つからなかった時だけでなく、メソッドからIllegalStateExceptionやUnsupportedOperationExceptionが発生した場合も同じエラーが返されます。
(※7) メソッドからIllegalStateException、UnsupportedOperationExceptionが発生した場合は除きます。

■ 設定方法

REST サーブレットは、web.xmlにRESTServletを指定し、パスとClassのマッピングなどの設定を行うだけです。

<servlet>
    <servlet-name>restServlet</servlet-name>
    <servlet-class>net.arnx.jsonic.web.RESTServlet</servlet-class>
    <init-param>
        <param-name>config</param-name>
        <param-value>
        {
            "debug": true,
            "mappings": {
                "/{package}/{class}/{id:[0-9]+}.json": "sample.web.${package}.service.${class}Service",
                "/{package}/{class}.json": "sample.web.${package}.service.${class}Service",
                "/{class}.json": "sample.${class}Service"
            }
        }
        </param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>restServlet</servlet-name>
    <url-pattern>*.json</url-pattern>
</servlet-mapping>

configで設定できる値は次の通りです(method, verbを除き、RPCServletと同じです)。

キー値型説明
containernet.arnx.jsonic.web.Containerクラスのインスタンスを取得するためのコンテナを設定します。デフォルトは、net.arnx.jsonic.web.Containerです。
encodingjava.lang.StringRequest/Responseの文字エンコーディングを設定します。デフォルトはUTF-8です。
expirejava.lang.Booleanクライアントキャッシュを抑制するHTTPヘッダを出力します(Cache-Control: no-cache, Pragma: no-cache, Expires: Tue, 29 Feb 2000 12:00:00 GMT)。デフォルトはtrueです。
debugjava.lang.Booleanデバッグモードの有効/無効を切り替えます。デフォルトはfalseです。
mappingsjava.util.Map<String, String>URLパスとクラスのマッピングを行います。 パス中の{name}で囲まれた部分はパス変数として、クラス名の${name}に置換されたりメソッドの引数に設定されます(※8)。また、{name:regex}と記載することで、変数の定義を設定することができます(definitionsより優先します)。
definitionsjava.util.Map<String, Pattern>mappings中の変数の定義を正規表現で設定します。設定されない場合は[^/().]+が設定されたものと扱われます。
initjava.lang.String処理の実行前に呼び出されるメソッド名を設定します。デフォルトは"init"です。
destroyjava.lang.String処理の実行後に呼び出されるメソッド名を設定します。デフォルトは"destroy"です。
processornet.arnx.jsonic.JSON処理に使用するJSONクラスを設定します。デフォルトではThrowableのメソッドのみ無視するJSONクラスが設定されます。
namingConversionboolean呼び出し時のクラス名、メソッド名の変換を行うか否か設定します。デフォルトはtrueです。
errorsjava.util.Map<Class< extends Exception>, Integer>ExceptionクラスとHTTP Status Codeのマッピングを行います(継承したクラスも対象になります)。
methodjava.util.Map<String, String>HTTP Methodに対応するメソッド名を設定します。デフォルトは、{ "GET": "find", "POST": "create", "PUT": "update", "DELETE": "delete" }です。なお、パス変数にmethodが設定されている場合は無視されます。
verbjava.util.Set<String>使用できるHTTP Methodを制限します。デフォルトは、["HEAD", "GET", "POST", "PUT", "DELETE", "OPTIONS"]です。HEADとOPTIONSを使う場合は、methodも対応付ける必要があります。
(※8) 変数名のうち、classとpackageだけは特殊な扱いがされます。デフォルトでは、class変数中の文字列はUpperCamelに変換され、package変数中の「/」は「.」に変換されます。また、URLパスにはコンテキストパスを含める必要はありません。

なお、method, verbに関しては、mappingsの各データ毎にも設定できます。その場合は次のようにマッピング先をJSON objectにします(マッピング先のプロパティ名はtargetにしてください)。

    "mappings": {
        "/{package}/{class}.json": {
            "target": "sample.web.${package}.service.${class}Service",
            "method": { "GET": "print" },
            "verb": [ "GET" ]
        },
        ...
    }

■ パス変数によるメソッドの指定

本来RESTでは、HTTP Methodで処理が決定されるためRPC的な任意のメソッド呼び出しは推奨されませんが、それでは不便が多いためパス変数にmethodを指定することで、任意のメソッド呼び出しを可能にしました。

例えば、次のように設定を行うと /foo/calc.sum.json を呼び出すと sample.web.foo.service.CalcServicesum メソッドが呼びだされます。

    "mappings": {
        "/{package}/{class}.{method}.json": "sample.web.${package}.service.${class}Service"
    }

なお、このような使い方をする際は、verbと組み合わせてHTTP Methodを制限して使うことが推奨されます(GETで更新処理などを行うと、検索エンジンのクロールでデータが削除されるなどの問題が発生する可能性があります)。

■ JSONP - JSON with padding

HTTP MethodがGETの場合、リクエストパラメータとしてcallback=Function名を指定することでJSONPによる返答を返すことができるようになります。

<script type="text/javascript">
    function call(value) {
        alert(value);
    }
</script>

...

<script type="text/javascript" src="http://host/hoge.json?callback=call"></script>

■ JSON以外のレスポンス

CSVやXMLなどJSON以外のレスポンスを返したい場合やリダイレクトしたい場合は、処理の最後でHttpServletResponse#flushBuffer()を実行して出力をコミットしてください。ステータスコードやコンテントタイプの設定、JSONの出力などJSONIC側の後続処理を抑制することができます。

public class HogeService {
    // 自動インジェクション
    public HttpServletResponse response;
    
    // CSVを出力する
    public void print() throws IOException {
        response.setCharcterEncoding("MS932");
        PrintWriter writer = response.getWriter();
        writer.write("a,b,c\r\n");
        
        // バッファをフラッシュして、出力を確定させる
        response.flushBuffer();
    }
}

JSONIC 1.1 RESTモードからの変更点

DIコンテナ対応

RPCサーブレット、RESTサーブレットは、内部のコンテナを切り替えることで呼び出し対象のインスタンスを任意のDIコンテナにて管理することが可能です。

JSONICでは、Seasar2Spring FrameworkGoogle Guiceに対応したContainerを標準添付しています(※1.2よりパッケージ名が変更されています。ご注意ください)。このContainerを利用すると、DI Container上で管理されているコンポーネントをWebServiceとして利用することができるようになります。

<servlet>
    <servlet-name>rpcServlet or restServlet</servlet-name>
    <servlet-class>net.arnx.jsonic.web.RPCServlet or RESTServlet</servlet-class>
    <init-param>
        <param-name>config</param-name>
        <param-value>
          // "container": (net.arnx.jsonic.web.Containerを実装したクラス)
          // Seasar2対応Container
          "container": "net.arnx.jsonic.web.extension.S2Container" 
          // Spring Framework対応Container
          "container": "net.arnx.jsonic.web.extension.SpringContainer"
          // Google Guice対応Container
          "container": "net.arnx.jsonic.web.extension.GuiceContainer"
          ...
        </param-value>
    </init-param>
</servlet>

デフォルトでは最低限の機能のみ持つnet.arnx.jsonic.web.Containerが使われます。このコンテナが持つ機能は次の通りです。

(※9) 呼び出されるメソッド名は設定で変更可能です。

暗黙オブジェクトは以下のように設定してください。クラスだけでなくフィールド名も合わせる必要があります(デフォルトコンテナのみの機能です)。

public class HogeService {
    public ServletConfig config;
    public ServletContext application;
    public HttpServletRequest request;
    public HttpServletResponse response;
    public HttpSession session;
}

コンテナ自身にHttpServletRequestとHttpServletResponseのDI機能がないSpringContainerに関しては、setterによるインジェクション機能を提供しています(下記例を参照)。

public class SpringDrivenService {
    // setterを用意すると自動で挿入します。プロパティ名を一致させる必要はありません。
    public void setRequest(HttpServletRequest request) {
        ...
    }
    
    public void setResponse(HttpServletResponse response) {
        ...
    }
}
JSONIC version 1.2.0~1.2.7にて、デフォルトコンテナとSpringContainerを使用していた場合、この機能を用いると他のスレッドのrequest/response/sessionを取得してしまう脆弱性が存在しました(この問題はS2ContainerやGuiceContainerを利用していた場合には発生しません)。 同機能を利用している場合は、1.2.8以降にバージョンを上げるようお願いいたします。また、これに伴い Container クラスから request/response が削除されました。Container内の任意のメソッド内でrequest/responseを参照する場合は、ExternalContextを使用する必要があります。

処理エラーの取り扱い

処理でエラーが発生した場合、専用のログを取ったり、、トランザクションをロールバックしたいなどあるかもしれません。その場合は、コンテナを継承してexecuteメソッドをオーバーライドします。

public class TransactionalContainer extends Container {
    ...

    // 処理実行時によばれます。
    public Object execute(JSON json, Object component, Method method, List<?> params) throws Exception {
        Object ret = null;
        try {
            ret = super.execute(json, component, method, params);
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
            throw e;
        }
    }
}

Gatewayフィルタ

JSONICでは、おまけ機能としてJSONを使ってServletで良く使う各種の機能を実装したFilterを提供しています。JSONICの書式を使えるため手軽に設定が可能です。

最初にマッチしたパスの設定が使われますが、そこで設定が行われなわれていない場合、ルートの設定が初期値として利用されます。パスには正規表現が利用できます。

<filter>
    <filter-name>Gateway Filter</filter-name>
    <filter-class>net.arnx.jsonic.web.GatewayFilter</filter-class>
    <init-param>
        <param-name>config</param-name>
        <param-value>
            // 共通設定
            encoding: 'UTF-8'          // 文字コード設定
            locale: 'en'               // Responseのロケールを設定
            compression: true          // GZip圧縮
            
            // 拡張子がjsonのパスを対象
            '.+\.json': {
                expire: true           // クライアントキャッシュを無効化
            }
            
            // 例:日本向け設定
            '/ja/([^.]+)': {
                forward: '/$1.json'     // JSON Web Serviceに転送
                encoding: 'SHIFT_JIS'
                expire: true
                locale: 'ja-JP'
                access: ['jpuser']    // アクセス可能なロール
            }
        </param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>Gateway Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

configで設定できる値は次の通りです。なお、これらの設定はフィルタの設定に関わらず一度だけしか適用されません。

キー値型説明
encodingjava.lang.StringRequest/Responseの文字エンコーディングを設定します。デフォルトはnullです。
compressionjava.lang.BooleanクライアントからAccept-Encoding: gzip or x-gzipが送られる場合、ResponseをGZip圧縮します。
expirejava.lang.Booleanクライアントキャッシュを抑制するHTTPヘッダを出力します(Cache-Control: no-cache, Pragma: no-cache, Expires: Tue, 29 Feb 2000 12:00:00 GMT)。デフォルトはfalseです。
forwardjava.lang.String指定されたパスに転送します(パスはコンテキストパス以下を指定します。正規表現の置換変数が利用できます)。
accessjava.util.Set<String>アクセス可能なアプリケーションロールを配列で指定します(認証そのものはコンテナの機能などを使う必要があります)。
localejava.util.LocaleResponseのロケールを設定します。

なお、encodingとexpireに関してはRPCサーブレットやRESTサーブレット側にも同様の設定が用意されており、そちら側の設定が優先されます。