気軽な前後処理 ActionHook

LastaFluteの特徴の一つです。

ActionHookとは?

Actionでは、Interceptorはあまり使わず、もっと気軽に前後処理を実装できる ActionHook という機能が用意されています。別のクラスではなく、Action自身に前後処理を書きます。

Interceptorは便利ですが、あまり乱用するとすぐにカオスになりがちで、ディベロッパーからの精神的距離も遠く、修正の敷居が高いものです。 また、できるだけ Action を (Javassistで) エンハンスさせずに素のクラスとしてキープすることで、スタックトレースの汚れや起動スピードの劣化を抑制できます。

互いのメリット・デメリットを加味して、LastaFluteのポリシーとして適した方法はどちらだろう?と考慮した上で、ActionHook を用意しました。

hookメソッド

hookBefore(), hookFinally()

LastaFluteでは、全ての Action クラスがが共通のスーパークラスを継承することを推奨しています。 よって、すでに hookBefore(), hookFinally() というメソッドが定義済みの状態となるので、 それをオーバーライドして、リクエスト時に前後処理を挟むことができます。

hookBefore()
FormやBodyのマッピングの後、Executeメソッドよりも前に呼ばれる
hookFinally()
正常終了でもバリデーションエラーでも例外でも最後に必ず呼ばれる

いずれも Action のトランザクションの外側です。

hookBefore() にて、有効な ActionResponse (isDefined()がtrueになるもの) を戻すと、その Action はその時点で終了となり、呼ばれるはずだった Execute メソッドは呼び出されません。 何か業務的なチェック処理で前提を満たしていないときに別の画面に飛ばすようなケースで利用します。

hookFinally() は、システム例外でも業務例外でも呼び出されます。 引数の ActionRuntime にて、例外が発生の有無や、ActionResponseの戻りの判定などを行うことができるので、それを使って必要に応じて分岐させます。

流れのイメージ

こういう感じです。

e.g. flow image @Java
try {
    godHandPrologue()
    hookBefore()
    *execute action
} catch (...) {
    godHandMonologue()
} finally {
    hookFinally()
    godHandEpilogue()
}

スーパークラスでのHook

主には、抽象のスーパークラスで利用することが想定されます。

Actionクラス間で共通

スーパークラスで定義することで、そのスーパークラスを継承している Action 間で共通の Hook 処理となります。 よって、プロジェクト全体で Hook したいときは、プロジェクトの BaseAction, アプリごとに Hook したいときは、アプリの BaseAction で実装するとよいでしょう。 (Maihamaプロジェクトであれば、MaihamaBaseAction と DocksideBaseAction)

Exampleテンプレートだと、すでに [App]BaseAction にて、Hookメソッドが定義されています。 こちらに Action 間で共通のオリジナル Hook 処理を追加していくとよいでしょう。

e.g. Maihamaプロジェクトの MaihamaBaseAction の hook たち @Java
@Override
public ActionResponse hookBefore(ActionRuntime runtime) {
    ... (your orignal hook implementation here)
    return super.hookBefore(runtime);
}

@Override
public void hookFinally(ActionRuntime runtime) {
    super.hookFinally(runtime);
    ... (your orignal hook implementation here)
}

サブクラスでのHook (末端のActionクラス)

末端の具象クラスのActionでも、オーバーライドすることができます。

Executeメソッド間で共通

この場合は、Action間ではなく、その Action の Execute メソッド間で共通の Hook 処理 として利用することができます。

e.g. hook in concrete Action @Java
@Override
public ActionResponse hookBefore(ActionRuntime runtime) {
    ... (your orignal hook implementation here)
    return super.hookBefore(runtime);
}

@Override
public void hookFinally(ActionRuntime runtime) {
    super.hookFinally(runtime);
    ... (your orignal hook implementation here)
}

画面描画処理など、どの Execute メソッドでも実行しなければ

superを呼び忘れないで

superの呼び忘れ注意! (共通処理が消えてしまいます) IDEの補完でオーバーライドすれば安心です。

e.g. オーバーライドの補完、Eclipseの場合 @Java
hoo // と入力して、ctrl+space の補完で、オーバーライドしたいメソッドを選ぶ
--
// ずどーん
@Override
public ActionResponse hookBefore(ActionRuntime runtime) {
    // TODO Auto-generated method stub
    return super.hookBefore(runtime);
}

描画データは setupHtmlData() で

画面を表示する上で最低限必要なデータ

複数の画面で共通となる、画面を表示する上で最低限必要なデータを準備する処理、例えば、ヘッダーの表示情報やリストボックスの値など、これらは hookFinally() + runtime.isForwardToHtml() で実現することができますが、使うプロジェクトであればその組み合わせは何度も登場する定型的なものになると予想されます。

setupHtmlData()でOK

そのために、あらかじめ setupHtmlData() というメソッドが用意されていますので、 そのメソッドをオーバーライドし、その中で描画データの準備をするとよいでしょう。

e.g. Actionのサブクラスにて、画面描画処理をsetupHtmlData()で実装 @Java
@Override
protected void setupHtmlData(ActionRuntime runtime) {
    super.setupHtmlData(runtime);
    runtime.registerData(..., ...); // そのHTMLを描画するための準備処理
}

実質的に、hookFinally() + runtime.isForwardToHtml() と同じです。

スーパークラスならヘッダー情報とか

スーパークラスであれば、ヘッダーやフッターなど共通の部分を表示するためのデータが想定されます。

サブクラスならリストボックスとか

サブクラスであれば、Executeメソッド間で共通の、最低限その画面を表示するためのデータ、例えばリストボックスの値などが想定されます。 バリデーションエラーが発生したときも準備する必要がありますから、つどつど Action の中でコピーするのではなく、この setupHtmlData() で実装するとよいでしょう。

ActionRuntimeとは?

その Action への処理の状態を保持したオブジェクトで、Actionの定義情報に加え、いま実行されたフォワード先、リダイレクト先、発生した例外などを調べることができます。

TODO jflute 書き途中

業務例外は組み込み Hook の中で

TODO jflute 書き途中

とりあえず、MessageKeyApplicationException を継承したクラスを投げれば業務例外として扱われます。

JsonResponseのActionであれば、ApiFailureHook の handleApplicationException() が呼ばれます。

GodHand とは?

気になりましたか?

TODO jflute 書き途中