相変わらずJDK1.2で自転車走行していますが、やはりDIコンテナバブルを経験した我々(?)にとって、コンテナが無いのは何かとめんどくさいものです。
/ ̄ ̄\ / _ノ \ | ( ●)(●) . | (__人__) コンテナ欲しいだろ… | ` ⌒´ノ 常識的に考えて… . | } . ヽ } ヽ ノ \ / く \ \ | \ \ \ | |ヽ、二⌒)、 \
ということで、1.2でも使えそうなコンテナをネットを駆使して当たってみましたが、小さなコンテナでもProxyやバイトコードエンジニアリングを多用していたり、アノテーションバリバリだったり、ジェネリック上等だったりと、「1.2世代の産物ではない」という現実をまざまざと見せ付けられました。
仕方が無いので、機能的には簡単でもいいので、コンテナチックなものを作ることにしました。
あまり手の込んだことはしてもしょうがないので、
・設定ファイルはxml ・ListとMapぐらいはプロパティに使いたい ・型変換はそれなりに
というぐらいの要件で着手です。
まず設定ファイルのパースですが、Digesterを利用しました。
名前が暗号用ライブラリっぽいとか、エラー時にデバックがし難いとかを除けば、個人的にはかなり好きなプロダクトです。もちろん、Strutsに付随しているというのが大きなポイントですが。
次に型変換ですが、BeanUtilを調べた所、Converterがすごいことになっていて、そいつに全部任せることにしました。
BeanUtils.setProperty()を使うと、内部でこれだけのコンバーターが動くようです。
BigDecimalConverter BigIntegerConverter BooleanArrayConverter BooleanConverter ByteArrayConverter ByteConverter CharacterArrayConverter CharacterConverter ClassConverter DoubleArrayConverter DoubleConverter FileConverter FloatArrayConverter FloatConverter IntegerArrayConverter IntegerConverter LongArrayConverter LongConverter ShortArrayConverter ShortConverter SqlDateConverter SqlTimeConverter SqlTimestampConverter StringArrayConverter StringConverter URLConverter
昔からこんなにあったかな?
それを踏まえて、以下が材料です。
・JDK1.2 ・commons-digester-1.4.1 ・commons-beanutils-1.7.0 ・commond-logging-1.0.4 ・XMLパーサ(crimson-1.1.3)
普通にBeanを組み立てるだけなら簡単ですが、一応DIコンテナということで、参照の解決をしないといけません。
色々考えましたが、とりあえずすべてのBeanをシングルトンにしてしまえばうまく行きそうなので、それで作成することにしました。
設定ファイルは、Spring先生を参考にしました。全部属性で記述するのは、そのほうがDigesterに都合がいいからです。
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="bean1" class="test.container.TestBean1"> <property name="p1" value="testp1"/> <property name="p2"> <list> <entry ref="bean2"/> <entry value="p2list"/> </list> </property> <property name="p3"> <map> <entry key="foo" ref="bean2"/> <entry key="bar" value="p2map"/> </map> </property> <property name="p4" ref="bean2"/> </bean> <bean id="bean2" class="test.container.TestBean2"> <property name="p1" value="12"/> <property name="p4" ref="bean1"/> </bean> </beans>
これを解決できれば、要件は満たせそうです。
ということで、Spring先生のインターフェース名をパクりつつ、以下のコードを実装しました。
package container; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.digester.Digester; import org.apache.commons.digester.Rule; import org.xml.sax.Attributes; /** * @author c9katayama */ public class BeanFactory { private Map beanMap = new HashMap(); public BeanFactory(InputStream is) { init(is); } private void init(final InputStream is) { // 2度読むので、一旦ロード final byte[] config = BeanFactoryHelper.readConfig(is); // 一旦全beanを登録 Digester digester = new Digester(); digester.setValidating(false); digester.addRule("beans/bean", new BeanRegisterRule()); try { digester.parse(new ByteArrayInputStream(config)); } catch (Exception e) { throw new NestedRuntimeException(e); } // プロパティ解決 digester = new Digester(); digester.setValidating(false); digester.addRule("beans/bean", new BeanStackRule()); digester.addRule("beans/bean/property", new PropertyRule()); digester.addRule("beans/bean/property/list", new CollectionRule()); digester.addRule("beans/bean/property/list/entry", new EntryRule()); digester.addRule("beans/bean/property/map", new CollectionRule()); digester.addRule("beans/bean/property/map/entry", new EntryRule()); try { digester.parse(new ByteArrayInputStream(config)); } catch (Exception e) { throw new NestedRuntimeException(e); } } public Object getBean(String name) { Object bean = beanMap.get(name); if (bean == null) { throw new RuntimeException("Bean not found. name=" + name); } return bean; } }
はじめに設定ファイルを読み込んで、1回パースします。初回パース時は、beanタグだけを読み、class属性からbeanをインスタンス化して、id属性もしくはname属性をキーにしてbeanMapにputします。
2回目のパース時には一通りのbeanがbeanMapに入っていることになるので、propertyタグを読み、value属性ならBeanUtilsで型変換セット、ref属性ならbeanMapからbeanを取得してセット、という流れで、一通りのセットアップをします。
あとはgetBean()でbeanを取得します。
BeanFactory factory = new BeanFactory(BeanFactoryTest.class.getResourceAsStream("beans.xml")); TestBean1 bean1 = (TestBean1) factory.getBean("bean1"); assertEquals("testp1",bea1.getP1());
機能的にはコンテナというにはおこがましいですが、エラー処理をもうちゃんとしてやれば、今回の要件はこれで満たせそうです。
ソースはこちらからどうぞ。
http://hatena.souko105.net/20080409/container.zip