8、JavaEE---过滤器与监听器

目录

(一)什么是过滤器

 1.1过滤器的应用

1.2Filter API 

Filter接口

初始化

实际操作:

销毁 

过滤器的基本结构

过滤器初体验:

FilterChain接口:只定义了一个方法

FilterConfig接口:获取ServletContext和Filter配置参数,定义的方法有: 

(二)过滤器的部署 

web.xml文件中部署

​编辑

使用@WebFilter注解

 (四)常见的过滤模式:

(五)过滤器的使用

简单过滤器

字符编码过滤器

例题:SetCharacterEncodingFilter。

登录验证过滤器

例题:登录验证过滤器

(六)过滤器的执行顺序

例子:过滤器执行顺序

(七)在过滤器中包装请求

包装模式编程方法 

在过滤器中包装响应 

Servlet事件监听概述

(3) Servlet的事件处理三要素:

Servlet API定义了一些监听器接口,具体如下:

监听对象创建与销毁的监听器:

例题:监听对象创建与销毁的监听器

监听对象属性变化的监听器: 

监听对象中的属性变更

(1)attributeAdded()方法:增加属性时调用

(2)attributeRemoved()方法:删除属性时调用

(3)attributeReplaced()方法:重新更改属性时调用

(4)ServletContextAttributeEvent对象的主要方法

(5)HttpSessionAttributeEvent对象的主要方法

 例题:监听对象属性变化的监听器

注册监听器:

监听 session 中某个对象状态变化的监听器 

例题:监听 session 中某个对象状态变化的监听器 

监听建域对象的创与销毁

1、ServletContextListener接口

 例: 监听application对象的创建与销毁

2、HttpSessionListener接口

 例: 用户在线人数统计

监听域对象中的属性变更

(1)attributeAdded()方法:增加属性时调用

(2)attributeRemoved()方法:删除属性时调用

(3)attributeReplaced()方法:重新更改属性时调用

(4)ServletContextAttributeEvent对象的主要方法

(5)HttpSessionAttributeEvent对象的主要方法

监听器的实际应用:统计在线用户


(一)什么是过滤器

在开发一个网站时,可能有这样的需求:某些页面只希望几个特定的用户浏览。对于这样的访问权限的控制,该如何实现呢?Servlet 过滤器(Filter)就可以实现上述需求。

过滤器:客户端与目标资源间的中间层组件,用于拦截客户端的请求与响应信息。

过滤器是一种小巧的、可插入的Web组件,它能够对Web应用程序的前期处理和后期处理进行控制,可以拦截请求和响应,查看、提取或者以某种方式操作正在客户端和服务器之前进行交换的数据。 

 1.1过滤器的应用

  • 对用户请求进行身份认证
  • 修改请求数据的字符集
  • 日志记录和审核
  • 对用户发送的数据进行过滤或者替换
  • 数据压缩
  • 数据加密
  • XML数据的转换

1.2Filter API 

过滤器本质是一个Servlet类,需实现Filter接口。 核心接口:Filter、FilterConfig、FilterChain,位于javax.servlet包中。

Filter接口

初始化

Filter接口与Servlet接口类似,同样都有init()和destroy()方法,doFilter()方法与Servlt中的service()方法类似。

类型:javax.servlet.Filter

过滤器生命周期包括:初始化、处理请求和响应、销毁等阶段。

void init(FilterConfig filterConfig)

作用:初始化过滤器。

调用时机:Servlet容器创建Servlet过滤器实例后调用该方法。

如果为过滤器设置了初始参数,则可以通过 FilterConfig的

getInitParameter(String paramName)方法获得初始参数值。 

实际操作:

void doFilter(ServletRequest request,              

ServletResponse response,FilterChain chain)throws IOException,ServletException

作用:完成实际的过滤操作。

调用时机:当请求访问与过滤器关联的URL时调用该方法。

当 Web 服务器使用 Servlet 对象调用 service()方法处理请求前,发现应用了某个过滤器时,Web 服务器就会自动调用该过滤器的doFilter()方法。在doFilter0方法中有这样一条语句: 

chain.doFilter(request, response);

如果执行了该语句,就会执行下一个过滤器;如果没有下一个过滤器,就返回请求目标程序。如果因为某个原因没有执行“chaindoFilter(request,response);”,则请求就不会继续交给以后的过滤器或请求目标程序,这就是所谓的拦截请求。 

FilterChain参数用于访问后续过滤器。

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		// 前置代码块——在访问资源前执行
		chain.doFilter(request, response);
		// 后置代码块——在访问资源后执行
}

