GroovyMarkupの仕組みとクロージャのdelegate

ちょっと工夫すればGroovyMarkupでclosureがつかえる。以前書いたもので確認。動きましたー。
ueharaさん、ありがとうございます。

import groovy.xml.MarkupBuilder

map = [1:"one",2:"two",3:"three"]
	   
builder = new MarkupBuilder(out)
builder.html() {
    head() {
        title("Groovlet and MarkupBuilder")
    }
    body() {
        p("Number Mapping Display")
        table(border:1) {
            map.each {e| 
                delegate = builder
                tr() {
                    td(e.key);td(e.value);
                }
            }
        }
    }
}

ついでにわかったこと

  • MarkupBuilderのソースコードで次のコメント発見。"FIXME: the current state model doesn't allow for nodes with attributes and values"。こういうことはできないということ("test"が無視される)
body(text:"blue", "test")


よし、Closureをちゃんと理解しようとほのかに思う。

Web層のモデル

Web層のモデルを何処でつくるかの指針をひがさんに答えてもらいました。答えてもらって気づいたけど自分の疑問はダイコン時代とは直接関係なかったかも…。

とある本では、StrutsのActionFormがドメインオブジェクトでないことを
批判してますが、間違っているのはきっとその本の著者です。

あーあの本だ、読んだことある。
これに関連して、Springのサンプルの「jpetstore」はWeb層のモデルとエンティティがほぼ一緒になるようなサンプルだからちょっとずるい?と思った記憶がある。

ひがさんのいうようにプレゼンテーションフレームワークを使ったほうがいいのだろうなーと思いつつ、Groovyでプレゼンテーション層を作成した場合にどこが便利でどこが不便かに興味がある。簡単なアプリケーションは動かせたけど、Validationどうしようとか、Closure使ってなんかおもしろいことできないかなとか悩み中。
Groovyでコードアシストつかえるようになったらいいなぁと思うけど型が静的でない時点で無理なのか?

プレゼンテーション層にGroovy

こんなことしてみました。

  • StrutsでいうところのAction(LookupDispatchAction)部分をGroovyで作成。
  • 上記のGroovyでつくったClassにService層のクラスをdiconのouter&autoBindingでセッター・インジェクト。
    • Groovyなのでsetterメソッドを省略できるので楽チン、見やすい。ただautoBindig使用なのでpropertyの型は必ずインタフェースで。
    • componentをclass指定せずnameで管理すればinstanceのタイプをouterにしたものをひとつつくるだけ。
  • 特定の変数をgroovy.lang.Bindingクラスにくっつけて宣言せず参照できるようにする。requestやsessionに加えてmessages(複数メッセージを格納するクラス)などをGroovyServletの継承クラスで指定。diconでinstance="prototype"としてService層と一緒にinjectしてもいいかも。
  • GroovyServletで扱われる場合、Groovyスクリプトはgroovy.lang.Scriptクラスとして扱われrunメソッドが呼びだされる。これを親クラスでインターセプトし、diconをつかって依存性を注入してパラメターで指定されたメソッドを呼び出すようにする。Groovyスクリプトの戻り値でpathをもってきてTemplateServletの継承クラスにDispatchされるようにする。


app.dicon:"operation"というnameでinstance属性がouterのものを用意しておく


    
    
    
    

authentication.dicon:永続化層とサービス層のコンポーネント。SimpleAuthenticationはインタフェースの実装。


    
    
        traceInterceptor
    

Operation.java:groovyスクリプトの親になる

package taedium.presentation;
//import宣言は省略

public abstract class Operation extends Script {
    
    protected final Log logger = LogFactory.getLog(this.getClass());
    
    protected Log getLogger() {
        return logger;
    }
    
    /**
     * @see groovy.lang.Script#run()
     */
    public Object run() {
        // "operation"を使って依存性注入
        S2Container container = SingletonS2ContainerFactory.getContainer();
        container.injectDependency(this, "operation");
        
        // もともとrequestパラメータだがBindingオブジェクトにも格納されている
        // 親クラスのメソッドで取得
        String method = (String)getProperty("method");
        
        // パラメータ指定された任意のメソッドを呼び出す。
        String viewname = (String)invokeMethod(method, null);
        
        dispatch(viewname != null? viewname : "error");
        return null;
    }
    
    
    private void dispatch(String viewname) {        
        ResourceBundle bundle = ResourceBundle.getBundle("view");
        String path = bundle.getString(viewname);
        
        HttpServletRequest request = (HttpServletRequest)getProperty("request");
        HttpServletResponse response = (HttpServletResponse)getProperty("response");
        try {
            request.getRequestDispatcher(path).forward(request, response);
        } catch(Exception e) {
            throw new RuntimeException(e);
        }    
    }
}

login.groovyStrutsのAction部分に相当するgroovyスクリプト。Operationクラスを継承する。

package taedium.presentation;
import taedium.service.*

class Login extends Operation {
    
    // diconによって注入される。型はインタフェース
    AuthenticationService auth  
    
    
    // login()はOperation#run()から呼び出される。
    // name, password, session, messagesはBindingされている。
    login() {
        
        success = auth.login(name, password)
        
        if (success) {
            user = auth.getLoginUser(name, password)
            session.setAttribute("user", user);    
            return "menu"
        } else {
            messages.add("名前またはパスワードが不正です") 
            return "index"
        }
    }
}

ServletとgroovyのHTMLをつくるtemplateは省略…


感想などをおもいつくままに

  • groovyスクリプトにpackage宣言すればそのpackageのJavaクラスはimportする必要なくなる。そうなのかー。
  • outerやautoBindingをはじめて知ったときはどういうときに使えばいいのかわからなかったけどわかってきたかな。
  • setterを書かずに依存注入できてしまうのがすっきりしてうれしい。
  • sciptが継承をしないといけないのはかっこ悪いかな?
  • groovy.text.TemplateEngineの可能性を探ってみたい。
  • groovyがgroovyぽいのはgroovy.lang.MetaClassが暗躍しているからか?
  • しかしDEBUGが大変だった。今回はとにかく常にコンテナで動かしたけどTestのしやすさはJavaと比べてどうなのかなーと思う。まずJavaのTestの仕方がなってないのでJUnit in Actionを読もう。