TomcatでGoogleGuiceを使う

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

#環境
##Tomcat
7.0.50

##Google Guice
3.0

##Java
1.7.0_51

#Hello World
##依存 jar の追加
`guice-3.0.jar` と `guice-servlet-3.0.jar` をクラスパスに追加する。

build.gradle は以下。

```groovy:
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 にフィルターを追加する
```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>

```

##サーブレットクラスを作成する
```java: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 の設定とかをするためのリスナーを作成する
```java: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](https://qiita-image-store.s3.amazonaws.com/0/28302/80dea1d0-5751-c6d9-8a08-35f840722522.jpeg)

#Filter を登録する
```java: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() {}
}
```

```java: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 は実行しない
- マッチしなかったら、次のマッピングに進む

```java: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");
    }
}
```

```java: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");
    }
}
```

```java: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);
                    }
                });
    }
}
```

```text:http://localhost:8080/guice-web/hoge.htmlにアクセスしたときの標準出力
Hoge Serlvet
```

```text:http://localhost:8080/guice-web/fuga.htmlにアクセスしたときの標準出力
Fuga Servlet
```

#１つのサーブレットに複数の URL マッピングを割り当てる
```java: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...)` を使えば、１つのサーブレットに複数の URL パターンをマッピングできる。

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

```java: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.html` や `http://localhost:8080/guice-web/xyz/890/hoge.html` にアクセスすれば、 `HogeServlet` にディスパッチされる。

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

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

```text: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()` メソッドを呼んでオブジェクトを取得する。

```java:Hoge.java
package sample.guice.web;

import com.google.inject.servlet.RequestScoped;

@RequestScoped
public class Hoge {
}
```

```java: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 オブジェクトが取得できていることがわかる。

```text:
hashCode : 2075307770
hashCode : 439602598
hashCode : 1216745001
hashCode : 1094789367
```

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

```java: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();
    }
}
```

```java: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](https://qiita-image-store.s3.amazonaws.com/0/28302/ecfb5fee-6a84-9eff-d747-c3360ab02a57.jpeg)

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

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

```java: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);
    }
}
```

```java: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);
                    }
                });
    }
}
```

```text:サーバー初期化時の標準出力
hoge = HOGE
```




















#参考
- [Servlets - google-guice - Guice Servlet Extensions](http://code.google.com/p/google-guice/wiki/Servlets)
- [Google Guice | Packt Publishing](http://www.packtpub.com/article/google-guice)