四 状态管理
4.1 状态管理概述
4.1.1 为什么需要状态管理
Web应用程序使用的是HTTP协议进行通信,而HTTP协议是“无状态”协议,即服务器一旦响应完客户的请求后,就断开连接,而同一个客户的下一次请求将重新建立网络连接。 说白点,就是HTTP协议只能用于传输数据,不记录状态(就是不记录时谁发送的请求)
服务器端有时需要判断是否为同一个客户发送的请求。比如客户的多次选购商品。因此有必要跟踪同一个客户发送的多次请求。
不然会出现的情况: 如果不跟踪,那么多个客户添加的商品可能在同一个购物车里,某一个客户添加的商品可以在多个购物车里。
4.1.2 什么是状态管理
有的时候,需要将浏览器与服务器之间的多次交互(请求和响应)看成一个整体(同一个用户的多次请求),并将多次交互时所涉及的数据(即状态)保存下来,提供给后续的交互进行数据的管理即状态管理。
-
状态就是数据
-
管理指的是在这个多次交互的过程中对数据的存储、修改、删除。
生活中很多与状态管理类似的案例。如洗车卡记录洗车次数就是很典型的状态管理。
洗车卡可以是一张记录简单次数的标示,车主每次携带卡片洗车后由商家修改,车主即可带走这张记录数据的卡片,商家不会保存任何数据,客户自己负责携带需要维护的数据。
还有一种处理方式就是商家只给客户一个卡号,每次客户来洗车时自己不会记录洗车次数,只要报上卡号,商家就会从系统中找到与卡号对应的数据,修改后仍然是商家保存,客户带走的只是一个卡号这个标示。
以上两种模式都能实现洗车次数的记录,也就是数据的管理,只是各有利弊,程序中的状态管理与这个案例都采用了同样的处理原理。
4.1.3 状态管理的两种常见模式
-
Cookies模式
如果将数据存储在客户端,每次向服务器端发请求时都将存在客户端的数据随着请求发送到服务器端,修改后在发回到客户端保存的这种模式叫做Cookie。
-
Session模式
如果将数据存储在服务器端,并且为这组数据标示一个编号,只将编号发回给客户端。当客户端向服务器发送请求时只需要将这个编号发过来,服务器端按照这个编号找到对应的数据进行管理的这种模式叫做Session。
4.2 Cookies状态管理
4.2.1 什么是cookies
当浏览器向服务器发送请求时,服务器收到请求后,会将少量的数据以set-Cookies消息头的方式绑定到response对象上,然后发送浏览器,浏览器会将这些数据保存下来
当浏览器再次向该服务器发送请求时,会将这些保存的数据以cookies消息头的形式发送给服务端。
4.2.2 如何创建Cookie
Servlet API提供了javax.servlet.http.Cookie这种类型。 其中存储的文本以name-value对的形式进行区分,所以创建Cookie时指定name-value对即可。这个name-value最终是以Set-Cookie这种消息头的形式跟随相应数据包到达客户端,所以要想将数据添加到消息头中需要使用response对象提供的方法。
创建Cookie的代码如下所示:
Cookie c = new Cookie(String name,String value);
response.addCookie( c );
-
name:用于区分不同Cookie的名字
-
value:Cookie的值
案例演示:
1. 编写一个Servlet,实现将用户名及区域两个信息以Cookie的形式保存的功能
2. 程序运行后,通过查看浏览器的辅助功能确认两个Cookie是否保存成功
在浏览器上查看Cookie信息: 右键检查或者F12
4.2.3 如何查询Cookie
当客户端向服务器发出请求时,服务器端可以尝试着从请求数据包的消息头中获取是否携带了Cookie信息。实现这一功能的代码如下:
Cookie[] request.getCookies();
注意:如果没有Cookie信息, 返回null.
由于客户端是可以存放多个Cookie的,所以request提供的获取Cookie的方法的返回值是Cookie数组,如果想进一步获取某一个Cookie信息可以通过遍历数组,分别使用相应的getXXX方法来获取每一个Cookie的name和value。
获取一个Cookie对象的名称或值
String getName();
String getValue();
4.2.4 如何修改Cookie
所谓Cookie的修改,本质是获取到要变更值的Cookie,通过setValue方法将新的数据存入到cookie中,然后由response响应对象发回到客户端,对原有旧值覆盖后即实现了修改。
主要实现代码:
Cookie[] cookies = request.getCookies();
if(cookies!=null){
for(Cookie c : cookies){
String cookieName = c.getName();
if(name.equals(“uname”)){
c.setValue(“Mark”);
}
}
其中response.addCookie(c)是非常重要的语句,如果没有这一行代码,那么就算是使用setValue方法修改了Cookie的值,但是不发回到客户端的话,也不会实现数值的改变。所以只要见到response.addCookie这行代码,即服务器端发回了带有Set-Cookie消息头的信息。
4.2.5 cookie的过期时间
默认情况下,cookie保存到浏览器端的内存中,只要不关闭浏览器,cookie就一直在。只要浏览器已关闭,就消失。 如果想要在关闭浏览器后,还想要将cookie保存一段时间,就可以通过设置cookie过期时间来达到目的。
设置Cookie的过期时间使用如下代码:
void setMaxAge(int seconds);
该方法是Cookie提供的实例方法。参数seconds的单位为秒,但精度不是很高。
seconds > 0 :代表Cookie保存在硬盘上的时长
seconds = 0 : 代表Cookie的生命时长为现在,而这一刻稍纵即逝,所以马上Cookie就等同于过了生存时间,所以会被立即删除。这也是删除Cookie的实现方式。
seconds < 0 :缺省值,浏览器会将Cookie保存在内存中。
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;
import javax.servlet.*;
import javax.servlet.*;
public class AddCookieServlet extends HttpServlet {
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType(
"text/html;charset=utf-8");
PrintWriter out = response.getWriter();
//创建cookie
Cookie c = new Cookie("username", "Lisa");
c.setMaxAge(40); // 设置Cookie在40秒后过期
response.addCookie(c);
Cookie c2 = new Cookie("city", "NewYork");
c2.setMaxAge(40); // 同样设置City Cookie在40秒后过期
response.addCookie(c2);
out.close();
}
}
4.2.6 cookie的路径问题
1)客户端如何保存cookie的路径
客户端存储Cookie之后,并不是针对同一个应用访问任何资源时都自动发送Cookie到服务器端,而是会进行路径的判断。只有符合路径规范的请求才会发送Cookie到服务器端。
客户端在接受Cookie时会为该Cookie记录一个默认路径,这个路径记录的是添加这个Cookie的Web组件的路径。
如,当客户端向 http://localhost:8080/test/file/addCookie.jsp
发送请求时创建了cookie,那么该cookie的路径就是 /test/file.
2)客户端什么时候发送Cookie
只有当访问的地址是Cookie的路径或者其子路径时,浏览器才发送Cookie到服务器端。
如,Cookie的路径是 /test/file,那么如果访问的是 /test/file/a.jsp 或者 /test/file/b/c.jsp时,都会发送Cookie。
如果访问的是 /test/d.jsp,则浏览器不会发送Cookie。
3) 如何设置Cookie的路径
设置Cookie的路径可以使用Cookie的API方法,setPath(String uri)
;
如以下代码就实现了设置Cookie的路径为应用的顶级目录,这样所有资源路径要么与此路径相等,要么是子路径,从而实现了客户端发送任何请求时都会发送Cookie。
Cookie c = new Cookie("uname","jack");
c.setPath("/appName");
response.addCookie(c);
cookie.setPath("/appName/account")
appName/acount/add
appName/acount/del
appName/acount/update
appName/manager/add
appName/manager/del
appName/manager/update
appName/program/add
appName/program/del
appName/program/update
4.2.7 cookie的限制
--cookie可以被用户禁止
--cookie会将状态数据保存到浏览器端,不安全,
--cookie只能保存少量数据,大约在4kb左右
--cookie的个数有限制
--cookie只能保存字符串
4.3 Session状态管理
4.3.1 什么是session
见名知意,是会话的意思。当浏览器访问服务器时,服务器会为每一个浏览器在服务器端的内存中分配空间,单独维护一个Session对象,每个session对象都有一个唯一标识符,叫sessionId。服务器会将sessionId以cookie的方式发给浏览器,浏览器会保存sessionid.
当浏览器再次向服务器发送请求时,会带上sessionId。服务端收到sessionId后,会依据sessionId查找是否有对应的session对象。
4.3.2 如何获取session对象
获取Session对象的方法如下:
HttpSession s = request.getSession(boolean flag);
-
flag = true:先从请求中找找看是否有SID,没有会创建新Session对象,有SID会查找与编号对应的对象,找到匹配的对象则返回,找不到SID对应的对象时则会创建新Session对象。
总结:填写true就一定会得到一个Session对象。要么返回找到的,要么返回新创建的
-
flag = false:不存在SID以及按照SID找不到Session对象时都会返回null,只有根据SID找到对应的对象时会返回具体的Session对象。
总结:填写false时,不会创建新的Session. 要么返回找到的,要么返回null。
如何想要设置flag为true, 可以使用以下重载方法, 相当于设置了flag为true. 提供该方法主要是为了书写代码时更方便,大多数情况下还是希望能够返回一个Session对象的。
HttpSession s = request.getSession();
4.3.3 Session的常用API
Session对象,也是采用name-value对的形式来区分每一组数据。
1)绑定数据的代码:
void session.setAttribute(String name,Object obj)
Session对象可以保存更复杂的对象类型数据了,不像Cookie只能保存字符串。
2)获取绑定数据的代码:
Object obj = session.getAttribute(String name)
3)移除绑定数据的代码
void session.removeAttribute(String name)
4)删除session对象
session.invalidate(): 立即失效
该方法会使得服务器端与该客户端对应的Session对象不再被Session容器管理,进入到垃圾回收的状态。对于这种立即删除Session对象的操作主要应用于不再需要身份识别的情况下,如登出操作。
4.3.4 Session的有效时间(超时时间)
Session会以对象的形式占用服务器端的内存,过多的以及长期的消耗内存会降低服务器端的运行效率,所以Session对象存在于内存中时会有默认的时间限制,一旦Session对象存在的时间超过了这个缺省的时间限制则认为是Session超时,Session会失效,不能再继续访问。
Web服务器缺省的超时时间设置一般是30分钟。
如果用户想要自定义session的时间,有如下两种方式,
1)第一种:声明式 (全局)
在web.xml中添加如下配置
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<!-- 注意:单位是分钟 -->
使用声明式来修改缺省时间,那么该应用创建的所有Session对象的生命周期都会应用这个规定的时间,单位为分钟。
2)第二种:编程式 (局部)
在代码中使用session的api进行设置
session.setMaxInactiveInterval(int seconds) //单位是秒
4.3.5 session的优缺点
优点:
安全(状态数据保存到服务器端)
session绑定的数据类型更加丰富,cookie只能是字符串
session保存的数据量更大,cookie只能是4kb
缺点:
session占服务器的内存。
案例演示:登录验证
需求:如果访问的页面没有经过登录,那么需要立即跳转到登录页面
五 Servlet的特性
5.1 Servlet注解
5.1.1 注解简介
Servlet注解是Java Servlet 3.0版本引入的一种特性,允许在Servlet类上使用注解来配置Servlet,而不需要传统的web.xml配置文件。通过使用@WebServlet注解,可以指定Servlet的名称、URL模式以及其他相关配置。
Servlet注解的优点
-
简化配置:通过注解,可以避免在web.xml文件中进行繁琐的配置,减少代码量,提高开发效率。
-
动态配置:注解允许在运行时动态地添加、修改或删除Servlet的配置,提高了系统的灵活性。
Servlet注解的缺点
-
兼容性问题:虽然Servlet注解提供了很多便利,但在一些较旧的Java EE环境中可能不支持,需要额外的兼容性处理。
-
复杂性增加:对于复杂的配置需求,可能需要结合web.xml文件使用,增加了配置的复杂性。
5.1.2 注解应用
-
注解要写在每个Servlet组件的类上面
-
格式:@WebServlet(…)
-
常用属性:value和urlPatterns一般是必须的,但是二者不能共存,若同时指定,一般自动忽略value。
-
注意事项:
- urlPatterns和value只需要配置一个即可。 是数组形式,使用逗号隔开,元素是字符串类型,每个元素都是以/开头。
-如果只有一个元素,{}可以省略。
-urlPatterns依然支持精确匹配,通配符匹配,后缀匹配
- 每个属性都是使用逗号隔开。
属性名 | 类名 | 属性描述 |
---|---|---|
name | String | 指定servlet的name属性,等价于<servlet-name> ,若没有指定,则默认是类的全限定名 |
value | String[] | 等价于urlPatterns,两者不能共存,只有一个值时,可以省略{} |
urlPatterns | String[] | 指定一组servlet的url的匹配模式,等价于<url-pattern> |
loadOnStartup | int | 指定servlet的加载顺序,等价于<load-on-startup> |
initParams | WebinitParams[] | 指定一组初始化参数,等价于<init-params> |
asyncSupported | boolean | 声明servlet是否支持异步操作模式,等价于<async-supported> |
displayName | String | servlet的显示名,等价于<display-name> |
description | String | servlet的描述信息,等价于<description> |
案例演示
@WebServlet(name = "m1",
urlPatterns = {"/login.lr", "/register.lr", "/getCode.lr"},
initParams = {@WebInitParam(name = "company", value = "佑才"),
@WebInitParam(name = "school", value = "南开大学")},
loadOnStartup = 1)
public class WebServletDemo extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("--------Servlet注解的应用--------");
ServletConfig servletConfig = getServletConfig();
String company = servletConfig.getInitParameter("company");
String school = servletConfig.getInitParameter("school");
System.out.println("公司:" + company);
System.out.println("学校:" + school);
}
}
5.2 ServletContext的应用
5.2.1 ServletContext的简介
ServletContext:Servlet上下文对象,它是一个接口。一个web工程,只有一个ServletContext对象实例(单例),同一个web应用程序中,所有的Servlet组件共享同一个ServletContext对象。web服务器启动时创建,web服务器关闭时销毁,也就是说,只要容器不关闭或者应用不卸载,servlet上下文对象就一直存在。
ServletContext一般有以下四个用途:
1.获取web.xml中配置的上下文参数context-param
2.获取当前的工程路径(项目名称),格式:/工程路径
3.获取工程部署后在服务器硬盘上的绝对路径
4.像Map一样存取数据
获取ServletContext对象的方式
1. GenericServlet提供了getServletContext()方法。(推荐)
2. HttpServletRequest提供了getServletContext()方法。(推荐)
3. ServletConfig提供了getServletContext()方法。
4. HttpSession提供了getServletContext()方法。
5.2.2 案例演示:
全局参数,在web.xml中配置
<context-param>
<param-name>deptname</param-name>
<param-value>开发部门</param-value>
</context-param>
可以有多个<context-param>
ServletContext对象:
1. 在整个项目中是唯一的,只有一个。(单例)
2. 所有的Servlet组件(包括JSP)都可以拿到这个对象
3. 既然是唯一的,我们就可以使用该对象来存储一个共享数据。 此时:ServletContext对象也可以称之为域对象
域:就是用来存储数据的。
4. 也可以使用上下文对象,获取全局变量context-param,以及项目名,以及项目在服务器
import javax.servlet.ServletContext;
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 java.io.IOException;
@WebServlet(value = "/testServletContext")
public class ServletContextDemo extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {
// 获取ServletContext对象
ServletContext sct1 = this.getServletContext();//从GenericServlet中继承过来的
ServletContext sct2 = request.getServletContext(); //从请求对象上获取
ServletContext sct3 = this.getServletConfig().getServletContext(); //从ServletConfig上获取
ServletContext sct4 = request.getSession().getServletContext();
//判断四个变量指向的是不是同一个对象
System.out.println(sct1 == sct2);
System.out.println(sct1 == sct3);
System.out.println(sct1 == sct4);
//获取全局参数: 全局参数在web.xml中配置
String deptname = sct3.getInitParameter("a");
System.out.println("deptname: " + deptname);
//获取当前项目的名称
String contextPath = sct1.getContextPath();
String contextPath1 = request.getContextPath();//从请求对象身上也可以获取到
System.out.println(contextPath+" "+contextPath1);
//获取当前项目在服务器端的硬盘上的绝对路径
String realPath = sct3.getRealPath(""); // 传入一个字符串 ,可以是空字符串,可以是斜杠
System.out.println(realPath);
}
}
5.3 过滤器接口
5.3.1 什么是过滤器
Servlet规范中的三大接口:Servlet接口,Filter接口、Listener接口。
过滤器接口,是Servlet2.3版本以来,定义的一种小型的,可插拔的Web组件,可以用来拦截和处理Servlet容器的请求和响应过程。以便查看,提取或以某种方式操作正在客户端与服务器端之间交换的数据。
应用场景如下:
1. 对请求的数据是否包含敏感词进行提前筛选过滤,没有必要在下一层(业务层)进行处理。
2. 用户没有进行登录,应该跳往登录页面等操作。
插拔的理解:
方便增加或减少某个功能模块,需要添加过滤就多部署一个class修改一下web.xml文件,需要减少某个功能只要删除web.xml中对应的声明即可。
方便修改处理逻辑。当把一些功能独立到某个模块时,如果逻辑变了,只修改这一个文件并更新就可以实现功能的改变,而不用修改每一个使用这个插件的组件。
5.3.2 如何编写过滤器
编写过滤器遵循下列步骤:
-
编写一个实现了Filter接口的类
-
实现Filter接口的三个方法,过滤逻辑在doFilter方法中实现
-
在Web程序中注册过滤器
-
把过滤器和Web应用一起打包部署
5.3.3 过滤器的执行流程
单个过滤器的执行流程,如下图所示:
1. 客户端发来请求后,不会直接将请求送达Servlet,而是先走过滤器1的doFilter方法中的code1。
2. 当遇到chain.doFilter()方法时,就会调用Servlet组件的service()方法,执行里面的逻辑代码。
3. service()方法执行结束后并不会立即将响应返回给客户端,而是回到过滤器1的doFilter()方法中code2部分,如果该部分有代码就会执行,执行结束后才会将response对象返回给客户端。从流程中可以看到,过滤器不仅仅对Servlet的执行前起到过滤作用,对于执行后同样有过滤效果。所以,过滤器是对request和response的检查。
多个过滤器的执行流程,如下图所示:
过滤器1的doFilter的code1 ,然后 过滤器2的doFilter的code1 ,然后再试 service()方法 ,再然后是过滤器2的doFilter的code2 ,之后是 过滤器1的doFilter的code2 ,最后返回给客户端
在这个动作的传递过程中一定要写 chain.doFilter()。
多个过滤器的优先级
在一个Web应用中,可以有多个过滤器,它们的优先级由位于web.xml文件中的声明顺序决定,具体是按照<filter-mapping>
的顺序来决定的。如下代码所示,filter1和filter2都已经注册,执行顺序是filter2 ,然后 filter1 。
<filter>
<filter-name>filter1</filter-name>
<filter-class/>
</filter>
<filter>
<filter-name>filter2</filter-name>
<filter-class/>
</filter>
<filter-mapping>
<filter-name>filter2</filter-name>
<url-pattern>/comment2</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/comment1</url-pattern>
</filter-mapping>
5.3.4 过滤器的初始化参数
容器启动之后,会创建过滤器实例。通过init方法来完成过滤器的初始化。初始化时可以添加一些配置,提升动态性。而这些参数通过在web.xml文件中的<init-param>
以name-value对的形式存在。
读取这些name-value对需要使用FilterConfig对象,从web.xml文件到FilterConfig对象的过程由容器完成,并通过参数传入到init方法之中,只需要设定一些成员对象保存数值就可以在doFilter方法中使用。
初始化参数配置方法如下:
<filter>
<filter-name>filter1</filter-name>
<filter-class>web.CommentFilter1</filter-class>
<!-- 初始化参数 -->
<init-param>
<param-name>illegalStr</param-name>
<param-value>xxx</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/comment</url-pattern>
</filter-mapping>
读取初始化参数使用如下代码:
public class CommentFilter implements Filter{
FilterConfig config = null;
public void init(FilterConfig arg0) throws ServletException {
config = arg0;
}
public void doFilter(ServletRequest arg0,ServletResponse arg1, FilterChain arg2)throws IOException, ServletException {
String illegalStr = config.getInitParameter("illegalStr");
// … …
}
public void destroy() {
// … …
}
}
5.3.5 案例演示:敏感词过滤
CommentFilter1类
package com.shuilidianli.filter;
import javax.servlet.*;
import java.io.IOException;
public class CommentFilter1 implements Filter {
//添加全局变量FilterConfig
FilterConfig config = null;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//给全局变量config赋值
config = filterConfig;
}
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf8");
//使用全局变量config获取一些敏感词
String word1 = config.getInitParameter("word1");
System.out.println("word1:"+word1);
//获取评论内容
String comment = request.getParameter("comment");
if(comment.contains(word1)){
//转发到comment.jsp页面进行提醒
request.setAttribute("message","您输入了敏感词"+word1);
request.getRequestDispatcher("comment.jsp").forward(request,response);
}else{
//继续向下执行
chain.doFilter(request,response);
}
}
@Override
public void destroy() {
}
}
CommentFilter2类
package com.shuilidianli.filter;
import javax.servlet.*;
import java.io.IOException;
public class CommentFilter2 implements Filter {
//添加全局变量FilterConfig
FilterConfig config = null;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//给全局变量config赋值
config = filterConfig;
}
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf8");
//使用全局变量config获取一些敏感词
String word2 = config.getInitParameter("word2");
//获取评论内容
String comment = request.getParameter("comment");
if(comment.contains(word2)){
//转发到comment.jsp页面进行提醒
request.setAttribute("message","您输入了敏感词"+word2);
request.getRequestDispatcher("comment.jsp").forward(request,response);
}else{
//继续向下执行
chain.doFilter(request,response);
}
}
@Override
public void destroy() {
}
}
CommentServlet类
package com.shuilidianli.web;
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 java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/toComment")
public class CommentServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
//处理中文乱码问题
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf8");
String comment = request.getParameter("comment");
PrintWriter pw = response.getWriter();
pw.println("评论通过,您的评论内容如下:<br>");
pw.println("<p>"+comment+"</p>");
pw.close();
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter>
<filter-name>f1</filter-name>
<filter-class>com.shuilidianli.filter.CommentFilter1</filter-class>
<init-param>
<param-name>word1</param-name>
<param-value>尼玛</param-value>
</init-param>
</filter>
<filter>
<filter-name>f2</filter-name>
<filter-class>com.shuilidianli.filter.CommentFilter2</filter-class>
<init-param>
<param-name>word2</param-name>
<param-value>qusi</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>f2</filter-name>
<url-pattern>/toComment</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>f1</filter-name>
<url-pattern>/toComment</url-pattern>
</filter-mapping>
</web-app>
5.4 监听器
5.4.1 监听器的概念
Servlet规范中的三大接口,除了Servlet接口,Filter接口,剩下的就是==Listener接口==了。
监听器采用了设计模式中的观察者模式,监听器本身是观察者,被监听的对象是被观察者。当被监听对象的状态发生改变时,就会通知观察者,也就是监听器,监听器在收到通知后可以做出相应的处理逻辑。
Servlet监听器是Servlet规范中定义的一种特殊类,用于监听ServletContext、HttpSession和ServletRequest等域对象的创建与销毁事件,以及监听这些域对象中属性发生修改的事件。
5.4.2 监听器的分类
在Servlet2.5版本以来,定义了以下三类8种监听器:
第一大类:监听 Session、request、context 这三个对象的创建与销毁的:
-
HttpSessionListener
-
ServletContextListener
-
ServletRequestListener
第二大类:监听对象属性变化的:
-
HttpSessionAttributeListener
-
ServletContextAttributeListener
-
ServletRequestAttributeListener
第三类:监听Session 内的对象的:
-
HttpSessionBindingListener
-
HttpSessionActivationListener
-
与上面六类不同,这两类 Listener 监听的是Session 内的对象,而非 Session 本身,不需要在 web.xml中配置。
5.5 监听器案例演示
5.5.1 ServletContextListener
package com.servlet.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 自定义一个类,实现Servlet上下文的监听器接口
* 作用:可以监听Servlet上下文的创建和销毁时机。
*/
public class MyServletContextListener implements ServletContextListener {
/**
*
* @param sce: 当监听器被通知时,事件对象自创创建,并赋值给方法的形参
*
* 监听器的方法:都是在发生事件时,自动调用的
*/
@Override
public void contextInitialized(ServletContextEvent sce) {
//获取时间,格式化成yyyy-MM-dd HH:mm:ss
Date now = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String timeStr = sdf.format(now);
//从监听事件对象上,获取Servlet上下文对象,然后从其身上获取当前项目名称
String appName = sce.getServletContext().getContextPath();
System.out.println("项目"+appName+"的ServletContext对象在"+timeStr+"被创建");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
//获取时间,格式化成yyyy-MM-dd HH:mm:ss
Date now = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String timeStr = sdf.format(now);
//从监听事件对象上,获取Servlet上下文对象,然后从其身上获取当前项目名称
String appName = sce.getServletContext().getContextPath();
System.out.println("项目"+appName+"的ServletContext对象在"+timeStr+"被销毁");
}
}
在web.xml中注册监听, 放在servlet标签之前
<listener>
<listener-class>com.servlet.listener.MyServletContextListener</listener-class>
</listener>
5.5.2 测试
5.5.3 HttpSessionListener
package com.servlet.listener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* 自定义一个类型,实现HttpSession监听器接口,用于监听Session对象的创建和销毁
*
* 对应的方法:
* sessionCreated(): Session对象创建时,被主动调用
* sessionDestroyed(): Session对象销毁时时,被主动调用
*/
public class MySessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("---创建了一个session对象----");
}
/**
* session销毁的方式:
* 1. 程序员主动调用session.invalidate()
* 2. session超时
* @param se
*/
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("---销毁了一个session对象----");
}
}
在web.xml中注册监听, 放在servlet标签之前
<listener>
<listener-class>com.servlet.listener.MySessionListener</listener-class>
</listener>
5.5.4 测试
5.5.5 ServletRequestListener
package com.servlet.listener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
/**
* ServletRequestListener接口,用来监听request对象的创建和销毁
*
* 方法如下:
* requestInitialized(): 当request对象被创建时,自动调用
* requestDestroyed(): 当request对象被销毁时,自动调用
*/
public class MyRequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("------request对象被销毁了----------");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("------request对象被创建出来了----------");
}
}
5.5.6 测试
5.5.7 属性监听器
ServletContextAttributeListener/ServletRequestAttributeListener/HttpSessionAttributeListener的三个监听接口,功能相似。
package com.servlet.listener;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
/**
* 监听Servlet上下文对象的属性的。
* 当绑定属性,修改属性,移除属性时,会被监听到
*/
public class MyContextAttributeListener implements ServletContextAttributeListener {
/**
* 添加属性时被触发
* @param scae
*/
@Override
public void attributeAdded(ServletContextAttributeEvent scae) {
System.out.println("添加属性时被触发");
}
/**
* 移除属性时被触发
* @param scae
*/
@Override
public void attributeRemoved(ServletContextAttributeEvent scae) {
System.out.println("移除属性时被触发");
}
/**
* 替换修改属性时被触发
* @param scae
*/
@Override
public void attributeReplaced(ServletContextAttributeEvent scae) {
System.out.println("替换修改属性时被触发");
}
}
5.5.8 测试
访问三个"/sctAdd","/sctReplace","/sctRemove"请求资源路径,进行测试即可
package com.servlet.listener;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextListener;
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 java.io.IOException;
@WebServlet(urlPatterns = {"/sctAdd","/sctReplace","/sctRemove"})
public class MyContextAttributeListenerTest extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext sct = getServletContext();
String uri = req.getRequestURI();
if (uri.contains("sctAdd")) {
sct.setAttribute("username","zhangsan");
} else if (uri.contains("sctReplace")) {
sct.setAttribute("username","lisi");
}else if (uri.contains("sctRemove")) {
sct.removeAttribute("username");
}
}
}
5.5.9 HttpSessionBindingListener
package com.servlet.vo;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
/**
* 自定义一个类,如果你想监听这个类的实例是否被绑定到session上时,
* 就可以实现HttpSessionBindingListener接口
*
* 或者你想监听这个类的实例从session身上移除了,也可以实现该接口
*/
public class Student implements HttpSessionBindingListener {
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("--valueBound--");
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println("---valueUnbound---");
}
}
5.5.10 测试
测试绑定
Student student = new Student();
session.setAttribute("s1",student);
测试移除
session.removeAttribute("s1");
5.5.11 HttpSessionActivationListener
package com.servlet.vo;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;
import java.io.Serializable;
/**
* 自定义的类型:如果该类的实例对象,随着HttpSession对象一起序列化或者反序列时,你想要监听到这种状态,那就实现
* 该接口
*/
public class Teacher implements Serializable, HttpSessionActivationListener {
/**
* 对象被钝化时触发: 钝化,就是序列化的意思
* @param se
*/
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
System.out.println("----对象被钝化了-----");
}
/**
* 对象被活化时触发: 活化,就是反序列化的意思
*
*
* 使用Session获取对象时,应该提前钝化一下,然后再读取钝化后文件里的数据
* 就会激活活化方法,如果第二次再次读取,不会触发该方法,原因是第一次读取的
* 已经保存到session里。第二次是从session中获取的,并不是从文件中获取的。
* @param se
*/
@Override
public void sessionDidActivate(HttpSessionEvent se) {
System.out.println("----对象被活化了-----");
}
}
5.5.12 测试
添加配置文件:与WEB-INF并列创建一个文件夹META-INF, 创建一个context.xml配置文件,放入下面配置信息
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<!-- classname: 配置用于持久化Session的类型。
saveOnRestart: true表示在重启时保存
maxIdleSwap:session中的对象多久不使用就钝化,单位:分钟 -->
<Manager className="org.apache.catalina.session.PersistentManager" saveOnRestart="true" maxIdleSwap="1">
<!-- classname: 用于持久化的文件流,
directory:钝化文件的位置 -->
<Store className="org.apache.catalina.session.FileStore" directory="D:/bb" />
</Manager>
</Context>
默认钝化的位置:
%CATALINA_BASE%\work\Catalina\localhost\项目名\下
测试钝化:绑定对象到Session上,1分钟后查看控制台输出内容
Teacher t1 = new Teacher();
session.setAttribute("t1",t1);
测试活化: 获取session对象即可,不用获取session里的t1。当然必须是同一个session对象