JavaでさくさくWebアプリ開発

かなり久々の技術エントリ。

運用はお堅い重いサーバーを使ったとしても開発は軽いほうがいい。当たり前ですね。

というわけでさくさく開発する方法を書いてみる。DIコンテナはCDIGuice、Springなど好きなものでよいが、今回は省く。軽いこともあって開発中はGuiceを使うことをお勧めしたい。注入は@Injectを使うため、開発中と運用中でコードが変わるってのは少ないはずだ。

まずはJAX-RS

まず、アクションベースのWebアプリはJAX-RSを使うこと。これが基本。サーブレットAPIを使わずに開発することについては今までも書いてきた。サーブレットAPIを触らないことにより開発効率とテストのしやすさを両立できる。

こんな感じ。

@Path("/")
public class Hoge {
    @GET
    @Path("add/{a}/{b}")
    public Response add(@PathParam("a")int a, @PathParam("b")int b){
        String text = a + "+" + b + "=" + (a+b);
        return Response.ok(text , "text/plain").build();
    }
}

okはわざわざこんなことやらなくてもいいけど、JavaJAX-RSよくわからん人でもなんとなく動きはわかるようにしてみた。これがJAX-RS
書き方は結構自由なので、同じ動きをいろんなかきかたができる。いろんなかきかたができるといってもわかりにくいということではなく、柔軟にstatusコードとかmimeとか数値や文字列だったり定数クラスだったり指定することができる。

        return Response.ok(entity , "text/plain").build();
        return Response.ok().entity(text).type("text/plain").build();
        return Response.ok(text).type("text/plain").build();
        return Response.status(200).entity(text).type("text/plain").build();
        return Response.status(Response.Status.OK).entity(text).type(MediaType.TEXT_PLAIN_TYPE).build();

もちろんOK以外もいくつかメソッドで用意されている。こんな感じ。

        return Response.seeOther(new URI("http://example.com")).build();
        return Response.serverError().entity("ごめんちゃい").build();

すでに枯れた標準APIなのでJava使いなら大抵誰でも知ってるはず。もうすぐ次のバージョンが出るけど、互換性があって基本部分に変更はないので大丈夫。あくまでもさらに便利になるフィルタやクライアント(今までは実装依存だった)の標準化をしたに過ぎない。

POJOなのでこういうテストを書いてもいい。

    @Test
    public void testAdd() {
        System.out.println("add");
        int a = 12;
        int b = 3;
        Hoge instance = new Hoge();
        Response result = instance.add(a, b);
        
        assertEquals(200, result.getStatus());
        assertEquals("12+3=15", result.getEntity().toString());
    }

そのほか実際にスタンドアロンサーバーで通信するテストを書いてもいい。サーブレットコンテナはいらないのでJavaSEで開発ができる。さくさくである。この場合テンプレートにJSPは利用できないので他のテンプレートエンジンを利用することになる。基本クライアントサイドで組み立てたほうがいいと思うけどね。サーバーとはデータのみやり取りするように。

jax-rsのライブラリはRIのjerseyから落としてくるとよい。
https://jersey.java.net/nonav/documentation/latest/chapter_deps.html

  • jersey-bundle.jar
  • asm.jar

このあたりを。

それでもサーブレットコンテナがほしい

それでも特定のサーブレットJSPを利用したいこともあるかもしれない。自分のアプリの都合ではなく、ライブラリなどの都合でフィルターが使いたいとかで。完全新規ならフィルターはJerseyのフィルタを使えばいいがそうではない場合もあるだろう。そうなるとTomcatなどが必要になってきて開発はどんどん重くなる。

軽いコンテナは?と聞いたらすぐに帰ってくるのがJetty。

そう、Jettyのembeddedを利用する。

jetty-all-[version].jarというのをクラスパスに追加する。servlet-api-3.0.jarもいれておく。

jetty-allはこのへんから。
http://mvnrepository.com/artifact/org.eclipse.jetty.aggregate

servlet-api-3.0.jarはjetty-distribution-[version].zipとかをjettyの公式サイトのトップから落としてそこから拾うとよい。

