“本例基于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,来访问网站,以实现查看多个用户登录信息的测试”