■応用の森 JSON形式から動的にクラスを生成する |
2ー1)DB構造の前説 |
動的クラス変換の説明する前に、DBの構造がわからないと 変換イメージがつかみにくいので、データ構造を解説しておきます。 DBはPostgreSQL ver9.5.1 を使用します。 テーブル構造は以下となります。 create table t_zipref ( prefcd char(2) PRIMARY KEY, /* 都道府県コード */ prefnm varchar(16), /* 都道府県名称 */ areacd char(2) /* 地域コード */ ); テーブルの型は、一般的なレコード型のテーブルです。(jsonb型は使いません) データは以下のような感じになっています。 promenade=# Select * From t_zipref; prefcd | prefnm | areacd --------+-------- +-------- 01 | 北海道 | 01 02 | 青森県 | 02 03 | 岩手県 | 02 04 | 宮城県 | 02 05 | 秋田県 | 02 06 | 山形県 | 02 07 | 福島県 | 02 08 | 茨城県 | 03 09 | 栃木県 | 03 10 | 群馬県 | 03 : :以下省略 ただ、このままではJSONから動的クラスを生成する目的としては適当ではないため、 PostgreSQL DBの組み込み関数json_agg()を使って、レコード型をJSON型に変換します。 promenade=# SELECT json_agg(t_zipref) FROM t_zipref; 出力結果は以下のようになります。 json_agg ------------------------------------------------------ [{"prefcd":"40","prefnm":"福岡県","areacd":"08"}, + {"prefcd":"41","prefnm":"佐賀県","areacd":"08"}, + {"prefcd":"42","prefnm":"長崎県","areacd":"08"}, + {"prefcd":"43","prefnm":"熊本県","areacd":"08"}, + {"prefcd":"44","prefnm":"大分県","areacd":"08"}, + {"prefcd":"45","prefnm":"宮崎県","areacd":"08"}, + {"prefcd":"46","prefnm":"鹿児島県","areacd":"08"}, + {"prefcd":"47","prefnm":"沖縄県","areacd":"08"} : :以下省略 ] このSQL文をMyBatis XML Mapperで定義してあげます。 この時の検索キーは、地域コードで絞り込みします。 このJSON形式の文字列をGsonでパージングして、Javassistを使って動的にクラスを生成します。 詳しい説明は後ほど行います。 |
2ー2)XML Mapper |
DBからJSON形式のデータを検索する MyBatis Mapperです。 |
WebContent/WEB-INF/mappers/PrefMap.xml |
前述のとおり、通常のレコード型テーブルから、PostgreSQL組み込み関数、json_agg()を使って JSON形式の検索結果を返すMapperです。
|
2-3)Mapperインターフェース |
XML Mapperを実行する Mapperインターフェースです。 |
src/main/java/jp/dip/arimodoki/mapper/PrefMap.java |
XML MapperとMapperインターフェースの関係の基本的な説明は、 本編「■Springの小径 8-3)XMLMapper」を参照してください。
|
2-4)ビジネスロジック |
話の流れの都合上、ビジネスロジックの説明をすることにします。 コントローラから受け取ったリクエストパラメータ(地域コード)を検索キーとして、 前述の、Mapperインターフェースを実行してDBからJSON文字列を検索します。 |
src/main/java/jp/dip/arimodoki/model/BlZip.java |
Mapperから返却されたJSON形式は、これ自体は問題ないのですが、 後述する、JSONデシリアライザは、key:value の組を持たないとパージングできないため、 ここで返却されたJSONの配列に対して、key:"preflist” を付加します。 そして、JSONコンバータのデシリアライザを実行することで、 JSON構造から、動的にクラスが生成されます。 生成されたクラスのオブジェクトをgetObject()で取り出して、フォームBeanにセットし コントローラに返却します。 同階層にインターフェースBlZipIf.javaが必要ですが、 Eclipseの「インターフェース抽出」で自動生成できるので、ここでは割愛します。
|
2-5)コントローラ |
view(HTML)からのリクエストを、@RequestBody String jParamで受け取り、 リクエストパラメータbeanにセットします。 前述の、ビジネスロジックのDB検索メソッドgetJPrefList()を実行します。 |
src/main/java/jp/dip/arimodoki/cntl/ZipCode.java |
フォームBeanに格納された動的クラスオブジェクトを、view(HTML)に返却し、 view側(zipPref.html)でリストを表示します。
|
2-6)リクエストパラメータBean |
view(HTML)からのリクエストパラメータ(地域コード)を格納するBeanクラスです。 |
src/main/java/jp/dip/arimodoki/model/JsonReqArea.java |
同階層にインターフェースJsonReqAreaIf.javaが必要ですが、 Eclipseの「インターフェース抽出」で自動生成できるので、ここでは割愛します。
|
2-7)フォームBean |
コントローラとビジネスロジック間のデータ受け渡し用フォームBeanクラスです。 |
src/main/java/jp/dip/arimodoki/model/FormZip.java |
コントローラで受け取ったリクエスト(地域コード)を格納する、jsonReqAreaと、 DB検索結果の動的クラスオブジェクトを格納するprefListを保持します。 同階層にインターフェースFormZipIf.javaが必要ですが、 Eclipseの「インターフェース抽出」で自動生成できるので、ここでは割愛します。
|
2-8)JSONコンバータ |
このサンプルの心臓部である、JSONデシリアライザクラスです。 これは、【■JSON ぶらり旅 グラフ描画】ですでに出現している、 JSON変換クラス JsonConvert.java に新しい動的Class作成メソッドを追加拡張してあります。 なかなかボリュームのあるクラスになってしまったので、 本編で説明するより、ソースに直接記述した方がわかりやすかったので 詳しい説明はソース中に書いているつもりです。 動きを理解したかったら、ソースをよく読んでみてください。 |
src/main/java/jp/dip/arimodoki/common/JsonConvert.java |
色々なメソッドがごちゃごちゃありますが。 JSON文字列から、動的クラスを生成するメソッドは、 public Object Deserialize(String jsonData, String rootClassName) です。 ざっくりと大まかに説明しておくと、 引数jsonDataで指定されたJSON文字列を、Gsonでパージングしながら Javassistで動的にクラスを生成しつつ、Javaのリフレクションで アクセサー(getter/setter)を追加する処理を行います。 同時に、JSON中のデータ(value)のセットも行います。 同階層にインターフェースJsonConvertIf.javaが必要ですが、 Eclipseの「インターフェース抽出」で自動生成できるので、ここでは割愛します。 |
いちいちDTOクラスを作らなくてもいいというのは ぱっと見、夢のような手法ですが、それなりに弱点はあります。 まず、Javassistを使って、JSON構造からクラスのプロパティを生成しているので、 このプロパティに対する入力規制(validation)アノテーションを付けることができません。 つまり、レスポンス用としては効果がありますが、リクエスト用のFormBeanとしては使えない。 次に、プログラム実行時に、クラスが存在しなかったら自動的に作られるのはいいのですが、 開発者から見ると、どんなクラスでどんなプロパティがあるのか、表面的には全くわかりません。 見えているのは、JSON文字列として返されるデータ構造だけなので、 JSONの構造からクラス構造を想像し、頭の中で変換してプログラミングする必要があります。 慣れれば、そんなに難しくはありませんが、最初は戸惑うこと必至です。 あと、rootClassNameの名前のクラスが存在しなかったら作成するので、 へたに毎回ユニークになるようなrootClassNameを与えてしまうと、 無限にクラスを作ってしまい、システムリソースを枯渇させてしまうので注意が必要です。 ![]() ![]() ![]() |
DynamicClass view(HTML) ![]() |
DynamicClass Java |