ServletContextの初期化を捉える

f:id:bau1537:20210925143925j:plain

ServletContext

環境

  • プロジェクト構成
    • いろいろ試しながらやってるんで、ここでやりたいこととは無関係なファイル/ディレクトリがちらほらある
.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── org
        │       └── example
        │           ├── filter
        │           │   ├── DemoFilter1.java
        │           │   └── DemoFilter2.java
        │           ├── listener
        │           │   └── DemoServletContextListener.java
        │           └── servlet
        │               └── DemoServlet.java
        └── webapp
            └── WEB-INF
                └── web.xml

10 directories, 6 files
  • pom
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>servlet</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>Servlet Maven Webapp</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
$ java -jar $JETTY_HOME/start.jar --version

Jetty Server Classpath:
-----------------------
Version Information on 22 entries in the classpath.
Note: order presented here is how they would appear on the classpath.
      changes to the --module=name command line options will be reflected here.
 0:                    (dir) | ${jetty.base}/resources
 1:             2.0.0-alpha1 | ${jetty.home}/lib/logging/slf4j-api-2.0.0-alpha1.jar
 2:                   11.0.6 | ${jetty.home}/lib/logging/jetty-slf4j-impl-11.0.6.jar
 3:                    5.0.2 | ${jetty.home}/lib/jetty-jakarta-servlet-api-5.0.2.jar
 4:                   11.0.6 | ${jetty.home}/lib/jetty-http-11.0.6.jar
 5:                   11.0.6 | ${jetty.home}/lib/jetty-server-11.0.6.jar
 6:                   11.0.6 | ${jetty.home}/lib/jetty-xml-11.0.6.jar
 7:                   11.0.6 | ${jetty.home}/lib/jetty-util-11.0.6.jar
 8:                   11.0.6 | ${jetty.home}/lib/jetty-io-11.0.6.jar
 9:                   11.0.6 | ${jetty.home}/lib/jetty-jndi-11.0.6.jar
10:                   11.0.6 | ${jetty.home}/lib/jetty-security-11.0.6.jar
11:                   11.0.6 | ${jetty.home}/lib/jetty-servlet-11.0.6.jar
12:                   11.0.6 | ${jetty.home}/lib/jetty-webapp-11.0.6.jar
13:                   11.0.6 | ${jetty.home}/lib/jetty-plus-11.0.6.jar
14:                    2.0.0 | ${jetty.home}/lib/jakarta.transaction-api-2.0.0.jar
15:                   11.0.6 | ${jetty.home}/lib/jetty-annotations-11.0.6.jar
16:                      9.1 | ${jetty.home}/lib/annotations/asm-9.1.jar
17:                      9.1 | ${jetty.home}/lib/annotations/asm-analysis-9.1.jar
18:                      9.1 | ${jetty.home}/lib/annotations/asm-commons-9.1.jar
19:                      9.1 | ${jetty.home}/lib/annotations/asm-tree-9.1.jar
20:                    2.0.0 | ${jetty.home}/lib/annotations/jakarta.annotation-api-2.0.0.jar
21:                   11.0.6 | ${jetty.home}/lib/jetty-deploy-11.0.6.jar
  • web.xml に書いてある内容は今回関係ないのでパス

Event Listener で ServletContext が初期化された時を捕捉する

  • ではやってみましょー
  • Event Listener というものを使っていく
  • Event Listener を使うと、ウェブアプリケーションで発生する特定のイベントを捕捉することができる
    • HttpSession とか ServletRequest とかのイベントもOK
  • ServletContext のライフサイクルに関係するイベントを捕捉するのであれば、 jakarta.servlet.ServletContextListener を継承すればよさそう
    • Event Listener として登録しないと動いてくれないので、 WebListener アノテーションをつける必要
  • 次に示すとおり、 contextInitialized メソッドで、 ServletContext 初期化のタイミングを捕捉する
package org.example.listener;

import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;

@WebListener
public class DemoServletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContextListener.super.contextInitialized(sce);
        System.out.println("context initialized");
        System.out.println("getServerInfo : " + sce.getServletContext().getServerInfo());
        System.out.println("getServletContextName : " + sce.getServletContext().getServletContextName());
    }

}
  • デプロイして動かすとログが出力される
    • ログの出力タイミングから推測すると、サーブレットクラスや、フィルタークラスよりも先にServletContextが初期化されてる
    • それもそうで、サーブレットクラス/フィルタークラスの初期化メソッドの引数には初期化済みのServletContextが渡されるので、先に初期化しないといけない
context initialized
getServerInfo : jetty/11.0.6
getServletContextName : /servlet-1.0-SNAPSHOT
  • 確かに、ウェブアプリケーション共通の情報にアクセスできる
  • 公式では contextInitialized の使い方の例としてデータベースのコネクションの作成&保持と書いてる
    • HTTPリクエストを受け付ける前にやっておきたい作業場所と考えるのがいいだろう
    • Servletの初期化メソッドでも同じことができるけど、Jettyだと最初のリクエストが来ないと実行されないし、最初のリクエストだけレスポンスに時間がかかってしまう

Servlet Request が作成されるタイミングも捕捉してみる

  • Servlet Request が作成されるタイミングを補足できるようなので、アクセスログとかを出したいならこれかも?と思ってやってみた
  • jakarta.servlet.ServletRequestListener を継承する
  • requestInitialized メソッドをオーバーライドする
package org.example.listener;

import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.HttpServletRequest;

@WebListener
public class DemoServletRequestListener implements ServletRequestListener {

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        ServletRequest servletRequest = sre.getServletRequest();
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        sre.getServletContext().log("request url : " + httpServletRequest.getRequestURL());
    }

}
  • デプロイしてアクセスするとログが出てくる
2021-10-02 21:47:07.839:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-26: request url : http://localhost:8080/servlet-1.0-SNAPSHOT/WEB-INF/
2021-10-02 21:47:13.633:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-29: request url : http://localhost:8080/servlet-1.0-SNAPSHOT/demo
2021-10-02 21:47:41.114:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-32: request url : http://localhost:8080/servlet-1.0-SNAPSHOT/filter
  • フィルターでもログは出せるけど、Event Listenerの場合はどのパスにアクセスが来た場合でもログが出る
    • フィルターは対象のURLを絞れる
    • 正直アクセスログの使い分けはどちらにすればいいか悩みどころ
    • どんなリクエストも出したい場合は Event Listener がいいかも?
  • Event Listener では Servlet Response の作成を捕捉できない
  • ちなみに Event Listener クラスへのイベント通知は必ずしも同期的ではない