Servlet 3.0の新機能 非同期処理を試す

サーブレット 3.0の新機能は過去にもいくつか書いていたが、気がついたらJSF中心になっていったので久々に。web-fragmentがうまく動かなかったからネタを変えたのは秘密。

いわゆるコメット、ロングポーリングを実現するものであります。


今回のサンプルはユーザーがアクセスするとつかみっぱなしになって、もう一人がアクセスしたときに先につかみっぱなしになっている人が解放されて武座右座に文字が表示されるというもの。武多雨座2つ開いて交互にアクセスして動作を試すということが必要。

アノテーションマッピングしてるのでコードはサーブレットひとつのみ。web.xmlはいらない。

package servlet;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@WebServlet(name="NewServlet", 
        urlPatterns={"/NewServlet"},
        asyncSupported=true)
public class NewServlet extends HttpServlet {
   
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        int current;
        synchronized(NewServlet.class){
            current = ++count;
        }
        System.out.println("サーブレット開始:" + current);
        AsyncContext ac = request.startAsync();
        ac.start(new Async(ac,current));
        System.out.println("サーブレット終了:" + current);
    } 


    static final Object lock = new Object();
    static int count = 0;

    public static class Async implements Runnable {
        AsyncContext ac;
        int current;

        public Async(AsyncContext ac,int current) {
            this.ac = ac;
            this.current = current;
        }

        @Override
        public void run() {
            System.out.println("非同期開始:" + current);
            
            ac.getResponse().setCharacterEncoding("UTF-8");
            ac.getResponse().setContentType("text/plain");
            try {
                PrintWriter writer = ac.getResponse().getWriter();
                try{
                    synchronized(lock){
                        lock.notify();
                        lock.wait();
                    }
                    writer.println("Async OK?");
                }finally{

                    writer.close();
                    ac.complete();
                    System.out.println("非同期終了:" + current);

                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

    }

}

先に言っておくとスプリアスウェイクアップ対応してないので注意されたし。あくまでも概念をわかりやすく書いただけと思ってください。タイムアウトしたりブラウザで停止おしたときの対応はまともにやっていません。

まず「@WebServlet」のアノテーションで「asyncSupported=true」という属性をセットしておく。コレを入れないとエラーになる。したがって通常の同期式なのかどうかはアノテーションを見ればすぐにわかる。

AsyncContext ac = request.startAsync();

この1行で取得するAsyncContextがポイント。こいつはrequestとresponseを保持している。そして非同期に実行されるRunnableの中でresponse等を取り出して出力できるというわけだ。

ac.start(new Async(ac,current));

startメソッドで非同期に実行する。引数はRunnnableを実装したオブジェクト。


実行してブラウザ2つ用意して交互に開くと以下のようなログが表示される。

情報: サーブレット開始:1
情報: サーブレット終了:1
情報: 非同期開始:1
情報: サーブレット開始:2
情報: サーブレット終了:2
情報: 非同期開始:2
情報: 非同期終了:1
情報: サーブレット開始:3
情報: サーブレット終了:3
情報: 非同期開始:3
情報: 非同期終了:2

非同期処理が開始する前にサーブレットが終了しているのがわかる。

スレッドローカルを利用したライブラリは動作を把握しておく必要があるかと。