オレオレAjaxアノテーションで超簡単Ajax

チュートリアルのダウンロードのソース見てたら「あれ?これパクれば Ajax できるんじゃね?」と思ってさっそくやってみた。クラス名とか気にしないで・・・。

すぱいだー日記さんにインスピレーションを受けて
SAStrutsで自前AjaxアノテーションAOPを使って
超簡単にAjaxできてしまうプログラムを書いてみた。


Action側のコードはこんな感じ。

public class AjaxTestAction {
	
    public String hoge;

    public String fuga;
	
    @Ajax
    @Execute(validator = false)
    public String callAjax() {
        return hoge + " と " + fuga; 
    }	
}

通常のSAStrutsの実行メソッドに
自前で作った@Ajaxアノテーションを付けるだけ。


呼び出し側のJSPは以下のとおり。

<script src="../js/prototype.js"></script>
<script>
function ajaxTest() {
  var url = '/sa-struts-tutorial/ajaxTest/callAjax';
  var queryString = 'hoge=ほげ&fuga=ふが';
  var myAjax = new Ajax.Request(
        url, 
        {
           method: 'post', 
           parameters: queryString, 
           onComplete: showResponse
  	}
  );
}

function showResponse(request) {
  alert(request.responseText);
}
</script>

<input type="button" onclick="ajaxTest()">

振る舞いとしては、ボタンが押されたらAjaxTestActionクラスに
hogeパラメータとfugaパラメータを渡してcallAjaxメソッドを呼び出し、
戻り値をJavaScriptのalert関数で表示する感じ。


hogeパラメータに「ほげ」、fugaパラメータに「ふが」という値を
セットしたクエリーストリング(queryString )を作成して
Ajax.Requestに渡している。


呼び出すURLは'/sa-struts-tutorial/ajaxTest/callAjax'なので、
sa-struts-tutorialアプリのAjaxTestActionクラスのcallAjaxメソッドとなる。

オレオレAjaxの仕込み方

どのような仕込みをしたのかについて説明する。


オリジナルで作ったAjaxInterceptorは@Ajaxアノテーションが実行メソッドにあれば、
String型の戻り値が画面遷移先ではなく、レスポンスとして扱うようになる仕組み。


以下、順を追って説明。

 Step1. オリジナルのAjaxアノテーションを作成する
 Step2. インターセプターを作成する
 Step3. diconファイルにインターセプターの設定を施す

Step1. オリジナルのAjaxアノテーションを作成する

Ajax.java

package tutorial.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Ajax {
    String encode () default "UTF-8";

    String contentType() default "text/plain";
}

@Ajaxアノテーション
文字エンコードをセットできるようにしています。
デフォルトはUTF-8

Step2. インターセプターを作成する

AjaxInterceptor.java

package tutorial.interceptor;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletResponse;

import org.aopalliance.intercept.MethodInvocation;
import org.seasar.framework.aop.interceptors.AbstractInterceptor;
import org.seasar.framework.container.SingletonS2Container;

import tutorial.annotation.Ajax;

public class AjaxInterceptor extends AbstractInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable {

        Ajax ajax = invocation.getMethod().getAnnotation(Ajax.class);

        if (ajax == null) {
            return invocation.proceed();
        }

        Object ret = invocation.proceed();
        printOut(ret.toString(), ajax.contentType(), ajax.encode());

        return null;
    }

    protected void printOut(String value, String contentType, String encode) {
        HttpServletResponse response =
            SingletonS2Container.getComponent(HttpServletResponse.class);
        response.setContentType(contentType + "; charset=" + encode);

        PrintWriter out = null;
        try {
            out =
                new PrintWriter(new OutputStreamWriter(response.getOutputStream(), encode));
            out.print(value);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }
}

SMARTデプロイの規約によりルートパッケージ直下のinterceptorパッケージにInterceptorという名前で終わるクラス名にしておけば、このクラスは自動的にS2コンテナに登録される。SMARTデプロイの規約により"ajaxInterceptor"というコンポーネント名でS2コンテナから取り出すことができる。


AjaxInterceptorは呼び出されたメソッドに@Ajaxアノテーションがあるかどうかをチェックして、あればメソッドの戻り値を画面遷移としでではなく、レスポンスの値として処理する。

Step3. diconファイルにインターセプターの設定を施す

customizer.dicon

…中略…

    <component name="actionCustomizer"
        class="org.seasar.framework.container.customizer.CustomizerChain">
        <initMethod name="addAspectCustomizer">
            <arg>"aop.traceInterceptor"</arg>
        </initMethod>
        <initMethod name="addAspectCustomizer">
            <arg>"actionMessagesThrowsInterceptor"</arg>
        </initMethod>
        <initMethod name="addCustomizer">
            <arg>
                <component
                    class="org.seasar.framework.container.customizer.TxAttributeCustomizer"/>
            </arg>
        </initMethod>
        <initMethod name="addCustomizer">
            <arg>
                <component
                    class="org.seasar.struts.customizer.ActionCustomizer"/>
            </arg>
        </initMethod>
        <!-- 追加したAjaxInterceptor用の設定はここから -->
        <initMethod name="addAspectCustomizer">
            <arg>"ajaxInterceptor"</arg>
        </initMethod>
        <!-- 追加したAjaxInterceptor用の設定はここまで -->        
    </component>

…中略…

actionCustomizerに作成したAjaxInterceptorを関連付ける。こうすることでActionクラスの全publicメソッドが呼ばれるたびに、AjaxInterceptorが呼ばれることになる。


以上。