销毁 

public void destroy()

作用:释放Servlet过滤器占用的资源。

Servlet容器在销毁过滤器实例前调用该方法。

过滤器的基本结构

public class FirstFilter implements Filter{
	private FilterConfig filterConfig = null;	
//init()初始化
	public void init(FilterConfig filterConfig) throws  ServletException {
		this.filterConfig = filterConfig;
	}
//doFilter方法
	public void doFilter(ServletRequest request,ServletResponse response, 	FilterChain chain) throws IOException, ServletException {
		//前置代码块
		chain.doFilter(request, response);
		 //后置代码块
	}
//销毁
	public void destroy(){
		this.filterConfig = null;
	}
}

过滤器初体验:

FirstFilter.java:

package listener;
import jakarta.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;

public class FirstFilter  implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //设置响应类型
        servletResponse.setContentType("text/html;charset=utf-8");
        //获取输出对象out
        PrintWriter out = servletResponse.getWriter();
        //在浏览器中输出
        out.println("首先执行过滤器<br>");
        //执行下一个过滤器
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

注意:需要部署Filter后服务器才可以运行Filter对象 

FilterChain接口:只定义了一个方法

void doFilter(ServletRequest request,  ServletResponse response)

过滤器链中的下一个过滤器被调用,如果调用该方法的过滤器是最后一个过滤器,那么目标资源将被调用。

FilterConfig接口:获取ServletContext和Filter配置参数,定义的方法有: 

  • String getFilterName():得到过滤器名字
  • ServletContext getServletContext():得到上下文对象
  • String getInitParameter(String name) :得到指定名称的初始参数
  • Enumeration getInitParameterNames() :得到所有初始参数的名称(类型为Enumeration)

(二)过滤器的部署 

web.xml文件中部署

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
	<!--过滤器的声明-->
	<filter>
		<filter-name>过滤器名</filter-name>
 		<filter-class>过滤器类名</filter-class>
 		<init-param>	//可选(初始化的参数)
   			<param-name>参数名</param-name>
   			<param-value>参数值<param-value>
 		</init-param>
	</filter>
	<!--过滤器的映射-->
 	<filter-mapping>
    		<filter-name>过滤器名</filter-name>
    		<url-pattern>访问路径</url-pattern>
  	</filter-mapping>
</web-app>

以FirstFilter为例:

 <filter>
        <filter-name>FirstFilter</filter-name>
        <filter-class>filter.FirstFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>FirstFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

