nullをsetterでインジェクトして欲しい
S2.3.8では、コンポーネントの組み立て結果(おそらくOGNL式を与えたcomponent定義の場合のみ)がnullの場合NullPointerExceptionを吐く(もしくはClassUnmatchRuntimeExceptionで型チェック落ちする)。でも、nullを
インジェクションしたい場合もあるんだー、というお話。
以下経緯というかストーリー。
S2Strutsを使ったプロダクトのチームと議論していて、あちこちのActionで「ユーザーの対応端末チェック」*1のコードがほぼまったく同じ位置に同じコールシーケンスで登場する、というものがあった。例えば
public String execute() { if (hogeService.getUserAgent(..)) { return SOMETHING_FAIL; } userDevice = deviceService.getDevice(...); if (userDevice == null) { return SOMETHING_UNSUPPORTED; } . .
というあたりが散らばっている、と。しかもActionではこのuserDeviceとかいうビーンを積極的に利用する。でも、メンバーはDRYを意識して「これはイヤだ」と。
で、解決策として
- Facadeみたいな感じでこれを連続して実行してくれる(スクリプトな)単一メソッドを提供するサービスを作る
- Utilを提供する
- しかし、serviceを使うのだからUtilはちょっと違うのでは (なるほど)
- AOPでやる(interceptorを作る)
- deviceBeanはActionが使う、ということは Actionがinterceptorに依存する...別にいいような悪いような
- どっちかっていうと気持ち悪い (ごもっとも)
- setRequest()のインジェクションみたく setUserDevice()してもらう!
とまあ、いろいろあがった挙句、面白そう、ということで一番下の「setterインジェクション」案が選ばれました。
いろいろ方法はあると思うのだけど、.diconでやってみることに。
deviceBeanInjector.getDeviceBeanByRequest(request)
これでActionに
public void setInjectedDeviceBean( DeviceBean ..) {..}
があれば、組み立て時にインジェクションされるはず!
で、これがあっさりうまくいったんです。instance="request"の場合、OGNLも遅延評価(とはいわないか?)されるので、リクエストごとにDeviceBeanがセットされたんです。これは良い!とひとしきり満足。
したかのように見えたんですが、このインタフェース、「非対応の場合はnullを返す」となってるんですね。ということは、setInjectedDeviceBean(null)がコールされる予定…だったのですが、OGNLの評価結果がnullの場合は NullPointerExceptionが出るという事態が発生。
NullObjectパターンを適用?しかしそれは影響範囲が大きい…とすると…
…で、今のところ考えてるのが
- s2container.diconを使った S2自体のモジュールを拡張する方法を使う
- ぬるぽが出ているのは オブジェクトを組み立てた後、nullに対してさらにBeanDescを取得しているから
- null に対してPropertyAssemblerとかを呼んでいるから
- だとしたら、nullが出てきたらそれ以上のアセンブルをしなければいいんじゃないか?
- 「nullの場合は組み立ては終り」というのはどっちの責務だろうか?
- ComponentDeployer デプロイする人が各アセンブラに組み立て指示しているから「指示しないようにする」
- PropertyAssemblerとか nullにプロパティ組み立て指示されたら「何もせずに終わる」
- ど、どっちでもいいのかなあ…
- 「nullの場合は組み立ては終り」というのはどっちの責務だろうか?
この OGNL式で書かれたcomponentの結果がnullになる場合に getComponent()が出来ない、という件って
変更してしまったら大変なことになるのだろうか。
結局、こんなクラスを作って、s2container.diconに書けば動いた!す、すばらしい。S2が自分自身をconfigurationContainerから構成するようになっていることがこんなにも素晴らしいとは。。。
public class NullAllowedAssemblerProvider extends AssemblerFactory.DefaultProvider { public MethodAssembler createInitMethodAssembler(final ComponentDef cd) { return new DefaultInitMethodAssembler(cd) { public void assemble(Object component) throws IllegalMethodRuntimeException { if (component == null) { return; } super.assemble(component); } }; } public PropertyAssembler createAutoPropertyAssembler(final ComponentDef cd) { return new AutoPropertyAssembler(cd) { public void assemble(Object component) { if (component == null) { return; } super.assemble(component); } }; } public PropertyAssembler createManualOnlyPropertyAssembler(final ComponentDef cd) { return new ManualOnlyPropertyAssembler(cd) { public void assemble(Object component) { if (component == null) { return; } super.assemble(component); } }; } }
以下、s2container.dicon
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.3//EN" "http://www.seasar.org/dtd/components23.dtd"> <components> <component class="some.packages.NullAllowedAssemblerProvider" /> </components>
*1:携帯電話向けのシステムなので