目录
一:监听域对象创建和销毁
1、什么是监听器?监听器有什么用?
2、Servlet规范中提供了哪些监听器?
3、实现一个监听器的步骤
4、HttpSessionBindingListener
5、HttpSessionIdListener & HttpSessionActivationListener
6、使用监听器统计网站在线人数
①监听器是Servlet规范中的一员。就像Filter一样。Filter也是Servlet规范中的一员。
②在Servlet中,所有的监听器接口都是以“Listener”结尾。
③监听器实际上是Servlet规范留给我们javaweb程序员的特殊时机。
④特殊的时刻如果想执行这段代码,你需要想到使用对应的监听器。
javax.servlet包下:
①ServletContextListener
②ServletContextAttributeListener
③ServletRequestListener
④ServletRequestAttributeListener
jakarta.servlet.http包下:
①HttpSessionListener
②HttpSessionAttributeListener
③HttpSessionBindingListener
④HttpSessionIdListener
⑤HttpSessionActivationListener
这里主要先讲解熟悉的关于三个域对象的监听器:
ServletContext、ServletRequest、HttpSession
(1)以ServletContextListener为例
①第一步:编写一个类实现ServletContextListener接口。并且实现里面的方法。
监听器中的方法不需要程序员手动调用。是发生某个特殊事件之后被服务器调用。
// ServletContext对象被创建的时候调用。
void contextInitialized(ServletContextEvent event)
// ServletContext对象被销毁的时候调用
void contextDestroyed(ServletContextEvent event)
② 第二步:在web.xml文件中对ServletContextListener进行配置,如下:
当然,第二步也可以不使用配置文件,也可以用注解,例如:@WebListener 即可。
com.bjpowernode.javaweb.servlet.MyServletContextListener
注意:所有监听器中的方法都是不需要javaweb程序员调用的,由服务器来负责调用。
什么时候被调用呢?当某个特殊的事件发生(特殊的事件发生其实就是某个时机到了)之后,被web服务器自动调用。
③服务器启动时,ServletContext对象创建, contextInitialized方法执行
服务器关闭时,ServletContext对象销毁, contextDestroyed方法执行
package com.bjpowernode.javaweb.servlet;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.javaweb.servlet
* @Project:JavaWeb
* @name:MyServletContextListener
* @Date:2022/12/4 13:23
*/
// ServletContextListener监听器主要监听的是:ServletContext对象的状态。
public class MyServletContextListener implements ServletContextListener { // 服务器启动时间点
/**
* 监听器中的方法不需要程序员手动调用。是发生某个特殊事件之后被服务器调用。
* @param sce
*/
@Override
public void contextInitialized(ServletContextEvent sce) { // 服务器关闭时间点
// 现在这个特殊的时刻写代码,你写就是了。它会被服务器自动调用。
// 这个方法是在ServletContext对象被创建的时候调用。
System.out.println("ServletContext对象创建了。");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// 现在这个特殊的时刻写代码,你写就是了。它会被服务器自动调用。
// 这个方法是在ServletContext对象被销毁的时候调用。
System.out.println("ServletContext对象被销毁了。");
}
}
(2)以ServletRequestListener为例
④ServletRequest对象是一次请求创建一个request对象,所以服务器启动后:
只要发送一次请求就会调用requestInitialized方法,请求结束立刻会调用requestDestroyed
注:我们直接访问http://localhost:8080/servlet15/ 会报404错误,因为默认会访问index.html,但是我们并没有写;就算如此也会发送出请求,执行这个监听器。
package com.bjpowernode.javaweb.servlet;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.javaweb.servlet
* @Project:JavaWeb
* @name:MyServletRequestListener
* @Date:2022/12/4 13:56
*/
@WebListener
public class MyServletRequestListener implements ServletRequestListener {
// request对象销毁时间点
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("request对象销毁了");
}
// request对象创建时间点
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("request对象初始化了");
}
}
(3)以HttpSessionListener为例
⑤我们都知道在访问jsp时,默认会创建session对象(九大内置对象);先编写一个my.jsp;在访问my.jsp时,会创建session对象,调用 sessionCreated方法;
当退出系统时,我们编写销毁session对象的方法,会调用sessionDestroyed方法。
<%@ page contentType="text/html;charset=UTF-8" %>
Title
my jsp page
退出系统
根据/exit请求,编写销毁session对象的类
package com.bjpowernode.javaweb;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.javaweb
* @Project:JavaWeb
* @name:ExitServlet
* @Date:2022/12/4 14:35
*/
@WebServlet("/exit")
public class ExitServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取session对象
HttpSession session = request.getSession(false);
if (session != null) {
// 销毁session
session.invalidate();
}
}
}
(4)AttributeListener的使用
①我们知道对于域对象都有setAttribute、getAttribute、removeAttribute方法,分别可以向域中存数据、取数据、清除数据;所以对于AttributeListener肯定是和这些处理域中数据有关。
②实际上对于ServletContextAttributeListener、ServletRequestAttributeListener 、HttpSessionAttributeListener这三个对象都有attributeAdded、attributeRemoved、attributeReplaced方法;表示:向域当中存储数据的时候调用、向域当中删除数据的时候调用、向域当中替换数据的时候调用。
③这里以HttpSessionAttributeListener对象为例:
编写HttpSessionAttributeListener监听器
package com.bjpowernode.javaweb.servlet;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.javaweb.servlet
* @Project:JavaWeb
* @name:MyHttpSessionAttributeListener
* @Date:2022/12/4 14:46
*/
@WebListener
public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener {
// 向session域当中存储数据的时候,以下方法被WEB服务器调用。
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
System.out.println("session data add");
}
// 将session域当中存储的数据删除的时候,以下方法被WEB服务器调用。
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
System.out.println("session data remove");
}
// session域当中的某个数据被替换的时候,以下方法被WEB服务器调用。
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
System.out.println("session data replace");
}
}
编写Servlet类用来处理域中的数据
当发送http://localhost:8080/servlet15/session/attribute/test 就能触发上面的监听器;
注意:调用getAttribute方法不会触发,只有setAttribute方法和removeAttribute方法才会触发
package com.bjpowernode.javaweb;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.javaweb
* @Project:JavaWeb
* @name:HttpSessionAttributeServlet
* @Date:2022/12/4 14:49
*/
@WebServlet("/session/attribute/test")
public class HttpSessionAttributeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取session对象
HttpSession session = request.getSession();
// 向session域中存储数据
session.setAttribute("user", "zhangsan");
// 替换,覆盖上面的数据
session.setAttribute("user", "lisi");
// 删除
session.removeAttribute("user");
}
}
(1)前面我们已经讲解了关于域对象的监听器,九个监听器中就已经学习了6个;接下来就先分析一下HttpSessionBindingListener;顾名思义就是关于数据绑定的!
(2)下面就通过一个例子来学习一下HttpSessionBindingListener监听器:
创建一个user1类实现监听器(不需要@WebListener注解),并重写方法
创建一个user2类不实现监听器
对比当数据放入放入域当中,两者会有什么区别:
①普通的user1类实现监听器,并重写监听器中两个方法
package com.bjpowernode.javaweb.bean;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
/**
* 普通的java类。但是它实现了:HttpSessionBindingListener
*/
public class User1 implements HttpSessionBindingListener {
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("绑定数据");
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println("解绑数据");
}
private String usercode;
private String username;
private String password;
public User1(String usercode, String username, String password) {
this.usercode = usercode;
this.username = username;
this.password = password;
}
public User1() {
}
public String getUsercode() {
return usercode;
}
public void setUsercode(String usercode) {
this.usercode = usercode;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
②普通的user2类不实现监听器
package com.bjpowernode.javaweb.bean;
/**
* 普通的java类。
*/
public class User2 {
private String usercode;
private String username;
private String password;
public User2() {
}
public User2(String usercode, String username, String password) {
this.usercode = usercode;
this.username = username;
this.password = password;
}
public String getUsercode() {
return usercode;
}
public void setUsercode(String usercode) {
this.usercode = usercode;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
③编写一个servlet类,把这两种数据都存进去
发现实现监听器的user1类会触发绑定事件!
package com.bjpowernode.javaweb.servlet;
import com.bjpowernode.javaweb.bean.User1;
import com.bjpowernode.javaweb.bean.User2;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/session/bind")
public class HttpSessionBindingListenerServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取session对象
HttpSession session = request.getSession();
// 准备两个对象:User1 User2
User1 user1 = new User1("111", "zhangsan", "123");
User2 user2 = new User2("111", "zhangsan", "123");
// 将user1存储到session域
session.setAttribute("user1", user1);
// 将user2存储到session域
session.setAttribute("user2", user2);
}
}
(3)区分:HttpSessionAttributeListener 和 HttpSessionBindingListener
HttpSessionAttributeListener监听器
①该监听器是需要使用@WebListener注解进行标注的(或者使用web.xml文件进行配置)
②该监听器监听的是session域中数据的变化。只要数据变化,则执行相应的方法;主要监测点在session域对象上。
③监听的是session域,只要把数据放入session域就进行监听!
HttpSessionBindingListener监听器
①该监听器是不需要使用@WebListener进行标注,类直接实现即可。
②假设User类实现了该监听器,那么User对象在被放入session的时候触发bind事件,User对象从session中删除的时候,触发unbind事件。
③假设Customer类没有实现该监听器,那么Customer对象放入session或者从session删除的时候,不会触发bind和unbind事件。
④监听的是普通的java对象,那个类实现了这个监听器,就监听那个类!
总结:
①对于HttpSessionAttributeListener监听的是任何种类的对象,只要放入session域当中就可以;上述user1和user2都可以触发!
②对于HttpSessionBindingListener监听的是特殊的对象,只有实现HttpSessionBindingListener接口的才可以;上述只有user1才能触发!
(4)那么这两个监听器有什么用呢?
我们通过一个简单的业务需求了解一下:
业务1:编写一个功能,记录该网站实时的在线用户的个数我们可以通过服务器端有没有分配session对象,因为一个session代表了一个用户。有一个session就代表有一个用户。如果你采用这种逻辑去实现的话,session有多少个,在线用户就有多少个。这种方式的话:HttpSessionListener够用了。session对象只要新建,则count++,然后将count存储到ServletContext域当中,在页面展示在线人数即可!
业务2:只统计登录的用户的在线数量
用户登录的标志是什么?session中曾经存储过User类型的对象。那么这个时候可以让User类型的对象实现HttpSessionBindingListener监听器,只要User类型对象存储到session域中,则count++,然后将count++存储到ServletContext对象中。页面展示在线人数即可。
这两个监听器不常用,这里只简单了解即可:
(1)HttpSessionIdListener:session的id发生改变的时候,监听器中的唯一一个方法就会被调用。
(2)HttpSessionActivationListener:监听session对象的钝化和活化的。
①钝化:session对象从内存存储到硬盘文件。
②活化:从硬盘文件把session恢复到内存。
实现oa项目中当前登录在线的人数!
(1)什么代表着用户登录了?
session.setAttribute("user", userObj); User类型的对象只要往session中存储过,表示有新用户登录。
(2)什么代表着用户退出了?
session.removeAttribute("user"); User类型的对象从session域中移除了。
或者有可能是session销毁了。(session超时) 。
(3)思考:我们要先思考一下寻访到什么域里面?
统计这个项目当汇总的登录在线人数,一个人一个的(session)以下的域肯定都不行,所以只能使用application域(ServletContext)。
①编写User类实现监听器的接口;然后修改关于所有session.getAttribute的代码
package com.bjpowernode.oa.bean;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.oa.bean
* @Project:JavaWeb
* @name:User
* @Date:2022/12/4 16:18
*/
public class User implements HttpSessionBindingListener {
// 重写两个方法
@Override
public void valueBound(HttpSessionBindingEvent event) {
// 用户登录了
// 相当于User类型的对象向session域当中存放
// 获取application域对象
// event.getSession()获取到session;在调用getServletContext()获取到域对象
ServletContext application = event.getSession().getServletContext();
// 获取到在线人数
Object onlioncount = application.getAttribute("onlioncount");
// 第一个用户登录,里面什么都没有,返回的是一个null
if (onlioncount == null) {
application.setAttribute("onlioncount",1);
}else {
// 直接 onlioncount++有问题,前面是Object类型,强转
Integer count = (Integer)onlioncount;
count++;
// 在存入域当中
application.setAttribute("onlioncount",count);
}
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
// 用户退出了
// 相当于User类型的对象向session域当中删除
// 获取application域对象
ServletContext application = event.getSession().getServletContext();
// 获取域当中的数据,肯定不是空
Integer onlioncount = (Integer) application.getAttribute("onlioncount");
onlioncount--;
// 在存入域当中
application.setAttribute("onlioncount",onlioncount);
}
// 定义属性
private String username;
private String password;
// 构造方法
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
// setter and getter
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
②修改UserServlet类和WelcomeServlet
// 把用户名放进session
session.setAttribute("username",username);
// 改为这样存
User user = new User(username, password);
session.setAttribute("user",user);
③修改过滤器LoginFilter
if("/index.jsp".equals(servletPath) || "/welcome".equals(servletPath) ||
"/dept/login".equals(servletPath) || "/dept/exit".equals(servletPath)
|| (session != null && session.getAttribute("username") != null)){
// username改为user,因为前面存储的名字变了
if("/index.jsp".equals(servletPath) || "/welcome".equals(servletPath) ||
"/dept/login".equals(servletPath) || "/dept/exit".equals(servletPath)
|| (session != null && session.getAttribute("user") != null)){
④修改list.jsp页面
欢迎${username}登录
欢迎${user.username}登录,在线人数${onlioncount}人
⑤最终达到的效果
假如开了两个浏览器,登录了两次
点击退出登录,刷新另一个浏览器的页面