JavaのWebアプリケーション開発フレームワークによる、Webサイト開発の顛末記です。

EclipseのMavenを使った、Spring-MVC、Thymeleaf、MyBatis 等のプログラミングテクニックを、
備忘録的に記録しています。実際に動くソースコードを多用して説明していますので、
これからEclipseや、Spring-MVCを始めたいと思っている人にとって、少しでも参考になれば幸いです。
■応用の森 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を与えてしまうと、
無限にクラスを作ってしまい、システムリソースを枯渇させてしまうので注意が必要です。

 Gson 2.6.2 API リファレンス
 Javassist API リファレンス
 リフレクションの解説