キャストの要らないStruts Actionはどうよ

Strutsは1.2系で終了しているid:paulowniaです。

今更Strutsネタも無いだろうという気がするが、あっちこっちで使われているフレームワークなので保守案件も少なくない。それにStrutsに代わる本命フレームワークが無いのも事実。

というわけでStrutsアプリにCRUD処理の追加のお仕事。だまってMappingDispatchAction使えば実装30分で終了だが、

public class HogeAction extends MappingDispatchAction {

    public ActionForward delete(ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res) {
        HogeForm hForm = (HogeForm)form;
        this.hogeDao.deleteById( hForm.getId() );
    }

    public ActionForward list(ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res) {
        HogeForm hForm = (HogeForm)form;
        hForm.setHoges( this.hogeDao.findAll() );
    }

    // 以下略
}

毎度のことながらActionFormのキャストが嫌な感じ。メソッド内に同じインスタンスを参照するローカル変数が二つある時点でウンコのようなコード略してウンコード。何とかしたい。

こーいう嫌な処理を肩代わりする地味なActionクラスを書いてみた。まずURLベースのメソッドディスパッチを実装。MappingDispatchAction のように executeでないメソッドが呼べるが、URLを見てディスパッチするのでstruts-configにparamaterを指定しなくていい。たとえば /hoge/create.do に対しては createメソッドが呼ばれる。リフレクションは空気のように使おう。ビジネスロジック的な処理で使うべきではないがこーいう場面では活用しよう。

このときコントローラメソッドの ActionForm には型パラメータを使えるようにする。どうせ定義するであろうCRUDメソッドはabstractメソッドとして記述しておくと継承クラス作成時にコード補完で楽できる。

public abstract class CrudAction<E extends ActionForm> extends Action {

    private static Log log = LogFactory.getLog(CrudAction.class);

    @SuppressWarnings("unchecked")
    public ActionForward execute(
            ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res)
            throws Exception {

        // SuppressWarnings below
        E eform = (E)form;

        String path = mapping.getPath();
        String methodName = path.substring(path.lastIndexOf('/') + 1);

        try {
            Method method = this.getClass().getMethod(methodName, ActionMapping.class, eform.getClass(), HttpServletRequest.class, HttpServletResponse.class);
            if (Modifier.isPublic(method.getModifiers())) {
                ActionForward forward = (ActionForward)method.invoke(this, mapping, eform, req, res);
                if (forward != null) {
                    return forward;
                }
            }
        } catch (NoSuchMethodException e) {
                String message = "Method " + methodName + " is not defined." ;
                log.error(message);
                throw new ServletException(message);
        }
        return this.unspecified(mapping, form, req, res);
    }

    public abstract ActionForward delete(ActionMapping mapping, E form, HttpServletRequest req, HttpServletResponse res) throws Exception;

    public abstract ActionForward create(ActionMapping mapping, E form, HttpServletRequest req, HttpServletResponse res) throws Exception;

    public abstract ActionForward update(ActionMapping mapping, E form, HttpServletRequest req, HttpServletResponse res) throws Exception;

    public abstract ActionForward edit(ActionMapping mapping, E form, HttpServletRequest req, HttpServletResponse res) throws Exception;

    public abstract ActionForward list(ActionMapping mapping, E form, HttpServletRequest req, HttpServletResponse res) throws Exception;

    protected ActionForward unspecified(
            ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.sendError(404, "Forwarding Action Not found.");
        return null;
    }
}

これを継承したCrudActionの実装は

public class HogeAction extends CrudAction<HogeForm> {

    @Override
    public ActionForward delete(ActionMapping mapping, HogeForm form, HttpServletRequest req, HttpServletResponse res) {
        this.hogeDao.deleteById( form.getId() );
    }

    @Override
    public ActionForward list(ActionMapping mapping, HogeForm form, HttpServletRequest req, HttpServletResponse res) {
        form.setHoges( this.hogeDao.findAll() );
    }

    // 以下略
}

キャスト不要で便秘が解消したようにすっきりした。Strutsの良いところは拡張が簡単なこと。最近のStruts開発は色々なツール使って楽してるのだろうけど、そういうのが準備できない場合でも小細工して奇麗にコード書けるよ、という辺りでよろしく。

個人的にはS2Struts使うのが幸せになれると思いますよ〜