我的任务是向站点管理员显示用户名列表以及每个用户当前使用的 Tomcat 会话数量(以及其他一些与支持相关的信息)。
我将经过身份验证的用户保留为应用程序上下文属性,如下所示(省略不必要的细节)。
Hashtable<String, UserInfo> logins //maps login to UserInfoRun Code Online (Sandbox Code Playgroud)
其中 UserInfo 定义为
class UserInfo implements Serializable {
String login;
private transient Map<HttpSession, String> sessions =
Collections.synchronizedMap(
new WeakHashMap<HttpSession, String>() //maps session to sessionId
);
...
}
Run Code Online (Sandbox Code Playgroud)
每次成功登录都会将会话存储到此sessions映射中。我的HttpSessionsListener实现sessionDestroyed()从该映射中删除了已销毁的会话,如果sessions.size() == 0,则从 中删除 UserInfo logins。
有时我会为某些用户显示 0 个会话。同行评审和单元测试表明代码是正确的。所有会话都是可序列化的。
Tomcat 是否有可能将会话从内存卸载到硬盘驱动器,例如,当有一段时间不活动时(会话超时设置为 40 分钟)?从 GC 的角度来看,是否存在会话“丢失”但未调用 HttpSessionsListener.sessionDestroyed() 的其他情况?
J2SE 6、Tomcat 6 或 7 独立运行,在任何操作系统上的行为都是一致的。
由于这个问题的浏览量接近 5000 次,我认为提供一个可行的解决方案示例会很有帮助。
问题中概述的方法是错误的 - 它不会处理服务器重新启动,也不会扩展。这是一个更好的方法。
首先,您的 HttpServlet 需要处理用户登录和注销,大致如下:
public class ExampleServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String action = req.getParameter("do");
HttpSession session = req.getSession(true);
//simple plug. Use your own controller here.
switch (action) {
case "logout":
session.removeAttribute("user");
break;
case "login":
User u = new User(session.getId(), req.getParameter("login"));
//store user on the session
session.setAttribute("user",u);
break;
}
}
}
Run Code Online (Sandbox Code Playgroud)
User bean 必须是可序列化的,并且必须在反序列化时重新注册自身:
class User implements Serializable {
private String sessionId;
private String login;
User(String sessionId, String login) {
this.sessionId = sessionId;
this.login = login;
}
public String getLogin() { return login; }
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
//re-register this user in sessions
UserAttributeListener.sessions.put(sessionId,this);
}
}
Run Code Online (Sandbox Code Playgroud)
您还需要一个 HttpAttributeListener 来正确处理会话生命周期:
public class UserAttributeListener implements HttpSessionAttributeListener {
static Map<String, User> sessions = new ConcurrentHashMap<>();
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
if ("user".equals(event.getName()))
sessions.put(event.getSession().getId(), (User) event.getValue());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent event) {
if ("user".equals(event.getName()))
ExampleServlet.sessions.remove(event.getSession().getId());
}
@Override
public void attributeReplaced(HttpSessionBindingEvent event) {
if ("user".equals(event.getName()))
ExampleServlet.sessions.put(event.getSession().getId(),
(User)event.getValue());
}
}
Run Code Online (Sandbox Code Playgroud)
当然,您需要在 web.xml 中注册您的监听器:
<listener>
<listener-class>com.example.servlet.UserAttributeListener</listener-class>
</listener>
Run Code Online (Sandbox Code Playgroud)
之后,您始终可以访问 UserAttributeListener 中的静态映射,以了解正在运行的会话数量、每个用户正在使用的会话数量等。理想情况下,您将拥有更复杂的数据结构,以保证其自己单独的单例类正确的访问方法。使用具有写时复制并发策略的容器也可能是一个好主意,具体取决于用例。
| 归档时间: |
|
| 查看次数: |
9150 次 |
| 最近记录: |