“/*”代表任何页面或Servlet的请求。

test.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
test.jsp
</body>
</html>

运行结果:

        如果在过滤器初始化时需要读取一些参数的值,则可以在<filter>标记中使用<init-param>子标记设置。例如: 

<filter>
        <filter-name>FirstFilter</filter-name>
        <filter-class>filter.FirstFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>GBK</param-value>
        </init-param>
    </filter>

那么就可以在 Filter 的init0方法中,使用参数 fConfig (FilterConfig 的对象)调用FilterConfig的getInitParameter(String paramName)方法获得参数值。例如: 

  public void init(FilterConfig filterConfig) throws ServletException {
        String en=filterConfig.getInitParameter("encoding");
    }

使用@WebFilter注解

@WebFilter(
		urlPatterns = { "/EncodingFilter" }, 
		initParams = { 
			@WebInitParam(name = "charset", value = "utf-8")
		}
)

属性

类型

描述

filterName

String

指定过滤器的name属性,等价于<filter-name>

value

String[]

该属性等价于urlPatterns属性,但是两者不能同时使用

urlPatterns

String[]

指定过滤器的url匹配模式。等价于<url-pattern>标签

servletNames

String[]

指定过滤器应用于那些Servlet.取值是@WebServlet中的name属性的值,或者是web.xml中<servlet-name>的取值

dispatcherTypes

DispatcherType

指定过滤器的转发模式。取值包括:ASYNC、ERROR、FORWARD、

INCLUDE、REQUEST

initParams

WebInitParam[]

指定一组过滤器初始化参数,等价于<init-param>标签

value 或者urlPatterns 通常是必需的,且二者不能共存,如果同时指定,通常是忽略 value 的取值。 

以FirstFilter为例:

package filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;
import java.io.PrintWriter;

@WebFilter(filterName = "FirstFilter",urlPatterns = {"/*"})
public class FirstFilter  implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String en=filterConfig.getInitParameter("encoding");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //设置响应类型
        servletResponse.setContentType("text/html;charset=utf-8");
        //获取输出对象out
        PrintWriter out = servletResponse.getWriter();
        //在浏览器中输出
        out.println("首先执行过滤器<br>");
        //执行下一个过滤器
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

 (四)常见的过滤模式:

①过滤所有文件 

<filter-mapping> 
	 <filter-name>FilterName</filter-name> 
	 <url-pattern>/*</url-pattern> 
   </filter-mapping> 

②过滤一个或者多个 Servlet(JSP)  

<filter-mapping> 
     	<filter-name>FilterName</filter-name> 
  	 <url-pattern>/PATH1/ServletName1(JSPName1)</url-pattern> 
</filter-mapping> 
<filter-mapping> 
        <filter-name>FilterName</filter-name> 
	<url-pattern>/PATH2/ServletName2(JSPName2)</url-pattern> 
  </filter-mapping> 

③过滤一个或者多个文件目录 

<filter-mapping> 
	<filter-name>FilterName</filter-name> 
	<url-pattern>/PATH1/* </url-pattern> 
</filter-mapping> 

(五)过滤器的使用

过滤器是Servlet 的一种特殊用法,主要用来完成一些通用的操作,例如编码的过滤,判断用户的登录状态等。

简单过滤器

public class SimpleFilter implements Filter {
	public void init(FilterConfig filterConfig) throws ServletException {
	}
	public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {
		response.setContentType("text/html;charset=GB2312");
		PrintWriter out = response.getWriter();
		out.println("<font color=blue>before doFilter()</font><br>");
		chain.doFilter(request, response);
		out.println("<font color=blue>after doFilter()</font><br>");
	}
	public void destroy() {
	}
}

字符编码过滤器

其中一种解决方法是:在获取表单信息之前,使用request 对象调用 setCharacterEncoding(String code)方法设置统一字符编码。使用该方法解决中文乱码问题时,接收参数的每个页面或 Servlet 都需要执行requestsetCharacterEncoding("XXX")语句。为了避免每个页面或Servlet 都编写request.setCharacterEncoding("XXX")语句,可以使用过滤器进行字符编码处理。

例题:SetCharacterEncodingFilter。

@WebFilter(filterName = "CharacterEncodingFilter", urlPatterns = { "/*" }, initParams = { @WebInitParam(name = "encoding", value = "utf-8") })
public class CharacterEncodingFilter implements Filter {
	private static String encoding;
	public void destroy() {	}
	public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {
		request.setCharacterEncoding(encoding);
		response.setContentType("text/html;charset="+encoding);
		chain.doFilter(request, response);
	}
	public void init(FilterConfig fConfig) throws ServletException {
		encoding = fConfig.getInitParameter("encoding");
	}
}

登录验证过滤器

在Web 工程中,某些页面或 Servlet 只有用户登录成功才能访问。如果直接在应用程序每个相关的源代码中判断用户是否登录成功,并不是科学的做法。这时可以实现一个登录验证过滤器,不用在每个相关的源代码中验证用户是否登录成功。

例题:登录验证过滤器

新建一个 Web工程loginValidate,在该Web 工程中至少编写两个JSP页面:login,jsp 与 loginSuccess,jsp,一个 Servlet (由 LoginServlet,java 负责创建)。用户在 login,jsp页面中输入用户名和密码后,提交给 Servlet,在Servlet 中判断用户名和密码是否正确,若正确跳转到loginSuccessjsp,若错误回到loginjsp 页面。但该 Web 工程有另外一个要求:除了访问 loginjsp 页面和 LoginServlet 以外,别的页面或 Servlet 都不能直接访问,必须先登录成功才能访问。在设计这个 Web 工程时,编写了一个登录验证过滤器并在该 Web 工程中使用。页面运行效果如图 8.4所示。

login.jsp主要代码:

<form action="loginServlet" method="post">
  用户名:<input type="text" name="name"><br>
  密码:<input type="password" name="password"><br>
  <input type="submit" value="提交">
  <input type="reset" value="重置">
</form>
loginServlet.java
package servlet;

import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;

import java.io.IOException;

@WebServlet(name = "loginServlet", value = "/loginServlet")
public class loginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String name = request.getParameter("name");
        String password = request.getParameter("password");
        if("filter".equals(name)&&"filter".equals(password)){
            HttpSession session=request.getSession(true);
            session.setAttribute("user",name);
            response.sendRedirect("loginSuccess.jsp");
        }else{
            response.sendRedirect("login.jsp");
        }
    }
}

 loginSuccess.jsp

<%
String name=(String) session.getAttribute("user");
%>

恭喜<%=name%>登录成功!

loginFilter.java:

package filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;
import java.io.PrintWriter;

@WebFilter(filterName = "loginFilter",urlPatterns = {"/*"})
public class loginFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req=(HttpServletRequest) servletRequest;
        HttpServletResponse res=(HttpServletResponse) servletResponse;
        HttpSession session=req.getSession(true);
        res.setContentType("test/html");
        res.setCharacterEncoding("UTF-8");
        PrintWriter out=res.getWriter();
        //得到用户请求的URI
        String req_uri=req.getRequestURI();
        //得到web应用程序的上下文路径
        String ctxPath=req.getContextPath();
        //去除上下文路径,得到剩余部分的路径
        String uri=req_uri.substring(ctxPath.length());
        //登录页面或Servlet不拦截
        if (uri.contains("login.jsp")||uri.contains("loginServlet")){
            filterChain.doFilter(req,res);
        }else {
            //判断用户是否已登陆
            if (null!=session.getAttribute("user")){
                //执行下一个过滤器
                filterChain.doFilter(req,res);
            }else {
                out.println("您没有登录,请先登录!3秒钟后回到登录页面。");
                res.setHeader("refresh","3;url="+ctxPath+"/login.jsp");
                return;
            }
        }
    }

    @Override
    public void destroy() {

    }
}

(六)过滤器的执行顺序

多个过滤器拦截路径相同时,

首先按照<filter-mapping>标记在web.xml中出现的先后顺序执行过滤器,

然后按照过滤器类名的字典顺序执行注解的过滤器

例子:过滤器执行顺序

为了验证过滤器的执行顺序,新建 SecondFilter、ThreeFilter、FourFilter 和 ZFilter。其中,SecondFilter、ThreeFilter和 FourFilter 使用注解的方式部署,它们的urlPatterns 都是"/*";而ZFilter在web.xml中部署,并部署在FirstFilter的前面。

web.xml代码:

<filter>
        <filter-name>ZFilter</filter-name>
        <filter-class>filter.ZFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>ZFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <filter>
        <filter-name>FirstFilter</filter-name>
        <filter-class>filter.FirstFilter</filter-class>

    </filter>
    <filter-mapping>
        <filter-name>FirstFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

SecondFilter.java主要代码

@WebFilter(filterName = "SecondFilter",urlPatterns = {"/*"})

  @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //设置响应类型
        servletResponse.setContentType("text/html;charset=utf-8");
        //获取输出对象out
        PrintWriter out = servletResponse.getWriter();
        //在浏览器中输出
        out.println("首先执行过滤器SecondFilter<br>");
        //执行下一个过滤器
        filterChain.doFilter(servletRequest,servletResponse);
    }

 ThreeFilter主要代码:

@WebFilter(filterName = "ThreeFilter",urlPatterns = {"/*"})
@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //设置响应类型
        servletResponse.setContentType("text/html;charset=utf-8");
        //获取输出对象out
        PrintWriter out = servletResponse.getWriter();
        //在浏览器中输出
        out.println("首先执行过滤器ThreeFilter<br>");
        //执行下一个过滤器
        filterChain.doFilter(servletRequest,servletResponse);
    }

FourFilter.java主要代码:

 @WebFilter(filterName = "FourFilter",urlPatterns = {"/*"})
@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //设置响应类型
        servletResponse.setContentType("text/html;charset=utf-8");
        //获取输出对象out
        PrintWriter out = servletResponse.getWriter();
        //在浏览器中输出
        out.println("首先执行过滤器FourFilter<br>");
        //执行下一个过滤器
        filterChain.doFilter(servletRequest,servletResponse);
    }

ZFilter主要代码:

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //设置响应类型
        servletResponse.setContentType("text/html;charset=utf-8");
        //获取输出对象out
        PrintWriter out = servletResponse.getWriter();
        //在浏览器中输出
        out.println("首先执行过滤器ZFilter<br>");
        //执行下一个过滤器
        filterChain.doFilter(servletRequest,servletResponse);
    }

 结果:

(七)在过滤器中包装请求

  • 在Filter中可以对得到的请求和响应进行再包装。
  • 包装(Wrapper)模式:request和response对象由Web容器直接管理,无法直接重写HttpServletRequest和HttpServletResponse类中的方法,所以用包装模式为两个对象赋予更多的功能(增强)
  • 包装模式以对客户端透明的方式扩展对象的功能,是继承重写的一个替代方案。在不改变类的源代码及原有继承关系的情况下,动态的扩展对象的功能。
  • HttpServletRequestWrapper: request对象的包装模式的实现类,实现了HttpServletRequest接口中的所有方法。

包装模式编程方法 

对request对象进行增强的包装模式编程方法

(1)创建一个类,继承包装实现类HttpServletRequestWrapper。

(2)定义一个构造方法,以增强对象request为参数,在创建包装对象时接收原始的request对象。

(3)重写需要增强的方法,编写request的增强功能。

在过滤器中包装响应 

Servlet API 为response对象提供的包装模式的实现类为HttpServletResponseWrapper,它实现了HttpServletRsponse接口中的所有方法,对response对象进行增强时只需重写涉及到的方法。

Servlet事件监听概述

创建不同功能的监听器类,需要实现不同的监听接口。一个监听器类可以实现多个接口,即可以多种功能的监听器一起工作。

根据监听的对象,可将监听器划分为

  • ServletContext 对象监听器、
  • HttpSession 对象监听器
  • ServletRequest 对象监听器;

根据监听的事件,可将监听器划分为

  • 对象自身的创建和销毁的监听器,
  • 对象中属性的创建、修改和消除的监听器
  • session 中某个对象状态变化的监听器。

(1)问题的提出

前面我们讲述了request、application、session等对象,它们都可以设置、修改属性。那么这些对象在产生、销毁及属性变更时能否监听?

回答是肯定的,这就是Servlet事件监听的相关内容。

(2) Servlet事件监听概述  

(3) Servlet的事件处理三要素:

 事件源:主要讨论request、application、session三类对象或它们的属性

事件:这些对象的产生、销毁及属性变更等

监听器:实现相应监听接口。

Servlet API定义了一些监听器接口,具体如下:

监听对象创建与销毁的监听器:

ServletContextListener:监听application的产生与销毁

       context 代表当前 Web 应用程序。服务器启动时执行 contextInitialized(ServletContextEvent sce)方法。服务器关闭时执行contextDestroyed(ServletContextEvent sce)方法。该监听器可用于启动时获取 web.xml中配置的初始化参数,可作为定时器、加载全局属性对象、创建全局数据库连接、加载缓存信息等。

HttpSessionListener:监听session的产生与销毁

         创建 session 时执行 essionCreated(HttpSessionEvent se)方法。超时或执行 sessioninvalidate() 时执行 sessionDestroyed(HttpSessionEvent se)方法。该监听器可用于统计在线人数、记录访问日志等。

ServletRequestListener:监听request的产生与销毁

        用户每次请求request 都将执行 requestInitialized(ServletRequestEvent sre)方法。request 处理完毕自动销前执行requestDestroyed(ServletRequestEvent sre)方法。该监听器可用于读取 request 参数,记录访问历史。 

例题:监听对象创建与销毁的监听器

在src目录下创建一个名为 listener 的包,并在包中创建一个名为MyObjectListener 的监听器类,该监听器类实现 HttpSessionListener、ServletContext Listener和ServletRequestListener 监听接口。具体代码如下:

MyListener.java

package listener;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;

public class MyListener implements HttpSessionListener, ServletContextListener, ServletRequestListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext servletContext=sce.getServletContext();
        System.out.println("即将启动"+servletContext.getContextPath());
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        ServletContext servletContext=sce.getServletContext();
        System.out.println("即将关闭"+servletContext.getContextPath());
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        HttpServletRequest request=(HttpServletRequest) sre.getServletRequest();
        long time =System.currentTimeMillis()-(Long)request.getAttribute("dateCreate");
        System.out.println(request.getRemoteAddr()+"请求处理结束,用时"+time+"毫秒");
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        HttpServletRequest request=(HttpServletRequest) sre.getServletRequest();
        String uri=request.getRequestURI();
        uri=request.getQueryString()==null?uri:(uri+"?"+request.getQueryString());
        request.setAttribute("dateCreate",System.currentTimeMillis());
        System.out.println("IP"+request.getRemoteAddr()+"请求"+uri);
    }

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        HttpSession session=se.getSession();
        System.out.println("新建一个session,ID为:"+session.getId());
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session=se.getSession();
        System.out.println("销毁一个session,ID为:"+session.getId());
    }
}

监听器需要部署才能在服务器运行!

在web.xml中部署:

  <listener>
        <listener-class>listener.MyListener</listener-class>
    </listener>

 部署监听器后,通过 http://localhost:8080/test_war_exploded/test.jsp运行 test.jsp 来测试监听器,测试结果如图所示。

注:监听器的启动顺序是 web.xml 的配置顺序;

加载顺序是“监听器一过滤器---Servlet” 

监听对象属性变化的监听器: 

监听对象属性变化的监听器分别为 HttpSessionAttributeListener、ServletContextAtributeListener 和 ServletRequestAttributeListener。这3 个监听接口分别用于监听 session、context和 request 的属性变化。

当在被监听对象中添加、更新、移除属性时,将分别执行attributeAdded、attributeReplaced 和attributeRemoved 方法。

  • ServletContextAttributeListener:监听application属性的增删改
  • HttpSessionAttributeListener:监听session属性的增删改
  • ServletRequestAttributeListener:监听request属性的增删改

监听对象中的属性变更

application、session等对象的属性在设置、修改、替换时都会引发相应事件。要监听这些事件,必须实现如下接口:

ServletContextAttributeListener

HttpSessionAttributeListener

这两个接口包含的方法名称相同,只是参数(对应的事件)不同

(1)attributeAdded()方法:增加属性时调用

void attributeAdded(ServletContextAttributeEvent scab)

void attributeAdded(HttpSessionAttributeEvent hsae)

(2)attributeRemoved()方法:删除属性时调用

void attributeRemoved(ServletContextAttributeEvent scab)

void attributeRemoved(HttpSessionAttributeEvent hsae)

(3)attributeReplaced()方法:重新更改属性时调用

void attributeReplaced(ServletContextAttributeEvent scab)

void attributeReplaced(HttpSessionAttributeEvent hsae)

(4)ServletContextAttributeEvent对象的主要方法

String getName() :返回application上变化属性的名称

Object getValue() :返回application上变化属性的值

getServletContext() :返回application

(5)HttpSessionAttributeEvent对象的主要方法

String getName() :返回变化的属性的名称

Object getValue() :返回变化属性的值

HttpSession getSession() :返回session  

 例题:监听对象属性变化的监听器

MyAttributeListener.java
package listener;

import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSessionAttributeListener;
import jakarta.servlet.http.HttpSessionBindingEvent;

public class MyAttributeListener implements HttpSessionAttributeListener {
    @Override
    public void attributeAdded(HttpSessionBindingEvent se) {
        String name=se.getName();
        System.out.println("新建session属性:"+name+",值为:"+se.getValue());
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent se) {
        String name=se.getName();
        System.out.println("删除session属性"+name+",值为:"+se.getValue());
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent se) {
        HttpSession session=se.getSession();
        String name  =se.getName();
        Object oldValue=se.getValue();
        System.out.println("修改session属性:"+name+",原值:"+oldValue+",新值:"+session.getAttribute(name));
    }
}

监听器部署web.xml:

  <listener>
        <listener-class>listener.MyAttributeListener</listener-class>
    </listener>

test.jsp添加如下代码:

<%
session.setAttribute("user","程恒");
session.setAttribute("user","程恒恒");
session.invalidate();
%>

启动服务器,运行localhost:8080/test/test.jsp

 观察控制台输出:

注册监听器:

分两种情况:

 ①使用@WebListener注解

②在web.xml中指定

web.xml配置监听器(与注解二选一)

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
	<listener>
		<listener-class>MyServletContextListener </listener-class>
	</listener>
</web-app>

监听 session 中某个对象状态变化的监听器 

监听绑定到HttpSession域中某个对象状态的事件监听器有 HtpSessionBindingListener和HttpSessionActivationListener。HttpSession 中对象的状态有:绑定一解除绑定,钝化一活化。

  • 绑定:通过setAttribute保存到session对象当中
  • 解除绑定:通过removeAttribue去除绑定。
  • 钝化:将session 对象持久化到存储设备上。
  • 活化:将session 对象从存储设备上恢复。

实现钝化和活化的监听器对象必须实现Serializable 接口,不需要在webxml中部署。

1、HttpSessionBindingListener:当对象被放到 session 中时,执行 valueBound(Http SessionBindingEventevent)方法。当对象被从session 中移除时,执行 valueUnbound(HttpSessionBindingEvent event)方法。对象必须实现该监听接口。

2、HttpSessionActivationListener :中的对象被钝化时,执行sessionWillsessionPassivate(HttpSessionEvent se)方法。当对象被重新加载(活化)时,执行sessionDidActivate(HttpSessionEvent se)方法。对象必须实现该监听接口。

例题:监听 session 中某个对象状态变化的监听器 

 在项目的 listener 包中创建一个名为MySeesionListener 的监听器类,该监听器类实现 HttpSessionBindingListener、HttpSessionActivationListener 和 Serializable 接口。具体代码如下:

MySeesionListener.java
package listener;

import jakarta.servlet.http.*;

import java.io.Serializable;

public class MySeesionListener  implements HttpSessionBindingListener, HttpSessionActivationListener, Serializable {
    private String name;

//即将被钝化到硬盘时
    @Override
    public void sessionWillPassivate(HttpSessionEvent se) {
        HttpSession session =se.getSession();
        System.out.println(this+"即将保存到硬盘。sessionId:"+session.getId());
    }
    //    活化
    @Override
    public void sessionDidActivate(HttpSessionEvent se) {
        HttpSession session =se.getSession();
        System.out.println(this+"已经成功从硬盘中加载。sessionId:"+session.getId());
    }

//绑定到session
    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        HttpSession session=event.getSession();
        String name = event.getName();
        System.out.println(this+"被绑定到session\""+session.getId()+"\"的"+name+"属性上");
    }
//从session中移除后
    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        HttpSession session=event.getSession();
        String name=event.getName();
        System.out.println(this+"被从session\""+session.getId()+"\"的"+name+"属性上移除");
    }

    public String toString(){
        return "MySessionListener("+name+")";

    }
    public String getName(){
        return  name;
    }
    public void setName(String name){
        this.name=name;
    }
}

需要将 test.jsp的代码修改监听器不需要在web.xml 中部署,但为了测试该监听器,如下:

<%
    MySeesionListener msl=new MySeesionListener();
    msl.setName("测试session内监听器");
    //绑定到session
    session.setAttribute("msl", msl);
    //从session中移除
    session.removeAttribute("msl");

%>

运行:http://localhost:8080/test_war_exploded/test.jsp

观察控制台

监听建对象的创与销毁

application、session、request等对象在创建与销毁时都会引发相应事件,可进行监听。

1、ServletContextListener接口

void contextInitialized(ServletContextEvent sce):

web应用程序在初始化开始前调用(在所有过滤器和Servlet初始化之前调用)

void contextDestroyed(ServletContextEvent sce):

当application将要关闭时调用(在所有Servlet和过滤器销毁之后调用)

 例: 监听application对象的创建与销毁

import javax.servlet.*;
@WebListener
public class MyServletContextListener implements ServletContextListener {
	public void contextDestroyed(ServletContextEvent sce) {
		System.out.println("在此处解除数据库连接!");
	}

	public void contextInitialized(ServletContextEvent sce) {
		System.out.println("在此处连接数据库!");
	}
}

2、HttpSessionListener接口

void sessionCreated(HttpSessionEvent se) :session对象产生时调用

void sessionDestroyed(HttpSessionEvent se) :session对象销毁时调用

应用场景:统计在线人数,一个用户登录时产生一个会话,人数增加1,用户离开时退出会话,人数减少1。

问题:如果用户非正常退出,则不会减1。所得结果不一定准确。

 例: 用户在线人数统计

import javax.servlet.*;
import javax.servlet.http.*;
@WebListener
public class CountListener implements HttpSessionListener {
	private int count = 0; //用于统计在线人数的变量

	public void sessionCreated(HttpSessionEvent hse) {
		count++; //session对象创建时count加1
		ServletContext context = hse.getSession().getServletContext();
		context.setAttribute("count", new Integer(count));
	}

	public void sessionDestroyed(HttpSessionEvent hse) {
		count--; //session对象销毁时count减1
		ServletContext context = hse.getSession().getServletContext();
		context.setAttribute("count", new Integer(count));
	}
}

index.jsp文件核心代码 

<H4>当前在线人数为:<%=application.getAttribute("count")%></H4>
<a href="logout.jsp">退出登陆</a>

logout.jsp     

<% session.invalidate();//使session对象失效 %>
<H4>您已经退出本系统</H4>

监听对象中的属性变更

application、session等对象的属性在设置、修改、替换时都会引发相应事件。要监听这些事件,必须实现如下接口:

ServletContextAttributeListener

HttpSessionAttributeListener

这两个接口包含的方法名称相同,只是参数(对应的事件)不同

(1)attributeAdded()方法:增加属性时调用

void attributeAdded(ServletContextAttributeEvent scab)

void attributeAdded(HttpSessionAttributeEvent hsae)

(2)attributeRemoved()方法:删除属性时调用

void attributeRemoved(ServletContextAttributeEvent scab)

void attributeRemoved(HttpSessionAttributeEvent hsae)

(3)attributeReplaced()方法:重新更改属性时调用

void attributeReplaced(ServletContextAttributeEvent scab)

void attributeReplaced(HttpSessionAttributeEvent hsae)

(4)ServletContextAttributeEvent对象的主要方法

String getName() :返回application上变化属性的名称

Object getValue() :返回application上变化属性的值

getServletContext() :返回application

(5)HttpSessionAttributeEvent对象的主要方法

String getName() :返回变化的属性的名称

Object getValue() :返回变化属性的值

HttpSession getSession() :返回session  

监听器的实际应用:统计在线用户

做一个网站,实现在线用户的统计。可以通过ServletContextListener 监听,当Web 应用上下文启动时,在ServletContext中添加一个List用来存放在线的用户名。然后通过HttpSessionAttributeListener监听,当用户登录成功,将用户名设置到Session 中,同时将用户名放到ServletContext的List中。当用户注销会话时,将用户名从应用上下文范围中的List列表中删除。具体步骤如下。

1、jsp页面:

<form action="loginServlet" method="post">
    用户名:<input type="text" name="username">
    <input type="text" value="登录">
</form>

2、创建登录与注销Servlet页面

LoginServlet.java

package servlet;

import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

@WebServlet(name = "LoginServlet", value = "/LoginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        //获取请求参数中的用户名
        String username = request.getParameter("username");
        //向session中添加属性,会触发HttpSessionAttributeListener中的attributeAdded方法
        if (username !=null && !username.equals("")){
            request.getSession().setAttribute(username,username);
        }
        //从应用上下文中获取在线用户列表
        List<String> online=(List<String>) getServletContext().getAttribute("online");
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.println("");
        out.println("<title>用户列表</title>");
        out.println("   ");
        out.println("当前用户是:"+username);
        out.print("<hr><h3>在线用户列表</h3>");
        int size=online==null? 0:online.size();
        for (int i=0;i<size;i++){
            if (i>0){
                out.println("<br>");
            }
            out.println(i+1+"."+online.get(i));
        }
        //注意:要对链接URL进行重写处理
        out.println("<hr/><a href=\""+response.encodeURL("loginoutServlet? id="+username)+"\">注销</a>");
        out.println("   ");
        out.println("");
        out.flush();
        out.close();
    }
}

LoginoutServlet.java

package servlet;

import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

@WebServlet(name = "LoginoutServlet", value = "/LoginoutServlet")
public class LoginoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        String username=request.getParameter("id");//即将注销的用户
        //从应用上下文中获取在线用户名列表
        List<String > online=(List<String>) getServletContext().getAttribute("online");
        online.remove(username);
        //销毁会话
        request.getSession().invalidate();
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.println("");
        out.println(" <title>用户列表</title>");
        out.println("   ");
        out.println(" <h3>在线用户列表</h3>");
        int size=online==null ? 0:online.size();
        for (int i=0;i<size;i++){
            if (i>0){
                out.println("<br>");

            }
            out.println(i+1+"."+online.get(i));
        }
        out.println("<hr><a href=\"index.jsp\"'>主页</a>");
        out.println("   ");
        out.println("");
        out.flush();
        out.close();
    }
}

3、创建监听器

OnlineListener.java
package listener;

import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.http.HttpSessionAttributeListener;
import jakarta.servlet.http.HttpSessionBindingEvent;

import java.sql.Array;
import java.util.ArrayList;
import java.util.List;

public class OnlineListener implements ServletContextListener, HttpSessionAttributeListener {
    private ServletContext application=null;
        //应用上下文初始时会回调的方法
    @Override
    public void contextInitialized(ServletContextEvent sce) {
       //初始化一个application对象
        application = sce.getServletContext();
        //设置一个列表属性,用于保存在线用户对象名
        this.application.setAttribute("online", new ArrayList<String>() );

    }

    //向会话中添加属性时的回调方法
    @Override
    public void attributeAdded(HttpSessionBindingEvent event) {
        //获取用户名列表
        List<String>onlines=(List<String>) this.application.getAttribute("online");
        onlines.add((String) event.getValue());
        //将添加后的列表重新设置到application属性中
        this.application.setAttribute("online",onlines);
    }
}

4、部署监听器

 <listener>
        <listener-class>listener.OnlineListener</listener-class>
    </listener>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值