從第一篇已經講解過了監聽器的基本概念,以及Servlet各類的監聽器。這篇博文主要講解的是監聽器的應用。html
咱們在網站中通常使用Session來標識某用戶是否登錄了,若是登錄了,就在Session域中保存相對應的屬性。若是沒有登錄,那麼Session的屬性就應該爲空。java
如今,咱們想要統計的是網站的在線人數。咱們應該這樣作:咱們監聽是否有新的Session建立了,若是新建立了Sesssion,那麼在線人數就應該+1。這個在線人數是整個站點的,因此應該有Context對象保存。web
大體思路:瀏覽器
public class CountOnline implements HttpSessionListener { public void sessionCreated(HttpSessionEvent se) { //獲取獲得Context對象,使用Context域對象保存用戶在線的個數 ServletContext context = se.getSession().getServletContext(); //直接判斷Context對象是否存在這個域,若是存在就人數+1,若是不存在,那麼就將屬性設置到Context域中 Integer num = (Integer) context.getAttribute("num"); if (num == null) { context.setAttribute("num", 1); } else { num++; context.setAttribute("num", num); } } public void sessionDestroyed(HttpSessionEvent se) { ServletContext context = se.getSession().getServletContext(); Integer num = (Integer) se.getSession().getAttribute("num"); if (num == null) { context.setAttribute("num", 1); } else { num--; context.setAttribute("num", num); } } }
在線人數:${num}
咱們每使用一個瀏覽器訪問服務器,都會新建立一個Session。那麼網站的在線人數就會+1。安全
使用同一個頁面刷新,仍是使用的是那個Sesssion,因此網站的在線人數是不會變的。服務器
咱們都知道Session是保存在內存中的,若是Session過多,服務器的壓力就會很是大。微信
可是呢,Session的默認失效時間是30分鐘(30分鐘沒人用纔會失效),這形成Seesion可能會過多(沒人用也存在內存中,這不是明顯浪費嗎?)session
固然啦,咱們能夠在web.xml文件中配置Session的生命週期。可是呢,這是由服務器來作的,我嫌它的時間不夠準確。(有時候我配置了3分鐘,它用4分鐘才幫我移除掉Session)併發
因此,我決定本身用程序手工移除那些長時間沒人用的Session。jsp
要想移除長時間沒人用的Session,確定要先拿到所有的Session啦。因此咱們使用一個容器來裝載站點全部的Session。。
只要Sesssion一建立了,就把Session添加到容器裏邊。毫無疑問的,咱們須要監聽Session了。
接着,咱們要作的就是隔一段時間就去掃描一下所有Session,若是有Session長時間沒使用了,咱們就把它從內存中移除。隔一段時間去作某事,這確定是定時器的任務呀。
定時器應該在服務器一啓動的時候,就應該被建立了。所以還須要監聽Context
最後,咱們還要考慮到併發的問題,若是有人同時訪問站點,那麼監聽Session建立的方法就會被併發訪問了。定時器掃描容器的時候,多是獲取不到全部的Session的。
這須要咱們作同步
因而乎,咱們已經有大體的思路了
public class Listener1 implements ServletContextListener, HttpSessionListener { //服務器一啓動,就應該建立容器。咱們使用的是LinkList(涉及到增刪)。容器也應該是線程安全的。 List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>()); //定義一把鎖(Session添加到容器和掃描容器這兩個操做應該同步起來) private Object lock = 1; public void contextInitialized(ServletContextEvent sce) { Timer timer = new Timer(); //執行我想要的任務,0秒延時,每10秒執行一次 timer.schedule(new MyTask(list, lock), 0, 10 * 1000); } public void sessionCreated(HttpSessionEvent se) { //只要Session一建立了,就應該添加到容器中 synchronized (lock) { list.add(se.getSession()); } System.out.println("Session被建立啦"); } public void sessionDestroyed(HttpSessionEvent se) { System.out.println("Session被銷燬啦。"); } public void contextDestroyed(ServletContextEvent sce) { } }
/* * 在任務中應該掃描容器,容器在監聽器上,只能傳遞進來了。 * * 要想獲得在監聽器上的鎖,也只能是傳遞進來 * * */ class MyTask extends TimerTask { private List<HttpSession> sessions; private Object lock; public MyTask(List<HttpSession> sessions, Object lock) { this.sessions = sessions; this.lock = lock; } @Override public void run() { synchronized (lock) { //遍歷容器 for (HttpSession session : sessions) { //只要15秒沒人使用,我就移除它啦 if (System.currentTimeMillis() - session.getLastAccessedTime() > (1000 * 15)) { session.invalidate(); sessions.remove(session); } } } } }
15秒若是Session沒有活躍,那麼就被刪除!
列出全部的在線用戶,後臺管理者擁有踢人的權利,點擊踢人的超連接,該用戶就被註銷了。
首先,怎麼能列出全部的在線用戶呢??通常咱們在線用戶都是用Session來標記的,全部的在線用戶就應該用一個容器來裝載全部的Session。。
咱們監聽Session的是否有屬性添加(監聽Session的屬性有添加、修改、刪除三個方法。若是監聽到Session添加了,那麼這個確定是個在線用戶!)。
裝載Session的容器應該是在Context裏邊的【屬於全站點】,而且容器應該使用Map集合【待會還要經過用戶的名字來把用戶踢了】
思路:
public class KickPerson implements HttpSessionAttributeListener { // Public constructor is required by servlet spec public KickPerson() { } public void attributeAdded(HttpSessionBindingEvent sbe) { //獲得context對象,看看context對象是否有容器裝載Session ServletContext context = sbe.getSession().getServletContext(); //若是沒有,就建立一個唄 Map map = (Map) context.getAttribute("map"); if (map == null) { map = new HashMap(); context.setAttribute("map", map); } //--------------------------------------------------------------------------------------- //獲得Session屬性的值 Object o = sbe.getValue(); //判斷屬性的內容是不是User對象 if (o instanceof User) { User user = (User) o; map.put(user.getUsername(), sbe.getSession()); } } public void attributeRemoved(HttpSessionBindingEvent sbe) { /* This method is called when an attribute is removed from a session. */ } public void attributeReplaced(HttpSessionBindingEvent sbe) { /* This method is invoked when an attibute is replaced in a session. */ } }
<form action="${pageContext.request.contextPath }/LoginServlet" method="post"> 用戶名:<input type="text" name="username"> <input type="submit" value="登錄"> </form>
//獲得傳遞過來的數據 String username = request.getParameter("username"); User user = new User(); user.setUsername(username); //標記該用戶登錄了! request.getSession().setAttribute("user", user); //提供界面,告訴用戶登錄是否成功 request.setAttribute("message", "恭喜你,登錄成功了!"); request.getRequestDispatcher("/message.jsp").forward(request, response);
<c:forEach items="${map}" var="me"> ${me.key} <a href="${pageContext.request.contextPath}/KickPersonServlet?username=${me.key}">踢了他吧</a> <br> </c:forEach>
String username = request.getParameter("username"); //獲得裝載全部的Session的容器 Map map = (Map) this.getServletContext().getAttribute("map"); //經過名字獲得Session HttpSession httpSession = (HttpSession) map.get(username); httpSession.invalidate(); map.remove(username); //摧毀完Session後,返回列出在線用戶頁面 request.getRequestDispatcher("/listUser.jsp").forward(request, response);
使用多個瀏覽器登錄來模擬在線用戶(同一個瀏覽器使用的都是同一個Session)
監聽Seesion的建立和監聽Session屬性的變化有啥區別???
若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章的同窗,能夠關注微信公衆號:Java3y