Java Webアプリケーションでは、プロジェクトの起動時にスレッドを開始し、定期的にデータベーススキャンやファイル監視などのタスクを実行する必要がよくあります。この機能を実現するには、`web.xml` ファイルに `Listener` を定義し、その `Listener` 内でスレッドを起動し、スレッド内で機能を実装します。
1. カスタムListenerの定義
Struts+Spring+HibernateのWebプロジェクトでは、`web.xml` に以下のような設定が含まれていることが一般的です。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
これらの設定により、Webコンテナ(Tomcatなど)はプロジェクト起動時に `org.springframework.web.context.ContextLoaderListener` クラスをインスタンス化します。
同様に、独自の `Listener` を `web.xml` に定義して、Webサーバーにインスタンス化させることができます。
<?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" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>com.example.listener.ProjectStartupListener</listener-class>
</listener>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
上記の `web.xml` では、Webサーバーが起動時に独自に定義した `com.example.listener.ProjectStartupListener` クラスをインスタンス化します(一般的に、独自の `Listener` は `ContextLoaderListener` の後に記述します)。このクラス内でスレッドを起動します。
public class ProjectStartupListener implements ServletContextListener{
private BackgroundTaskThread taskThread;
@Override
public void contextDestroyed(ServletContextEvent event) {
if (taskThread != null && taskThread.isRunning()) {
taskThread.stopThread();
}
}
@Override
public void contextInitialized(ServletContextEvent event) {
if (taskThread == null) {
taskThread = new BackgroundTaskThread(event);
taskThread.start();
}
}
}
`Listener` クラスはWebサーバーによって管理されます。Webサーバーが起動すると、`Listener` クラスをインスタンス化し、`contextInitialized(ServletContextEvent event)` メソッドを呼び出します。Webサーバーがシャットダウンすると、`contextDestroyed(ServletContextEvent event)` メソッドが呼び出されます。したがって、これらのメソッド内でスレッドの起動と停止を実装できます。
2. Springコンテナ外からのBeanへのアクセス
起動されたスレッドは、一定の間隔でデータベースをスキャンし、新規データを検出するために使用されます。一般的なStruts+Spring+HibernateのWebプロジェクトでは、Springコンテナ内のBeanはSpringコンテナによって管理されます。しかし、ここで起動されるスレッドはSpringコンテナの外に存在するため、Springコンテナ内のBeanのインスタンスにアクセスしてデータベースにアクセスする方法が必要です。Springの `WebApplicationContextUtils` ユーティリティクラスを使用して、Springコンテナの参照を取得し、その内部のBeanのインスタンスへの参照を取得できます。 スレッドのコードは以下の通りです。
public class BackgroundTaskThread extends Thread{
public volatile boolean isRunning = true;
// スキャン間隔(ミリ秒)
public long scanInterval;
private WebApplicationContext springContext;
private DataService dataService;
private NotificationService notificationService;
private AuditService auditService;
private Config config;
ServletContextEvent event;
public BackgroundTaskThread(ServletContextEvent e){
this.event = e;
this.springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(event.getServletContext());
this.dataService = (DataService) springContext.getBean("dataService");
this.notificationService = (NotificationService) springContext.getBean("notificationService");
}
public void run(){
while (isRunning){
try {
this.config = dataService.findConfigByName("scan_interval");
this.scanInterval = Long.parseLong(config.getValue());
sleep(scanInterval);
System.out.println("バックグラウンドタスクを実行中...");
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
public void stopThread(){
isRunning = false;
}
public boolean isRunning(){
return isRunning;
}
}
このスレッドのコンストラクタでは、`Listener` から渡された `ServletContextEvent` 変数を使用します。この変数の `getServletContext()` メソッドでWebプロジェクトの `servletContext` を取得し、その `ServletContext` をパラメータとして `WebApplicationContextUtils` の `getRequiredWebApplicationContext()` メソッドを呼び出して `ApplicationContext` オブジェクトを取得します。最後に、`ApplicationContext` の `getBean()` メソッドでBeanのインスタンスを取得し、データベースへのアクセスを実現します。