目录
FilterConfig接口:获取ServletContext和Filter配置参数,定义的方法有:
例题:SetCharacterEncodingFilter。
(2)attributeRemoved()方法:删除属性时调用
(3)attributeReplaced()方法:重新更改属性时调用
(4)ServletContextAttributeEvent对象的主要方法
(5)HttpSessionAttributeEvent对象的主要方法
(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>