
JSP MVCモデルを使った模擬アプリ課題
以下に、Servlet、JSP、JDBC、フロントコントローラーパターンを活用した3つの課題を提案します。 課題1: 簡易タスク管理アプリケーション 要件 模範 […]
Webアプリケーション開発において、フィルタ(Filter)とリスナ(Listener)は非常に強力な機能です。これらを適切に使用することで、アプリケーション全体にわたる共通処理を効率的に実装したり、アプリケーションのライフサイクルイベントを捕捉したりできます。本記事では、Servlet APIの一部として提供されるこれらの機能について、実践的な例を交えながら詳しく解説します。
フィルタは、クライアントからのリクエストがServletに到達する前、およびServletからのレスポンスがクライアントに返される前に処理を挟み込むことができるコンポーネントです。以下のような特徴があります:
@WebFilter("/*") // すべてのリクエストに適用
public class BasicFilter implements Filter {
private FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
System.out.println("BasicFilterが初期化されました");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("BasicFilter: 前処理");
// リクエストの前処理
long startTime = System.currentTimeMillis();
// 次のフィルタまたはServletへ処理を渡す
chain.doFilter(request, response);
// レスポンスの後処理
long endTime = System.currentTimeMillis();
System.out.println("リクエスト処理時間: " + (endTime - startTime) + "ms");
System.out.println("BasicFilter: 後処理");
}
@Override
public void destroy() {
System.out.println("BasicFilterが破棄されました");
}
}
複数のフィルタが登録されている場合、以下の順序で処理が行われます:
クライアント → Filter1 → Filter2 → ... → FilterN → Servlet → FilterN → ... → Filter2 → Filter1 → クライアント
@WebFilter("/secure/*")
public class AuthenticationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession(false);
// セッションにユーザー情報がない場合はログインページへリダイレクト
if (session == null || session.getAttribute("user") == null) {
httpResponse.sendRedirect(httpRequest.getContextPath() + "/login");
return;
}
// 認証済みの場合は次の処理へ
chain.doFilter(request, response);
}
// initとdestroyは省略(必要に応じて実装)
}
@WebFilter("/*")
public class LoggingFilter implements Filter {
private static final Logger logger = Logger.getLogger(LoggingFilter.class.getName());
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// リクエスト情報をログに記録
String requestInfo = String.format(
"RemoteAddr=%s, Method=%s, URL=%s, Parameters=%s",
httpRequest.getRemoteAddr(),
httpRequest.getMethod(),
httpRequest.getRequestURL(),
httpRequest.getParameterMap()
);
logger.info("Request: " + requestInfo);
// 処理時間の計測
long startTime = System.currentTimeMillis();
try {
chain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - startTime;
logger.info("Request processed in " + duration + "ms");
}
}
}
@WebFilter("/*")
public class EncodingFilter implements Filter {
private String encoding;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.encoding = filterConfig.getInitParameter("encoding");
if (this.encoding == null) {
this.encoding = "UTF-8";
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// リクエストとレスポンスのエンコーディングを設定
request.setCharacterEncoding(encoding);
response.setCharacterEncoding(encoding);
chain.doFilter(request, response);
}
}
リスナは、Webアプリケーションのライフサイクルイベントや、セッション・リクエストの状態変化を監視するためのコンポーネントです。以下のような特徴があります:
@WebListener
public class AppContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("アプリケーションが起動しました");
// データベース接続プールの初期化など
ServletContext ctx = sce.getServletContext();
String jdbcUrl = ctx.getInitParameter("jdbcUrl");
try {
ConnectionPool pool = new ConnectionPool(jdbcUrl);
ctx.setAttribute("connectionPool", pool);
System.out.println("データベース接続プールが初期化されました");
} catch (SQLException e) {
System.err.println("データベース接続プールの初期化に失敗しました");
e.printStackTrace();
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("アプリケーションが停止します");
// リソースの解放
ServletContext ctx = sce.getServletContext();
ConnectionPool pool = (ConnectionPool) ctx.getAttribute("connectionPool");
if (pool != null) {
pool.closeAllConnections();
System.out.println("データベース接続プールが解放されました");
}
}
}
@WebListener
public class SessionCounterListener implements HttpSessionListener {
private static final AtomicInteger activeSessions = new AtomicInteger();
public static int getActiveSessions() {
return activeSessions.get();
}
@Override
public void sessionCreated(HttpSessionEvent se) {
activeSessions.incrementAndGet();
System.out.println("セッションが作成されました。現在のアクティブセッション数: " + activeSessions.get());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
activeSessions.decrementAndGet();
System.out.println("セッションが破棄されました。現在のアクティブセッション数: " + activeSessions.get());
}
}
@WebListener
public class RequestStatsListener implements ServletRequestListener {
private static final AtomicLong totalRequests = new AtomicLong();
private static final AtomicLong totalProcessingTime = new AtomicLong();
@Override
public void requestInitialized(ServletRequestEvent sre) {
sre.getServletRequest().setAttribute("startTime", System.currentTimeMillis());
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
Long startTime = (Long) sre.getServletRequest().getAttribute("startTime");
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
totalRequests.incrementAndGet();
totalProcessingTime.addAndGet(duration);
System.out.printf("リクエスト処理完了: 処理時間=%dms, 平均処理時間=%.2fms%n",
duration,
(double) totalProcessingTime.get() / totalRequests.get());
}
}
public static long getTotalRequests() {
return totalRequests.get();
}
public static double getAverageProcessingTime() {
return totalRequests.get() > 0
? (double) totalProcessingTime.get() / totalRequests.get()
: 0;
}
}
@WebListener
public class PerformanceMonitor implements ServletContextListener,
ServletRequestListener, HttpSessionListener {
private static final Logger logger = Logger.getLogger(PerformanceMonitor.class.getName());
private ScheduledExecutorService scheduler;
@Override
public void contextInitialized(ServletContextEvent sce) {
scheduler = Executors.newScheduledThreadPool(1);
// 5分ごとにパフォーマンス統計をログに出力
scheduler.scheduleAtFixedRate(() -> {
ServletContext ctx = sce.getServletContext();
long totalMemory = Runtime.getRuntime().totalMemory();
long freeMemory = Runtime.getRuntime().freeMemory();
long usedMemory = totalMemory - freeMemory;
int activeSessions = SessionCounterListener.getActiveSessions();
long totalRequests = RequestStatsListener.getTotalRequests();
double avgProcessingTime = RequestStatsListener.getAverageProcessingTime();
String stats = String.format(
"Performance Stats: Memory Used=%dMB, Active Sessions=%d, " +
"Total Requests=%d, Avg Processing Time=%.2fms",
usedMemory / (1024 * 1024),
activeSessions,
totalRequests,
avgProcessingTime
);
logger.info(stats);
ctx.log(stats);
}, 0, 5, TimeUnit.MINUTES);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
if (scheduler != null) {
scheduler.shutdownNow();
}
}
// その他のリスナメソッドは省略...
}
@WebFilter("/*")
public class TenantIdentificationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// テナント識別子を取得(サブドメイン、ヘッダー、パスなどから)
String tenantId = identifyTenant(httpRequest);
// テナントごとの設定を適用
applyTenantSettings(tenantId, httpRequest);
// リクエスト属性にテナントIDを設定
httpRequest.setAttribute("tenantId", tenantId);
chain.doFilter(request, response);
}
private String identifyTenant(HttpServletRequest request) {
// サブドメインからテナントを識別(例: tenant1.example.com)
String serverName = request.getServerName();
if (serverName.contains(".")) {
return serverName.split("\\.")[0];
}
// ヘッダーからテナントを識別
String tenantHeader = request.getHeader("X-Tenant-ID");
if (tenantHeader != null && !tenantHeader.isEmpty()) {
return tenantHeader;
}
// デフォルトテナント
return "default";
}
private void applyTenantSettings(String tenantId, HttpServletRequest request) {
// テナントごとのデータソース設定
// テナントごとのテーマ設定
// テナントごとのロケール設定など
}
}
原因:
解決策:
原因:
解決策:
原因:
解決策:
フィルタとリスナは、Servlet仕様が提供する強力な機能であり、Webアプリケーション開発において以下のような利点があります:
これらの機能を適切に活用することで、より構造化され、保守性の高いWebアプリケーションを構築できます。実際の開発では、アプリケーションの要件に応じて、ここで学んだ基本パターンを適応させて使用してください。
さらに高度な使用方法として、フィルタとリスナを組み合わせた設計パターンや、非同期処理との連携、マイクロサービスアーキテクチャでの活用など、さまざまな応用が可能です。基礎をしっかり理解した上で、これらの応用技術にも挑戦していくと良いでしょう。