Threadへの情報のバインド
keywords : tomcat, javax.servlet.Filter, java.lang.InheritableThreadLocal
現在作成中のアプリに、監査ログの出力と閲覧という要件があります。
通常のシステム的なログ以外に、監査用のユーザーオペレーションログを出力する必要があるそうな。ログはファイルで日付でロールしてほしいとの注文付き。
で、少し考えた。
- ログはlog4jのDailyRollingFileAppenderを使えばOK。
- ログフォーマットの仕様はというと、ユーザー情報が必要らしい。JSF(JSP)やJSFのManagedBean内ならExternalContextからユーザー名を取ってこれるけど、domainパッケージ内のログ出力ではNG(domainパッケージはcoreパッケージにdependしない作りにしている)。
- 内部の各メソッドがユーザー名のバケツリレーをするというのはよろしくない。
ということで、ユーザー名の取得をServletコンテナのFilter経由で行い、ユーザー名の保持をInheritableThreadLocalで行うことにしました。
Domain Driven Design 的には、applicationレイヤのお仕事になるのかな?
Thread毎にUser名を保持するためのクラス。
ThreadLocalでなくてInheritableThreadLocalを使うのは、ユーザー名を持ったスレッドの子孫スレッドに情報を継承させるため。
public class ThreadInfo { /** Thread固有情報を保持するためのInheritableThreadLocal定義. */ private static ThreadLocal threadUser = new InheritableThreadLocal() { protected Object initialValue() { return new HashMap(); } }; /** * Threadもしくは先祖スレッドに紐づいたユーザー名を設定します. * @param user Threadもしくは先祖スレッドに紐づいたユーザー名 */ public static void setUser(String user) { getMap().put("user", user); } /** * Threadもしくは先祖スレッドに紐づいたユーザー名を取得します. * @return Threadに紐づいたユーザー名 */ public static String getUser() { return (String)getMap().get("user"); } /** * Thread固有情報用Mapの取得. * @return Thread固有情報用Map */ private static Map getMap() { return (Map)threadUser.get(); } }
関係ないけど、JDK1.5からjava.lang.management.ThreadInfoなんてクラスもあるんですね。
ユーザー名をThreadInfoに登録するフィルタ
public class BindUserToLocalThreadFilter implements Filter { /** * empty impl. * @see javax.servlet.Filter#destroy() */ public void destroy() { } /** * ユーザー名をThreadInfoに登録するフィルタ. * @see ThreadInfo * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, * javax.servlet.ServletResponse, javax.servlet.FilterChain) */ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; String user = request.getRemoteUser(); ThreadInfo.setUser(user); chain.doFilter(req, res); } /** * empty impl. * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) */ public void init(FilterConfig arg0) throws ServletException { } }
web.xml
<!-- フィルタ設定(BindUserToLocalThreadFilter Filter) --> <filter> <filter-name>bindUserToLocalThreadFilter</filter-name> <filter-class>com.hoge.filters.BindUserToLocalThreadFilter</filter-class> </filter> <!-- すべてのファイルはserviceTimeFilterを経由するように設定 --> <filter-mapping> <filter-name>bindUserToLocalThreadFilter</filter-name> <url-pattern>*.jsf</url-pattern> </filter-mapping>
ユーザー名が必要な箇所
String user = ThreadInfo.getUser();