tomcat集群时,原来通过HttpSessionListener实现类监听session的创建和销毁来统计在线人数的方法不再有效,因为不是每个人登陆都会在同一个tomcat服务器上,而在另一台tomcat上登陆的人的session是通过session复制创建的,而复制过程不会调用HttpSessionListener接口的方法,也一直没找着如何监听session复制的方法,所以就没法统计在线人了。
今天突然回想起tomcat下的manager应用上面就能看到session数和session的内容,于是本文的实现原理就是,做一个类似这样的servlet,此servlet把tomcat上负责管理应用的对象保存下来,供我任意使用。在tomcat上看应用的信息时,使用的是http://localhost:8080/manager/html/list这个路径,页面信息见下图:
于是把源码下来看看 (apache-tomcat-6.0.39-src),细看tomcat下webapps/manager/WEB-INF/web.xml文件配置,发现原来tomcat是通过org.apache.catalina.manager.ManagerServlet这个类来提供以上服务的,跟踪此类doGet方法代码
1 // --------------------------------------------------------- Public Methods
2
3 /**
4 * Process a GET request for the specified resource.
5 *
6 * @param request
7 * The servlet request we are processing
8 * @param response
9 * The servlet response we are creating
10 *
11 * @exception IOException
12 * if an input/output error occurs
13 * @exception ServletException
14 * if a servlet-specified error occurs
15 */
16 public void doGet(HttpServletRequest request, HttpServletResponse response)
17 throws IOException, ServletException {
18
19 // Identify the request parameters that we need
20 // 取得访问路径,
21 String command = request.getPathInfo();
22
23 String path = request.getParameter("path");
24 String deployPath = request.getParameter("deployPath");
25 String deployConfig = request.getParameter("deployConfig");
26 String deployWar = request.getParameter("deployWar");
27
28 // Prepare our output writer to generate the response message
29 response.setContentType("text/html; charset=" + Constants.CHARSET);
30
31 String message = "";
32 // Process the requested command
33 if (command == null || command.equals("/")) {
34 } else if (command.equals("/deploy")) {
35 message = deployInternal(deployConfig, deployPath, deployWar);
36 // 找到了,就是这个路径,往下看list方法
37 } else if (command.equals("/list")) {
38 } else if (command.equals("/reload")) {
39 message = reload(path);
40 } else if (command.equals("/undeploy")) {
41 message = undeploy(path);
42 } else if (command.equals("/expire")) {
43 message = expireSessions(path, request);
44 } else if (command.equals("/sessions")) {
45 try {
46 doSessions(path, request, response);
47 return;
48 } catch (Exception e) {
49 log("HTMLManagerServlet.sessions[" + path + "]", e);
50 message = sm
51 .getString("managerServlet.exception", e.toString());
52 }
53 } else if (command.equals("/start")) {
54 message = start(path);
55 } else if (command.equals("/stop")) {
56 message = stop(path);
57 } else if (command.equals("/findleaks")) {
58 message = findleaks();
59 } else {
60 message = sm.getString("managerServlet.unknownCommand", command);
61 }
62 // 就是这个方法生成上面的那个页面
63 list(request, response, message);
64 }
list
1 /**
2 * Render a HTML list of the currently active Contexts in our virtual host,
3 * and memory and server status information.
4 *
5 * @param request
6 * The request
7 * @param response
8 * The response
9 * @param message
10 * a message to display
11 */
12 public void list(HttpServletRequest request, HttpServletResponse response,
13 String message) throws IOException {
14
15 if (debug >= 1)
16 log("list: Listing contexts for virtual host '" + host.getName()
17 + "'");
18
19 PrintWriter writer = response.getWriter();
20
21 // HTML Header Section
22 writer.print(Constants.HTML_HEADER_SECTION);
23
24 // Body Header Section
25 Object[] args = new Object[2];
26 args[0] = request.getContextPath();
27 args[1] = sm.getString("htmlManagerServlet.title");
28 writer.print(MessageFormat.format(Constants.BODY_HEADER_SECTION, args));
29
30 // Message Section
31 args = new Object[3];
32 args[0] = sm.getString("htmlManagerServlet.messageLabel");
33 if (message == null || message.length() == 0) {
34 args[1] = "OK";
35 } else {
36 args[1] = RequestUtil.filter(message);
37 }
38 writer.print(MessageFormat.format(Constants.MESSAGE_SECTION, args));
39
40 // Manager Section
41 args = new Object[9];
42 args[0] = sm.getString("htmlManagerServlet.manager");
43 args[1] = response.encodeURL(request.getContextPath() + "/html/list");
44 args[2] = sm.getString("htmlManagerServlet.list");
45 args[3] = response.encodeURL(request.getContextPath() + "/"
46 + sm.getString("htmlManagerServlet.helpHtmlManagerFile"));
47 args[4] = sm.getString("htmlManagerServlet.helpHtmlManager");
48 args[5] = response.encodeURL(request.getContextPath() + "/"
49 + sm.getString("htmlManagerServlet.helpManagerFile"));
50 args[6] = sm.getString("htmlManagerServlet.helpManager");
51 args[7] = response.encodeURL(request.getContextPath() + "/status");
52 args[8] = sm.getString("statusServlet.title");
53 writer.print(MessageFormat.format(Constants.MANAGER_SECTION, args));
54
55 // Apps Header Section
56 args = new Object[6];
57 args[0] = sm.getString("htmlManagerServlet.appsTitle");
58 args[1] = sm.getString("htmlManagerServlet.appsPath");
59 args[2] = sm.getString("htmlManagerServlet.appsName");
60 args[3] = sm.getString("htmlManagerServlet.appsAvailable");
61 args[4] = sm.getString("htmlManagerServlet.appsSessions");
62 args[5] = sm.getString("htmlManagerServlet.appsTasks");
63 writer.print(MessageFormat.format(APPS_HEADER_SECTION, args));
64
65 // Apps Row Section
66 // Create sorted map of deployed applications context paths.
67
68 // host就当是当前的tomcat吧,那么contexts就此tomcat下的所有应用
69 Container children[] = host.findChildren();
70 String contextPaths[] = new String[children.length];
71 // 循环每个应用
72 for (int i = 0; i < children.length; i++)
73 // 应用名称
74 contextPaths[i] = children[i].getName();
75
76 TreeMap sortedContextPathsMap = new TreeMap();
77
78 for (int i = 0; i < contextPaths.length; i++) {
79 // 应用部署路径
80 String displayPath = contextPaths[i];
81 sortedContextPathsMap.put(displayPath, contextPaths[i]);
82 }
83
84 String appsStart = sm.getString("htmlManagerServlet.appsStart");
85 String appsStop = sm.getString("htmlManagerServlet.appsStop");
86 String appsReload = sm.getString("htmlManagerServlet.appsReload");
87 String appsUndeploy = sm.getString("htmlManagerServlet.appsUndeploy");
88 String appsExpire = sm.getString("htmlManagerServlet.appsExpire");
89
90 Iterator iterator = sortedContextPathsMap.entrySet().iterator();
91 boolean isHighlighted = true;
92 boolean isDeployed = true;
93 String highlightColor = null;
94
95 while (iterator.hasNext()) {
96 // Bugzilla 34818, alternating row colors
97 isHighlighted = !isHighlighted;
98 if (isHighlighted) {
99 highlightColor = "#C3F3C3";
100 } else {
101 highlightColor = "#FFFFFF";
102 }
103
104 Map.Entry entry = (Map.Entry) iterator.next();
105 String displayPath = (String) entry.getKey();
106 String contextPath = (String) entry.getValue();
107 Context context = (Context) host.findChild(contextPath);
108 if (displayPath.equals("")) {
109 displayPath = "/";
110 }
111
112 if (context != null) {
113 try {
114 isDeployed = isDeployed(contextPath);
115 } catch (Exception e) {
116 // Assume false on failure for safety
117 isDeployed = false;
118 }
119
120 args = new Object[7];
121 args[0] = URL_ENCODER.encode(contextPath + "/");
122 args[1] = RequestUtil.filter(displayPath);
123 if (context.getDisplayName() == null) {
124 args[2] = " ";
125 } else {
126 args[2] = RequestUtil.filter(context.getDisplayName());
127 }
128 managerServlet
129 // 应用是否已启动
130 args[3] = new Boolean(context.getAvailable());
131 args[4] = response.encodeURL(request.getContextPath()
132 + "/html/sessions?path="
133 + URL_ENCODER.encode(displayPath));
134 if (context.getManager() != null) {
135 args[5] = new Integer(context.getManager()
136 .getActiveSessions());
137 } else {
138 args[5] = new Integer(0);
139 }
140
141 args[6] = highlightColor;
142 //打印出一行关于此应用的信息,应用的URL,当前状态,session数等,具体见上图
143 writer.print(MessageFormat.format(APPS_ROW_DETAILS_SECTION,
144 args));
145
146 args = new Object[14];
147 args[0] = response
148 .encodeURL(request.getContextPath()
149 + "/html/start?path="
150 + URL_ENCODER.encode(displayPath));
151 args[1] = appsStart;
152 args[2] = response.encodeURL(request.getContextPath()
153 + "/html/stop?path=" + URL_ENCODER.encode(displayPath));
154 args[3] = appsStop;
155 args[4] = response.encodeURL(request.getContextPath()
156 + "/html/reload?path="
157 + URL_ENCODER.encode(displayPath));
158 args[5] = appsReload;
159 args[6] = response.encodeURL(request.getContextPath()
160 + "/html/undeploy?path="
161 + URL_ENCODER.encode(displayPath));
162 args[7] = appsUndeploy;
163
164 args[8] = response.encodeURL(request.getContextPath()
165 + "/html/expire?path="
166 + URL_ENCODER.encode(displayPath));
167 args[9] = appsExpire;
168 args[10] = sm.getString("htmlManagerServlet.expire.explain");
169 Manager manager = context.getManager();
170 if (manager == null) {
171 args[11] = sm.getString("htmlManagerServlet.noManager");
172 } else {
173 args[11] = new Integer(context.getManager()
174 .getMaxInactiveInterval() / 60);
175 }
176 args[12] = sm.getString("htmlManagerServlet.expire.unit");
177
178 args[13] = highlightColor;
179
180 if (context.getPath().equals(this.context.getPath())) {
181 writer.print(MessageFormat.format(
182 MANAGER_APP_ROW_BUTTON_SECTION, args));
183 } else if (context.getAvailable() && isDeployed) {
184 writer.print(MessageFormat.format(
185 STARTED_DEPLOYED_APPS_ROW_BUTTON_SECTION, args));
186 } else if (context.getAvailable() && !isDeployed) {
187 writer.print(MessageFormat.format(
188 STARTED_NONDEPLOYED_APPS_ROW_BUTTON_SECTION, args));
189 } else if (!context.getAvailable() && isDeployed) {
190 writer.print(MessageFormat.format(
191 STOPPED_DEPLOYED_APPS_ROW_BUTTON_SECTION, args));
192 } else {
193 writer.print(MessageFormat.format(
194 STOPPED_NONDEPLOYED_APPS_ROW_BUTTON_SECTION, args));
195 }
196
197 }
198 }
199
200 // Deploy Section
201 args = new Object[7];
202 args[0] = sm.getString("htmlManagerServlet.deployTitle");
203 args[1] = sm.getString("htmlManagerServlet.deployServer");
204 args[2] = response.encodeURL(request.getContextPath() + "/html/deploy");
205 args[3] = sm.getString("htmlManagerServlet.deployPath");
206 args[4] = sm.getString("htmlManagerServlet.deployConfig");
207 args[5] = sm.getString("htmlManagerServlet.deployWar");
208 args[6] = sm.getString("htmlManagerServlet.deployButton");
209 writer.print(MessageFormat.format(DEPLOY_SECTION, args));
210
211 args = new Object[4];
212 args[0] = sm.getString("htmlManagerServlet.deployUpload");
213 args[1] = response.encodeURL(request.getContextPath() + "/html/upload");
214 args[2] = sm.getString("htmlManagerServlet.deployUploadFile");
215 args[3] = sm.getString("htmlManagerServlet.deployButton");
216 writer.print(MessageFormat.format(UPLOAD_SECTION, args));
217
218 // Diagnostics section
219 args = new Object[5];
220 args[0] = sm.getString("htmlManagerServlet.diagnosticsTitle");
221 args[1] = sm.getString("htmlManagerServlet.diagnosticsLeak");
222 args[2] = response.encodeURL(request.getContextPath()
223 + "/html/findleaks");
224 args[3] = sm.getString("htmlManagerServlet.diagnosticsLeakWarning");
225 args[4] = sm.getString("htmlManagerServlet.diagnosticsLeakButton");
226 writer.print(MessageFormat.format(DIAGNOSTICS_SECTION, args));
227
228 // Server Header Section
229 args = new Object[7];
230 args[0] = sm.getString("htmlManagerServlet.serverTitle");
231 args[1] = sm.getString("htmlManagerServlet.serverVersion");
232 args[2] = sm.getString("htmlManagerServlet.serverJVMVersion");
233 args[3] = sm.getString("htmlManagerServlet.serverJVMVendor");
234 args[4] = sm.getString("htmlManagerServlet.serverOSName");
235 args[5] = sm.getString("htmlManagerServlet.serverOSVersion");
236 args[6] = sm.getString("htmlManagerServlet.serverOSArch");
237 writer.print(MessageFormat
238 .format(Constants.SERVER_HEADER_SECTION, args));
239
240 // Server Row Section
241 args = new Object[6];
242 args[0] = ServerInfo.getServerInfo();
243 args[1] = System.getProperty("java.runtime.version");
244 args[2] = System.getProperty("java.vm.vendor");
245 args[3] = System.getProperty("os.name");
246 args[4] = System.getProperty("os.version");
247 args[5] = System.getProperty("os.arch");
248 writer.print(MessageFormat.format(Constants.SERVER_ROW_SECTION, args));
249
250 // HTML Tail Section
251 writer.print(Constants.HTML_TAIL_SECTION);
252
253 // Finish up the response
254 writer.flush();
255 writer.close();
256 }
注意:context.getManager().findSessions()可以取得所有session,但这是个org.apache.catalina.Session[]数组,不是HttpSession[]数组,但这个Session接口里面有个getSession方法,返回结果正是HttpSession类型,没错,就是循环这个数组并调用其getSession方法就可以取得所有在线用户了
上面的Session[]数组是从context对象里面来的,而context是从host对象来的,host是个初始值为NULL的成员变量,是什么时候赋上值的?是在init方法执行前,setWrapper方法执行时赋的值,请看setWrapper方法代码
public class HostManagerServlet
extends HttpServlet implements ContainerServlet
1 /**
2 * Set the Wrapper with which we are associated.
3 *
4 * @param wrapper The new wrapper
5 */
6 public void setWrapper(Wrapper wrapper) {
7
8 //这里所有需要的对象都有了,其实下面我们需要拿到wrapper就够了
9 this.wrapper = wrapper;
10 if (wrapper == null) {
11 context = null;
12 host = null;
13 engine = null;
14 } else {
15 context = (Context) wrapper.getParent();
16 host = (Host) context.getParent();
17 engine = (Engine) host.getParent();
18 }
19
20 // Retrieve the MBean server
21 mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
22
23 }
setWrapper会在初始化时被调用,怎么实现的,首先看web.xml中对此servlet的配置,没什么特别,我们可以发散一下思维,struts2里面action如何能自动注入request对象?Spring如何让service监听事件?答案是一样的,那就是让你的类实现某个接口,你要的东西就给你了,对的,这里也一样,此servlet实现了ContainerServlet接口,初始的时候setWrapper方法才会被调用。
是JAVA新手的看这里,我提出上面这些问题,不是想卖什么关子,只是想启发JAVA初学者们,当某天你们做设计时,可以参考这种方法,一句概括就是:只要你实现我的接口,我就可以让你做某事,而不需要任何额外的配置。当然这种设计的缺点就是入侵、偶合。举个简单的应用场景:每天晚上,我用一个定时器通过Spring搜索所有实现了“GarbageCleaner”接口的service bean,并调用其clean方法清理对应模块的垃圾数据,那么任何模块的service只要实现了此接口,就会被调用。
回到正题,我也自已写个servlet并且实ContainerServlet接口吧,使用静态方法取得所有的session,具体代码如下:
1 package manager.session.http.servlet;
2
3 import java.io.IOException;
4 import java.util.LinkedHashMap;
5 import java.util.Map;
6
7 import javax.servlet.ServletException;
8 import javax.servlet.http.HttpServlet;
9 import javax.servlet.http.HttpServletRequest;
10 import javax.servlet.http.HttpServletResponse;
11 import javax.servlet.http.HttpSession;
12
13 import org.apache.catalina.ContainerServlet;
14 import org.apache.catalina.Context;
15 import org.apache.catalina.Session;
16 import org.apache.catalina.Wrapper;
17
18 public class TomcatWrapperServlet extends HttpServlet implements
19 ContainerServlet {
20 private static final long serialVersionUID = 1L;
21
22 // 弄个静态变量,初始化后就记下来,以备随时使用
23 private static Wrapper wrapper = null;
24
25 public Wrapper getWrapper() {
26 return wrapper;
27 }
28
29 public void setWrapper(Wrapper wrapper) {
30 TomcatWrapperServlet.wrapper = wrapper;
31 }
32
33 // doGet不做任何事情,只需要接收第一次请求,触发初始动作就完成它的使命了
34 @Override
35 protected void doGet(HttpServletRequest req, HttpServletResponse resp)
36 throws ServletException, IOException {
37 resp.getWriter().println("Hello world!");
38 resp.getWriter().flush();
39 resp.getWriter().close();
40 }
41
42 // 初始化后可通过此静态方法取得所有session
43 public static Map<String, HttpSession> fillSessions() {
44 if (wrapper == null) {// 没有初始化
45 throw new RuntimeException(
46 "本servlet未被初始化,您必须先通过URL访问本servlet后,才可以调用这个方法");
47 }
48 Map<String, HttpSession> sessions = new LinkedHashMap<String, HttpSession>();
49
50 // 取得本应用
51 Context context = (Context) wrapper.getParent();
52 // 取得Session[]数组
53 Session[] temps = context.getManager().findSessions();
54 if (temps != null && temps.length > 0) {
55 for (int j = 0; j < temps.length; j++) {
56 // Map<sessionId,session>
57 sessions.put(temps[j].getSession().getId(), temps[j]
58 .getSession());
59 }
60 }
61 return sessions;
62 }
63
64 }
在web.xml配置一下,然后启动应用,访问之,结果出现异常,是一个安全异常:TomcatWrapperServlet is privileged and cannot be loaded by this web application(想想如下),说我的类是个特权类,不能被普通的web应用加载,为何manager这个应用又可以呢?把manager/META-INF/context.xml复制到我的应用,再加载,再访问,一切搞定,此文件内容只有一句
Xml代码
<Context antiResourceLocking="false" privileged="true" />
HTTP Status 500 - Error allocating a servlet instance
type Exception report
message Error allocating a servlet instance
description The server encountered an internal error that prevented it from fulfilling this request.
exception
javax.servlet.ServletException: Error allocating a servlet instance
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:879)
org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:617)
org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1760)
java.lang.Thread.run(Thread.java:722)
root cause
java.lang.SecurityException: Servlet of class manager.session.http.servlet.TomcatWrapperServlet is privileged and cannot be loaded by this web application
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:879)
org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:617)
org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1760)
java.lang.Thread.run(Thread.java:722)
note The full stack trace of the root cause is available in the Apache Tomcat/6.0.37 logs.
Apache Tomcat/6.0.37
来自:http://www.jspspace.com/ResearchTopics/Art-1757-17.html
转载于:https://my.oschina.net/u/1590001/blog/268218