项目中有时需要在Java类中启动一个长时间运行的服务进程(监听进程、监控进程等),且该进程需要在tomcat启动时启动,在tomcat停止时退出。在项目开发的过程中,我需要在Java类中启动一个监听进程,而在该进程成功启动后,一调用该监听服务进程时,该进程就挂掉(退出),现将解决方法记录如下。

ServletContextListener接口能够接收到web context初始化和销毁的消息事件,因此我们可以在该接口中事件长时间运行进程的启动和停止。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class MyContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
String scriptPath = classLoader.getResource("daemon.sh")
.getPath();
List<String> commandsList = new ArrayList<String>();
commandsList.add("sh");
commandsList.add(scriptPath);
commandsList.add("start");
ProcessBuilder processBuilder = new ProcessBuilder(commandsList);
try {
Process process = processBuilder.start();
//以下两行代码是关键
ServletContext servletContext = sce.getServletContext();
servletContext.setAttribute("daemon", process);
int exitValue = process.waitFor();
if (0 == exitValue) {
System.out.println("start daemon success.");
} else {
System.out.println("start daemon failed, exitValue: " + exitValue);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
String scriptPath = classLoader.getResource("daemon.sh")
.getPath();
List<String> commandsList = new ArrayList<String>();
commandsList.add("sh");
commandsList.add(scriptPath);
commandsList.add("stop");
ProcessBuilder processBuilder = new ProcessBuilder(commandsList);
try {
Process process = processBuilder.start();
int exitValue = process.waitFor();
if (0 == exitValue) {
System.out.println("stop daemon success.");
} else {
System.out.println("stop daemon failed, exitValue: " + exitValue);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

然后需要在web.xml的所有filtersservlets定义之前定义:

1
2
3
<listener>
<listener-class>org.readus.executescript.MyContextListener</listener-class>
</listener>

如果使用Spring,则可使用如下设置代替上面的listener-class:

1
2
3
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

在上述的实现中,最重要的概括为两点:

  • 实现了ServletContextListener的接口
  • 将生成的长时间运行的进程写入ServletContext的属性中

Java中使用ServletContextListener接口来监听web应用的生命周期变化。其中包含的两个接口:

1
public void contextInitialized(ServletContextEvent sce);

该接口文档说明如下:

Receives notification that the web application initialization process is starting.All ServletContextListeners are notified of context initialization before any filters or servlets in the web application are initialized. @param sce the ServletContextEvent containing the ServletContext that is being initialized.

1
public void contextDestroyed(ServletContextEvent sce);

该接口的文档说明如下:

Receives notification that the ServletContext is about to be shut down. All servlets and filters will have been destroyed before any ServletContextListeners are notified of context destruction. @param sce the ServletContextEvent containing the ServletContext that is being destroyed.

本文所涉及的代码托管在了GitHub上ExecuteScriptDemo.