public class ServerMain {

    public static void main(String[] args) throws Exception {
        
        //サーバー起動
        Server server = new Server(8080);
        server.start();
    }
}

これで起動する。サーバーが起動しているかどうかブラウザで404がかえるのを確認する。

JerseyServletを設定する

リソースを列挙するクラスを用意。コンストラクタで列挙できるようにしてある。

public class HogeApplication extends Application{
    final Set<Class<?>> classes = new HashSet<>();

    public HogeApplication(Class<?>... classArray) {
        classes.addAll(Arrays.asList(classArray));
    }
    
    @Override
    public Set<Class<?>> getClasses() {
        return classes;
    }

}

クラスパスとかをスキャンしたりして自動登録してもいい。

    public static void main(String[] args) throws Exception {
        ServletContextHandler ctx = new ServletContextHandler();
        ctx.setContextPath("/test");

        //Jersey設定
        //リソースクラスの一覧を列挙
        HogeApplication app = new HogeApplication(Hoge.class);
        ServletContainer sc = new ServletContainer(app);
        ctx.addServlet(new ServletHolder(sc), "/res/*");
        
        //サーバー起動
        Server server = new Server(8080);
        server.setHandler(ctx);
        server.start();
    }

これで以下のURLで動くようになった。

http://localhost:8080/test/res/add/12/3

でもこれではスタンドアロンサーバーの状態のままに近い。一応サーブレットとして動いているので好きなようにフィルタ付けたりいろいろとできることはできるけど、JSPとかは動かない。

JSPとか動かす

ライブラリが足りないのでJSPを動かすのに必要なライブラリをクラスパスに通す。
jetty-distribution-[version]からlib/jsp以下のjarをもってくる。

mainクラスは以下のように修正。

    public static void main(String[] args) throws Exception {
        ServletContextHandler ctx = new ServletContextHandler(ServletContextHandler.SESSIONS);
        ctx.setClassLoader(Thread.currentThread().getContextClassLoader());
        ctx.setSessionHandler(new SessionHandler());
        ctx.setContextPath("/test");

        //Jersey設定
        //リソースクラスの一覧を列挙
        HogeApplication app = new HogeApplication(Hoge.class);
        ServletContainer sc = new ServletContainer(app);
        ctx.addServlet(new ServletHolder(sc), "/res/*");
        
        //JSP設定
        ctx.setResourceBase("webapp");
        ctx.addServlet(DefaultServlet.class, "/");
        ServletHolder jsp = ctx.addServlet(JspServlet.class, "*.jsp");
        jsp.setInitParameter("classpath", ctx.getClassPath());
        
        //サーバー起動
        Server server = new Server(8080);
        server.setHandler(ctx);
        server.start();

これで準備完了。

カレントディレクトリにwebappというディレクトリを作ってJSPを置いて動作確認しよう。

一瞬でサーバーが起動して動作が確認できるだろう。

JSPが動くということはViewableが動くということ。ViewableはJSPフォワードしてくれる仕組み。

先ほどのリソースクラスに追加してみる。

@Path("/")
public class Hoge {
    @GET
    @Path("add/{a}/{b}")
    public Response add(@PathParam("a")int a, @PathParam("b")int b){
        String text = a + "+" + b + "=" + (a+b);
        return Response.ok(text , "text/plain").build();
    }
    //---------------------------------------------------------------
    //ここから下が追加分
    @GET
    public Viewable get(@QueryParam("a")int a, @QueryParam("b")int b ){
        HashMap<String, Object> params = new HashMap<>();
        params.put("a", a);
        params.put("b", b);
        params.put("c", a + b);
        return new Viewable("/hoge.jsp", params);
    }
}

JSPはitという名前でアクセスすることができる。たとえばこんな感じ。

<%@page pageEncoding="UTF-8" contentType="text/plain" %>
${it.a} + ${it.b} = ${it.c}

以下のURLでブラウザで確認するとよいだろう。

http://localhost:8080/test/res/?a=12&b=3


これで1秒で起動するJSP等も動くサーバーの出来上がり。



続きかいた