16
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

TomcatでGoogleGuiceを使う

Posted at

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://localhost:8080/guice-web/hoge.htmlにアクセスしたときの標準出力
Hoge Serlvet
http://localhost:8080/guice-web/fuga.htmlにアクセスしたときの標準出力
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

#参考

16
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?