“本例基于Web基础的servlet展开,虽然较简单,但对Web的三大组件进行了结合。 ”
主要实现三个功能:
- 提供用户登录界面,提供注销功能。
- 显示当前网站的所有登录用户信息
- 管理员可以踢除指定的登录用户
友情提示: Web的三大组件都需要在web.xml中配置哦!
登录界面 login.jsp
这里比较简单,假设什么人都能进入,不涉及数据库
1 2 3 4
| <form action="${pageContext.request.contextPath }/LoginServlet" method="post"> 用户名:<input type="text" name="username" /><br> <input type="submit" value="提交"/> </form>
|
处理登录请求 LoginServlet
由于什么人都能进入,所有我们省去了校验用户名等操作,即假设登录成功。我们将拿到的用户数据放到session域中。核心代码如下:
1 2 3 4 5 6 7
| String username = request.getParameter("username"); HttpSession session = request.getSession(true); session.setAttribute("user", username); // 把登录用户的IP地址存放到session中 session.setAttribute("ip", request.getRemoteHost()); // 跳转到用户主页 response.sendRedirect(request.getContextPath() + "/index.jsp");
|
此时我们已经将用户的数据存入到了session域当中去了。但如果我们的用户名是中文的很有可能出现乱码。这里我们需要添加一个解决post乱码的代码。
在最前面加上:request.setCharacterEncoding("utf-8");
很显然,这句代码是复用的,每一个servlet都需要它的支持,这时候我们想到另一种一劳永逸的方法,就是添加一个过滤器,对其进行过滤,解决代码复写问题,详细代码见post/get乱码过滤器解决方案
监听器 OnLineListener
由于我们需要得到所有在线用户的信息,就需要监听user属性名称的增加。响应的接口的介绍参见HttpSessionAttributeLisener
下面是该监听器的具体代码:
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
| public class OnLineListener implements HttpSessionAttributeListener { /** * 当用户登录成功后,会执行session.setAttribute("user", username); 该方法用于监听用户的user属性的添加 */
@Override public void attributeAdded(HttpSessionBindingEvent event) { // 得到属性名 String name = event.getName(); ServletContext context = event.getSession().getServletContext(); //如果取出的属性名是user的话,即拿到用户名 if ("user".equals(name)) { // 同步的代码块,为了避免多个登录用户同时操作onLine数据时引发的并发问题 synchronized (OnLineListener.class) { // 1 把当前登录的session对象封装到map集合中 // 1.1 先从context域中获取session数据 Map<String, HttpSession> onLine = (Map<String, HttpSession>) context .getAttribute("onLine"); // 1.2 如果网站第一个登录用户,onLine为null,这时新建一个map集合 if (onLine == null) { onLine = new HashMap<String, HttpSession>(); } // 1.3把当前用户的session存入map集合 HttpSession session = event.getSession(); onLine.put(session.getId(), session); // 2 把封装好的map保存到context域中 context.setAttribute("onLine", onLine); } } }
@Override public void attributeReplaced(HttpSessionBindingEvent event) {
} //这部分代码可先不看,阅读之后内容回头再看 @Override public void attributeRemoved(HttpSessionBindingEvent event) { String name = event.getName(); ServletContext context = event.getSession().getServletContext(); String sessionId = event.getSession().getId(); if ("user".equals(name)) { // 1 获取context域中的map集合 Map<String, HttpSession> onLine = (Map<String, HttpSession>) context .getAttribute("onLine"); // 2 移除对应的session对象 onLine.remove(sessionId); // 3 把修改后的map保存到context域中 context.setAttribute("onLine", onLine); } } }
|
web.xml中的配置
1 2 3
| <listener> <listener-class>cn.ninja.web.OnLineListener</listener-class> </listener>
|
注意: 这里的编程技巧,onLine是先取再判断是否为空,空则说明是第一个用户,在新建一个Map。这样的写法完美的避免了每一次进入监听器都会重新建立一个Map的错误。并且,这里选择将Map放入context域中,其原因是context都能取得到,而session虽然也能取得到,但其每一个session对应一个用户使其并不合适放所有用户的集合。
网站主页 index.jsp
主要包括显示用户名,安全退出功能,查看在线登录用户功能
1 2 3
| 欢迎回来,${sessionScope.user }, <a href="${pageContext.request.contextPath }/LogoutServlet">【安全退出】</a><br/> <a href="${pageContext.request.contextPath }/GetOnLineServlet">【查看在线登录用户】</a>
|
处理查看在线用户请求 GetOnLineServlet
我们想要获得在线用户的登录名,ip地址,登录时间,最后访问时间。但是,我们之前获得的Map没办法直接取到这些值。所以我们这里先定义一个JavaBean取名为OnLineBean,来封装这些属性:
1 2 3 4 5 6
| private String sessionId;// session对象的id,它的作用我们之后再讲 private String name;// 登录名 private String ip;// ip private String loginTime;// 登录时间 private String lastTime;// 最后访问时间 //getter setter方法
|
以下我们给出GetOnLineServlet的核心代码,用于获得每个属性,并转发到在线用户信息界面
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
| // 1从context域中取出map集合 Map<String, HttpSession> onLine = (Map<String, HttpSession>) this .getServletContext().getAttribute("onLine"); // 2 创建一个新的List集合 List<OnLineBean> list = new ArrayList<OnLineBean>(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); // 3遍历map集合 if (onLine != null) { synchronized (GetOnLineServlet.class) { for (Entry<String, HttpSession> entry : onLine.entrySet()) { OnLineBean bean = new OnLineBean(); bean.setSessionId(entry.getKey()); HttpSession session = entry.getValue(); bean.setName((String) session.getAttribute("user")); bean.setIp((String) session.getAttribute("ip")); bean.setLoginTime(sdf.format(new Date(session .getCreationTime()))); bean.setLastTime(sdf.format(new Date(session .getLastAccessedTime()))); // 把封装好的javabean放入list集合中 list.add(bean); } } } // 4 把list转发到jsp页面中 request.setAttribute("list", list); request.getRequestDispatcher("/online.jsp").forward(request, response);
|
在线用户列表页面 onLine.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <table align="left" border="1" width="600px"> <tr> <th>编号</th> <th>登录名</th> <th>登录时间</th> <th>最后访问时间</th> <th>IP</th> <th>操作</th> </tr> <c:forEach items="${requestScope.list }" var="bean" varStatus="varSta"> <tr> <td>${varSta.count }</td> <td>${bean.name }</td> <td>${bean.loginTime }</td> <td>${bean.lastTime }</td> <td>${bean.ip }</td> <td><a href="${pageContext.request.contextPath }/KickOutServlet?sessionId=${bean.sessionId }">移除</a></td> </tr> </c:forEach> </table>
|
踢人功能请求 KickOutServlet
由onLine.jsp 可知,发生踢人请求时,会传递一个sessionId的参数,这也就是为什么需要把它包装在OnLineBean中的原因。接着我们从Map中获得相应的session对象,并将其信息remove,最后重定向到查看在线用户的网页上来。
这里需要使用之前的那个OnLineListener监听器来删除一些信息。见attributeRemoved方法。
1 2 3 4 5 6 7 8 9 10 11 12
| // 1 接收到踢出的id String sessionId = request.getParameter("sessionId"); // 2 强制注销指定id的用户 Map<String, HttpSession> onLine = (Map<String, HttpSession>) this .getServletContext().getAttribute("onLine"); // 查找需要注销的session对象 HttpSession session = onLine.get(sessionId); if (session != null) { session.removeAttribute("user");// 自动会调用监听器,移除map中的已经注销的用户信息 session.removeAttribute("ip"); } response.sendRedirect(request.getContextPath() + "/GetOnLineServlet");
|
这样,我们就可以成功踢人啦~
慢着,如果到此为止的话,我们实现的是相互踢人,这显然是不合理的。应该拥有管理权限的人才能踢人哦。
所以呢,我们这里需要加一个过滤器,将不是管理员的人过滤掉,这里的管理员暂且就定为本地ip啦~
管理员过滤器 SecuryFilter
我们将这个过滤器主要用来对KickOutServlet进行过滤,也就是在执行这个请求前执行过滤器,如果是管理员就放行。
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
| public class SecuryFilter implements Filter {
@Override public void destroy() {
}
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //获得ip地址 String ip = request.getRemoteHost(); response.setContentType("text/html;charset=utf-8");//这句可以放进乱码过滤器 if ("localhost".equals(ip) || "127.0.0.1".equals(ip)) { // 1管理员 chain.doFilter(request, response);
} else { // 2非管理员 response.getWriter().write("别想删除了,你不是管理员"); } }
@Override public void init(FilterConfig filterConfig) throws ServletException {
}
}
|
web.xml中对过滤器进行配置
1 2 3 4 5 6 7 8
| <filter> <filter-name>SecuryFilter</filter-name> <filter-class>cn.ninja.web.SecuryFilter</filter-class> </filter> <filter-mapping> <filter-name>SecuryFilter</filter-name> <url-pattern>/KickOutServlet</url-pattern> </filter-mapping>
|
用户的注销功能请求 LogoutServlet
用户注销,同样的会触发removeAttribute对应的监听器,这里即OnLineListener,所以不用再写啦~
最后重定向到登录界面。
1 2 3 4 5 6 7 8 9 10 11 12
| /** * 用户注销,删除保存登录成功时session对象中的数据 */ HttpSession session = request.getSession(false); if (session != null) { session.removeAttribute("user"); session.removeAttribute("ip"); } /* 这里触发监听器 */
response.sendRedirect(request.getContextPath() + "/login.jsp"); }
|
到此,全部的功能都已经实现啦~虽然这是个简单的小案例,但涵盖的内容包括了Web的三大组件,以及过滤器监听器,特记录下来,对这块知识做个总结。
“本次案例并没有使用多个ip去测,之后需要学习如何在一台电脑上虚拟多个局域网ip,来访问网站,以实现查看多个用户登录信息的测试”