Help us understand the problem. What is going on with this article?

TomcatでGoogleGuiceを使う

More than 5 years have passed since last update.

Tomcat で Google Guice を使う方法のメモ。

環境

Tomcat

7.0.50

Google Guice

3.0

Java

1.7.0_51

Hello World

依存 jar の追加

guice-3.0.jarguice-servlet-3.0.jar をクラスパスに追加する。

build.gradle は以下。

dependencies {
    providedCompile 'org.apache.tomcat:tomcat-servlet-api:7.0.50'
    compile 'com.google.inject:guice:3.0'
    compile 'com.google.inject.extensions:guice-servlet:3.0'
}

web.xml にフィルターを追加する

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
         version="3.0">
  <display-name>Guice Web</display-name>

  <filter>
    <filter-name>Guice Filter</filter-name>
    <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>Guice Filter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

サーブレットクラスを作成する

HelloGuiceServlet.java
package sample.guice.web;

import java.io.IOException;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.inject.Singleton;

@Singleton
public class HelloGuiceServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
        res.getWriter().println("Hello Guice Servlet");
    }
}

サーブレットには @Singleton を付けておく必要がある。

DI の設定とかをするためのリスナーを作成する

GuiceServletConfig.java
package sample.guice.web;

import javax.servlet.annotation.WebListener;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.servlet.ServletModule;

@WebListener
public class GuiceServletConfig extends GuiceServletContextListener {

    @Override
    protected Injector getInjector() {
        return Guice.createInjector(
                new ServletModule() {
                    @Override protected void configureServlets() {
                        serve("/hello").with(HelloGuiceServlet.class);
                    }
                });
    }
}

動作確認

ブラウザで http://localhost:8080/guice-web/hello にアクセス。
※ホスト名、ポート、コンテキストルート名は適宜読み替え。

guice-web.jpg

Filter を登録する

MyFilter.java
package sample.guice.web;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import com.google.inject.Singleton;

@Singleton
public class MyFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("MyFilter#init()");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("MyFilter#doFilter()");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {}
}
GuiceServletConfig.java
package sample.guice.web;

import javax.servlet.annotation.WebListener;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.servlet.ServletModule;

@WebListener
public class GuiceServletConfig extends GuiceServletContextListener {

    @Override
    protected Injector getInjector() {
        return Guice.createInjector(
                new ServletModule() {
                    @Override protected void configureServlets() {
                        filter("/*").through(MyFilter.class);
                        serve("/hoge.html").with(HogeServlet.class);
                    }
                });
    }
}

Filter を登録するときは、 filter(<URL パターン>).through(<Filter クラス>); を使う。

Filter にも @Singleton アノテーションを付けておく必要がある。

ディスパッチの順序

リクエスト URL に対してどの Servlet (Filter)が実行されるかは、以下のルールで行われる。

  • ServletModule で登録した順番で URL がマッチするかチェックする
  • マッチしたらその Servlet を実行し、以後の Servlet は実行しない
  • マッチしなかったら、次のマッピングに進む
HogeServlet.java
package sample.guice.web;

import java.io.IOException;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.inject.Singleton;

@Singleton
public class HogeServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
        System.out.println("Hoge Servlet");
    }
}
FugaServlet.java
package sample.guice.web;

import java.io.IOException;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.inject.Singleton;

@Singleton
public class FugaServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
        System.out.println("Fuga Servlet");
    }
}
GuiceServletConfig.java
package sample.guice.web;

import javax.servlet.annotation.WebListener;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.servlet.ServletModule;

@WebListener
public class GuiceServletConfig extends GuiceServletContextListener {

    @Override
    protected Injector getInjector() {
        return Guice.createInjector(
                new ServletModule() {
                    @Override protected void configureServlets() {
                        serve("/hoge.html").with(HogeServlet.class);
                        serve("*.html").with(FugaServlet.class);
                    }
                });
    }
}
http
Hoge Serlvet
http
Fuga Servlet

1つのサーブレットに複数の URL マッピングを割り当てる

GuiceServletConfig.java
package sample.guice.web;

import javax.servlet.annotation.WebListener;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.servlet.ServletModule;

@WebListener
public class GuiceServletConfig extends GuiceServletContextListener {

    @Override
    protected Injector getInjector() {
        return Guice.createInjector(
                new ServletModule() {
                    @Override protected void configureServlets() {
                        serve("/hoge.html", "*.hoge").with(HogeServlet.class);
                    }
                });
    }
}

serve(String...) を使えば、1つのサーブレットに複数の URL パターンをマッピングできる。

URL マッピングに正規表現を使用する

serveRegex() を使えば、 URL マッピングを正規表現で指定できる。

GuiceServletConfig.java
package sample.guice.web;

import javax.servlet.annotation.WebListener;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.servlet.ServletModule;

@WebListener
public class GuiceServletConfig extends GuiceServletContextListener {

    @Override
    protected Injector getInjector() {
        return Guice.createInjector(
                new ServletModule() {
                    @Override protected void configureServlets() {
                        serveRegex("/[a-z]+/[0-9]+/hoge.html").with(HogeServlet.class);
                    }
                });
    }
}

