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 は以下。
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 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>
##サーブレットクラスを作成する
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 の設定とかをするためのリスナーを作成する
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
にアクセス。
※ホスト名、ポート、コンテキストルート名は適宜読み替え。
#Filter を登録する
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() {}
}
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 は実行しない
- マッチしなかったら、次のマッピングに進む
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");
}
}
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");
}
}
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);
}
});
}
}
Hoge Serlvet
Fuga Servlet
#1つのサーブレットに複数の URL マッピングを割り当てる
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 マッピングを正規表現で指定できる。
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
を付与しているオブジェクトをインジェクションしようとすると、以下のようなスタックトレースが出力される。
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()
メソッドを呼んでオブジェクトを取得する。
package sample.guice.web;
import com.google.inject.servlet.RequestScoped;
@RequestScoped
public class Hoge {
}
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 などをインジェクションできる。
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();
}
}
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
にアクセスすると、以下のような画面が開き、各オブジェクトがインジェクションできていることがわかる。
#Servlet に初期化パラメータを渡す
web.xml
では Servlet や Filter に初期化パラメータを渡すため、 <init-param>
というタグが使用できた。
Guice で登録する場合は、 with()
メソッドの第2引数に Map<String, String>
のオブジェクトを渡すことで、初期化パラメータを渡すことができる。
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);
}
}
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
#参考