serveRegex() に指定した正規表現が、コンテキストパス以下のパスに完全に一致した場合、指定したサーブレットがディスパッチされる。

つまり、上記例の場合は、 http://localhost:8080/guice-web/abc/123/hoge.htmlhttp://localhost:8080/guice-web/xyz/890/hoge.html にアクセスすれば、 HogeServlet にディスパッチされる。

サーブレットにリクエストスコープのオブジェクトをインジェクションする

大きいスコープを持っているオブジェクトに小さいスコープのオブジェクトをインジェクションしようとすると、 com.google.inject.OutOfScopeException が発生する。

例えば、 @Singleton を付与しているサーブレットに @RequestScoped を付与しているオブジェクトをインジェクションしようとすると、以下のようなスタックトレースが出力される。

OutOfScopeExceptionのスタックトレース
Caused by: com.google.inject.OutOfScopeException: Cannot access scoped object. Either we are not currently inside an HTTP Servlet request, or you may have forgotten to apply com.google.inject.servlet.GuiceFilter as a servlet filter for this request.
    at com.google.inject.servlet.GuiceFilter.getContext(GuiceFilter.java:135)
    at com.google.inject.servlet.GuiceFilter.getRequest(GuiceFilter.java:121)
    at com.google.inject.servlet.ServletScopes$1$1.get(ServletScopes.java:78)
    at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:40)
(以下略)

この場合は、 Provider をインジェクションして、そこから get() メソッドを呼んでオブジェクトを取得する。

Hoge.java
package sample.guice.web;

import com.google.inject.servlet.RequestScoped;

@RequestScoped
public class Hoge {
}
HogeServlet.java
package sample.guice.web;

import java.io.IOException;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;

@Singleton
public class HogeServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Inject
    private Provider<Hoge> provider;

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
        System.out.println("hashCode : " + this.provider.get().hashCode());
    }
}

http://localhost:8080/guice-web/hoge.html に複数回アクセスすると、以下のように標準出力に出力され、リクエストごとに異なる Hoge オブジェクトが取得できていることがわかる。

hashCode : 2075307770
hashCode : 439602598
hashCode : 1216745001
hashCode : 1094789367

Guice 管理のオブジェクトに HttpServletRequest とかをインジェクションする

Guice に管理されているオブジェクトには、 HttpServletRequest や HttpSession などをインジェクションできる。

Hoge.java
package sample.guice.web;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.google.inject.Inject;
import com.google.inject.servlet.RequestParameters;
import com.google.inject.servlet.RequestScoped;

@RequestScoped
public class Hoge {
    @Inject
    private ServletContext context;
    @Inject
    private HttpSession session;
    @Inject
    private HttpServletRequest request;
    @Inject
    private HttpServletResponse response;
    @Inject @RequestParameters
    private Map<String, String[]> parameterMap;

    public void execute() throws IOException {
        PrintWriter pw = this.response.getWriter();

        pw.println("context.getServerInfo() = " + this.context.getServerInfo());
        pw.println("session.getId() = " + this.session.getId());
        pw.println("request.getRequestURI() = " + this.request.getRequestURI());
        pw.println("[parameterMap]");
        for (Entry<String, String[]> entrySet : this.parameterMap.entrySet()) {
            pw.println(entrySet.getKey() + " : " + Arrays.toString(entrySet.getValue()));
        }

        pw.close();
    }
}
HogeServlet.java
package sample.guice.web;

import java.io.IOException;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;

@Singleton
public class HogeServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Inject
    private Provider<Hoge> provider;

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
        this.provider.get().execute();
    }
}

http://localhost:8080/guice-web/hoge.html?hoge=HOGE&fuga=FUGA&fuga=Fuga にアクセスすると、以下のような画面が開き、各オブジェクトがインジェクションできていることがわかる。

guice-web-inject-request.jpg

Servlet に初期化パラメータを渡す

web.xml では Servlet や Filter に初期化パラメータを渡すため、 <init-param> というタグが使用できた。

Guice で登録する場合は、 with() メソッドの第2引数に Map<String, String> のオブジェクトを渡すことで、初期化パラメータを渡すことができる。

HogeServlet.java
package sample.guice.web;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServlet;

import com.google.inject.Singleton;

@Singleton
public class HogeServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    public void init() {
        ServletConfig config = this.getServletConfig();
        String hoge = config.getInitParameter("hoge");
        System.out.println("hoge = " + hoge);
    }
}
GuiceServletConfig.java
package sample.guice.web;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.annotation.WebListener;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.servlet.ServletModule;

@WebListener
public class GuiceServletConfig extends GuiceServletContextListener {

    @Override
    protected Injector getInjector() {
        return Guice.createInjector(
                new ServletModule() {
                    @Override protected void configureServlets() {
                        Map<String, String> initParam = new HashMap<>();
                        initParam.put("hoge", "HOGE");

                        serve("/hoge.html").with(HogeServlet.class, initParam);
                    }
                });
    }
}
サーバー初期化時の標準出力
hoge = HOGE

参考

opengl-8080
ただのSE。Java好き。
tis
創業40年超のSIerです。
https://www.tis.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away