第8章 会话_JSP_过滤器_监听器_Ajax
8.1 会话
8.1.1 会话管理概述
1、为什么需要会话管理
HTTP是无状态协议:
- 无状态就是不保存状态,即无状态协议(stateless),HTTP协议自身不对请求和响应之间的通信状态进行保存,也就是说,在HTTP协议这个级别,协议对于发送过的请求或者响应都不做持久化处理;
- 简单理解:浏览器发送请求,服务器接收并响应,但是服务器不记录请求来自哪个浏览器,服务器没记录浏览器的特征,就是客户端的状态;
举例:张三去一家饭馆点了几道菜,觉得味道不错,第二天又去了,对老板说,还点上次的那几道菜。
- 无状态:老板没有记录张三是否来过,更没有记录上次他点了那些菜,张三只能重新再点一遍;
- 有状态:老板把每次来吃饭的用户都做好记录,查阅一下之前的记录,查到了张三之前的菜单,直接下单;
2、会话管理实现的手段
Cookie和Session配合解决:
- cookie是在客户端保留少量数据的技术,主要通过响应头向客户端响应一些客户端要保留的信息;
- session是在服务端保留更多数据的技术,主要通过服务端HttpSession对象保存一些和客户端相关的信息;
- cookie和session配合记录请求状态;
举例:张三去银行办业务。
- 张三第一次去某个银行办业务,银行会为张三开户(Session),并向张三发放一张银行卡(Cookie);
- 张三后面每次去银行,就可以携带之间的银行卡(Cookie),银行根据银行卡找到之前张三的账户(Session);
8.1.2 Cookie
1、Cookie概述
Cookie是一种客户端会话技术,Cookie由服务端产生,它是服务器存放在浏览器的一小份数据,浏览器以后每次访问该服务器的时候都会将这小份数据携带到服务器去:
- 服务端创建Cookie,将Cookie放入响应对象中,Tomcat容器将Cookie转化为set-cookie响应头,响应给客户端;
- 客户端在收到Cookie的响应头时,在下次请求该服务的资源时,会以cookie请求头的形式携带之前收到的Cookie;
- Cookie是一种键值对格式的数据,从tomcat8.5开始可以保存中文,但是不推荐;
- 由于Cookie是存储于客户端的数据,比较容易暴露,一般不存储一些敏感或者影响安全的数据;
原理图:
应用场景举例:
- 记录用户名;
- 保存电影播放进度;
- … …
2、Cookie的使用
servletA向响应中增加Cookie:
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 创建Cookie
Cookie cookie1 =new Cookie("c1","c1_message");
Cookie cookie2 =new Cookie("c2","c2_message");
// 将cookie放入响应对象
resp.addCookie(cookie1);
resp.addCookie(cookie2);
}
}
servletB从请求中读取Cookie:
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求中的cookie
Cookie[] cookies = req.getCookies();
//迭代cookies数组
if (null != cookies && cookies.length!= 0) {
for (Cookie cookie : cookies) {
System.out.println(cookie.getName()+":"+cookie.getValue());
}
}
}
}
3、Cookie的时效性
默认情况下Cookie的有效期是一次会话范围内,我们可以通过Cookie的setMaxAge()方法让Cookie持久化保存到浏览器上。
- 会话级Cookie:
- 服务器端并没有明确指定Cookie的存在时间;
- 在浏览器端,Cookie数据存在于内存中;
- 只要浏览器还开着,Cookie数据就一直都在;
- 浏览器关闭,内存中的Cookie数据就会被释放;
- 持久化Cookie:
- 服务器端明确设置了Cookie的存在时间;
- 在浏览器端,Cookie数据会被保存到硬盘上;
- Cookie在硬盘上存在的时间根据服务器端限定的时间来管控,不受浏览器关闭的影响;
- 持久化Cookie到达了预设的时间会被释放;
cookie.setMaxAge(int expiry)参数单位是秒,表示cookie的持久化时间,如果设置参数为0,表示将浏览器中保存的该cookie删除。
- servletA设置一个Cookie为持久化cookie。
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 创建Cookie
Cookie cookie1 =new Cookie("c1","c1_message");
cookie1.setMaxAge(60);
Cookie cookie2 =new Cookie("c2","c2_message");
// 将cookie放入响应对象
resp.addCookie(cookie1);
resp.addCookie(cookie2);
}
}
- servletB接收Cookie,浏览器中间发生一次重启再请求servletB测试。
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求中的cookie
Cookie[] cookies = req.getCookies();
//迭代cookies数组
if (null != cookies && cookies.length!= 0) {
for (Cookie cookie : cookies) {
System.out.println(cookie.getName()+":"+cookie.getValue());
}
}
}
}
4、 Cookie的提交路径
访问互联网资源时不需要每次都需要把所有Cookie带上。访问不同的资源时,可以携带不同的cookie,可以通过Cookie的setPath(String path) 对Cookie的路径进行设置。
- 从ServletA中获取cookie。
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 创建Cookie
Cookie cookie1 =new Cookie("c1","c1_message");
// 设置cookie的提交路径
cookie1.setPath("/web03_war_exploded/servletB");
Cookie cookie2 =new Cookie("c2","c2_message");
// 将cookie放入响应对象
resp.addCookie(cookie1);
resp.addCookie(cookie2);
}
}
- 向ServletB请求时携带携带了 c1。
- 向其他资源请求时就不携带c1了。
8.1.3 Session
1、HttpSession概述
HttpSession是一种保留更多信息在服务端的一种技术,服务器会为每一个客户端开辟一块内存空间,即session对象.。客户端在发送请求时,都可以使用自己的session。这样服务端就可以通过session来记录某个客户端的状态了。
- 服务端在为客户端创建session时,会同时将session对象的id,即JSESSIONID以Cookie的形式放入响应对象;
- 后端创建完session后,客户端会收到一个特殊的Cookie,叫做JSESSIONID;
- 客户端下一次请求时携带JSESSIONID,后端收到后,根据JSESSIONID找到对应的session对象;
- 通过该机制,服务端通过session就可以存储一些专门针对某个客户端的信息了;
- session也是域对象(后续详细讲解);
应用场景:
-
记录用户的登录状态:
用户登录后,将用户的账号等敏感信息存入session;
-
记录用户操作的历史:
例如记录用户的访问痕迹,用户的购物车信息等临时性的信息;
2、HttpSession的使用
用户提交form表单到ServletA,携带用户名,ServletA获取session 将用户名存到session,用户再请求其他任意Servlet,获取之间存储的用户。
- 定义表单页,提交用户名。
<form action="servletA" method="post">
用户名:
<input type="text" name="username">
<input type="submit" value="提交">
</form>
- 定义ServletA,将用户名存入session。
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求中的参数
String username = req.getParameter("username");
// 获取session对象
HttpSession session = req.getSession();
// 获取Session的ID
String jSessionId = session.getId();
System.out.println(jSessionId);
// 判断session是不是新创建的session
boolean isNew = session.isNew();
System.out.println(isNew);
// 向session对象中存入数据
session.setAttribute("username",username);
}
}
- 响应中收到了一个JSESSIONID的Cookie。
- 定义其他Servlet,从session中读取用户名。
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取session对象
HttpSession session = req.getSession();
// 获取Session的ID
String jSessionId = session.getId();
System.out.println(jSessionId);
// 判断session是不是新创建的session
boolean isNew = session.isNew();
System.out.println(isNew);
// 从session中取出数据
String username = (String)session.getAttribute("username");
System.out.println(username);
}
}
- 请求中携带了一个JSESSIONID的Cookie。
getSession方法的处理逻辑:
4、HttpSession时效性
为什么要设置session的时效?
- 用户量很大之后,Session对象相应的也要创建很多。如果一味创建不释放,那么服务器端的内存迟早要被耗尽;
- 客户端关闭行为无法被服务端直接侦测,或者客户端较长时间不操作也经常出现,类似这些的情况,就需要对session的时限进行设置了;
默认的session最大闲置时间(两次使用同一个session中的间隔时间) 在Tomcat/conf/web.xml配置为30分钟。
我们可以自己在当前项目的web.xml对最大闲置时间进行重新设定。
也可以通过HttpSession的API 对最大闲置时间进行设定。
// 设置最大闲置时间
session.setMaxInactiveInterval(60);
也可以直接让session失效。
// 直接让session失效
session.invalidate();
8.1.4 三大域对象
1、域对象概述
域对象:一些用于存储数据和传递数据的对象。传递数据不同的范围,我们称之为不同的域。不同的域对象代表不同的域,共享数据的范围也不同。
- web项目中,我们一定要熟练使用的域对象分别是 :请求域、会话域、应用域;
- 请求域对象是HttpServletRequest ,传递数据的范围是一次请求之内及请求转发;
- 会话域对象是HttpSession,传递数据的范围是一次会话之内,可以跨多个请求;
- 应用域对象是ServletContext,传递数据的范围是本应用之内,可以跨多个会话;
生活举例:热水壶摆放位置不同。使用的范围就不同。
- 摆在张三工位下,就只有张三一个人能用;
- 摆在办公室的公共区,办公室内的所有人都可以用;
- 摆在楼层的走廊区,该楼层的所有人都可以用;
三大域对象的数据作用范围图解:
- 请求域
- 会话域
- 应用域
- 所有域在一起
2、域对象的使用
域对象的API:
API | 功能 |
---|---|
void setAttribute(String name,String value) | 向域对象中添加/修改数据 |
Object getAttribute(String name); | 从域对象中获取数据 |
void removeAttribute(String name); | 移除域对象中的数据 |
API测试:
- ServletA向三大域中放入数据。
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 向请求域中放入数据
req.setAttribute("request","request-message");
//req.getRequestDispatcher("servletB").forward(req,resp);
// 向会话域中放入数据
HttpSession session = req.getSession();
session.setAttribute("session","session-message");
// 向应用域中放入数据
ServletContext application = getServletContext();
application.setAttribute("application","application-message");
}
}
- ServletB从三大于中取出数据。
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 从请求域中获取数据
String reqMessage =(String)req.getAttribute("request");
System.out.println(reqMessage);
// 从会话域中获取数据
HttpSession session = req.getSession();
String sessionMessage =(String)session.getAttribute("session");
System.out.println(sessionMessage);
// 从应用域中获取数据
ServletContext application = getServletContext();
String applicationMessage =(String)application.getAttribute("application");
System.out.println(applicationMessage);
}
}
- 请求转发时,请求域可以传递数据。
请求域内一般放本次请求业务有关的数据,如:查询到的所有的部门信息
; - 同一个会话内,不用请求转发,会话域可以传递数据。
会话域内一般放本次会话的客户端有关的数据,如:当前客户端登录的用户
; - 同一个APP内,不同的客户端,应用域可以传递数据。
应用域内一般放本程序应用有关的数据 如:Spring框架的IOC容器
;
8.2 案例开发-快递管理-第三期
8.2.1 登录状态会话保存
定义常量存储状态类
public class MyConstants {
/**
* session存储用户key
*/
public static final String SESSION_USER_KEY = "user";
}
修改UserLoginController层的登录方法: 进行账号信息保存
@WebServlet("/user/login")
public class UserLoginController extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.设置编码格式
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
//2.获取请求参数(username,password)
String username = req.getParameter("username");
String password = req.getParameter("password");
//3.调用业务逻辑
UserService userService = new UserServiceImpl();
SysUser sysUser = userService.login(username,password);
//4.响应前端数据
if (sysUser == null) {
System.out.println("账号或者密码错误!");
req.getRequestDispatcher("/login.html").forward(req,resp);
}else{
//将登录用户数据存储到session 作用: 1.保留登录状态,用于查询登录快递信息 2.登录保护凭证
req.getSession().setAttribute(MyConstants.SESSION_USER_KEY,sysUser);
System.out.println("登录成功,登录页面!");
req.getRequestDispatcher("/index.html").forward(req,resp);
}
}
}
8.2.2 当前登录用户快递列表展示
-
修改html页面中快递管理跳转页面(所有页面)
注意: 需要修改除了login.html以外的所有页面
<li><a href="/delivery/data/list"><i class="nav"></i><em>快递管理</em></a></li>
-
导入快递记录数据库脚本
# 快递公司表 CREATE TABLE sys_company ( id INT AUTO_INCREMENT PRIMARY KEY, company_name VARCHAR(255) NOT NULL ); INSERT INTO sys_company (company_name) VALUES ('顺丰快递'), ('圆通速递'), ('中通快递'), ('申通快递'), ('韵达快递'); # 快递记录管理 CREATE TABLE sys_delivery ( id INT AUTO_INCREMENT PRIMARY KEY, delivery_name VARCHAR(10) COMMENT '收件人姓名', user_id INT COMMENT '关联用户id', company_id INT COMMENT '关联公司id', phone VARCHAR(11) COMMENT '手机号', address VARCHAR(100) COMMENT '收获地址', send_time DATE COMMENT '送达日期', state INT DEFAULT 0 COMMENT '签收状态 0 未签收 1已签收' ); INSERT INTO sys_delivery (delivery_name, user_id, company_id, phone, address, send_time,state) VALUES ('二狗子', 1, 1, '13812345678', '北京市朝阳区', '2024-07-11',1), ('二狗子', 1, 2, '13987654321', '上海市浦东新区', '2024-07-12',0), ('二狗子', 1, 3, '13611112222', '广州市天河区','2024-07-13', 0), ('二狗子', 1, 4, '13544443333', '深圳市南山区','2024-07-14', 1), ('二狗子', 1, 1, '13755556666', '成都市武侯区','2024-07-15', 0), ('二狗子', 1, 2, '13377778888', '重庆市渝中区','2024-08-03', 1), ('二狗子', 1, 3, '13299990000', '武汉市江汉区', '2024-08-02',0), ('驴蛋蛋', 2, 4, '13122223333', '西安市雁塔区', '2024-08-11',1), ('驴蛋蛋', 2, 1, '13033334444', '杭州市西湖区', '2024-07-11',0), ('驴蛋蛋', 2, 2, '13466667777', '南京市鼓楼区', '2024-07-22',1);
-
准备快递记录和公司信息pojo类
@Data public class Company { private int id; private String companyName; } @Data // 自动生成 Getter、Setter、equals、hashCode 和 toString 方法 @NoArgsConstructor // 自动生成无参构造方法 public class Delivery { private int id; private String deliveryName; private int userId; private int companyId; private String phone; private String address; private String sendTime; private int state; private Company company; //关联公司对象 }
-
准备CompanyDao和CompanyDaoImpl
public interface CompanyDao { /** * 根据id查询公司详情 * @return */ List<Company> queryList(); } public class CompanyDaoImpl extends BaseDao implements CompanyDao { @Override public List<Company> queryList() { String sql = "select id , company_name companyName from sys_company;"; List<Company> list = baseQuery(Company.class, sql); return list; } }
-
准备CompanyService和CompanyServiceImpl
public interface CompanyService { /** * 查询快速公司数据 * 用于给快递记录进行对象赋值! * @return */ List<Company> findCompanyList(); } public class CompanyServiceImpl implements CompanyService { @Override public List<Company> findCompanyList() { CompanyDao companyDao = new CompanyDaoImpl(); List<Company> companies = companyDao.queryList(); return companies; } }
-
准备DeliveryDao和DeliveryDaoImpl
public interface DeliveryDao { /** * 查询当前登录用户对应的所有快递记录 * @param userId * @return */ List<Delivery> queryByUserId(Integer userId); } public class DeliveryDaoImpl extends BaseDao implements DeliveryDao { @Override public List<Delivery> queryByUserId(Integer userId) { String sql = "select id , delivery_name deliveryName , user_id userId , company_id companyId , " + "phone , address , state ,send_time sendTime from sys_delivery where user_id = ? ;"; return baseQuery(Delivery.class,sql,userId); } }
-
准备DeliveryService和DeliveryServiceImpl
public interface DeliveryService { /** * 查询当前用户的所有快递记录 * @param userId * @return */ List<Delivery> findList(Integer userId); } public class DeliveryServiceImpl implements DeliveryService { @Override public List<Delivery> findList(Integer userId) { //1.检查当前用户编号 if (userId == null){return null;} //todo: 此处涉及多表处理,查询快递和快递对应的公司!我们利用循环处理! //2.查询当前用户对应的快递记录集合 DeliveryDao deliveryDao = new DeliveryDaoImpl(); List<Delivery> deliveries = deliveryDao.queryByUserId(userId); //3.遍历处理快递记录,关联对应的快递公司信息 if (deliveries.size() > 0){ CompanyService companyService = new CompanyServiceImpl(); List<Company> companies = companyService.findCompanyList(); //stream操作改成Map格式,避免循环中进行数据库查询 // 使用 Lambda + Stream 将 List<Company> 转换为 Map<Integer, Company> Map<Integer, Company> companyMap = companies.stream() .collect(Collectors.toMap(Company::getId, company -> company)); for (Delivery delivery : deliveries) { Company company = companyMap.get(delivery.getCompanyId()); delivery.setCompany(company); } } return deliveries; } }
-
修改MyConstants常量类
public class MyConstants { /** * session存储用户key */ public static final String SESSION_USER_KEY = "user"; /** * 存储快递记录的key */ public static final String DELIVERIES_DATA_KEY = "deliveries"; }
-
准备DeliveryListController
@WebServlet("/data/list") public class DeliveryListController extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1.设置编码格式 req.setCharacterEncoding("utf-8"); resp.setContentType("text/html;charset=utf-8"); //2.获取登录用户ID SysUser sysUser = (SysUser) req.getSession().getAttribute(MyConstants.SESSION_USER_KEY); Integer userId = sysUser.getId(); //3.调用业务逻辑 DeliveryService deliveryService = new DeliveryServiceImpl(); List<Delivery> deliveries = deliveryService.findList(userId); //4.响应前端数据 req.setAttribute(MyConstants.DELIVERIES_DATA_KEY,deliveries); req.getRequestDispatcher("/list.html").forward(req,resp); } }
-
转发后list.html页面资源地址错误
<link href="/delivery/css/public.css" rel="stylesheet" type="text/css"> <script type="text/javascript" src="/delivery/js/jquery.min.js"></script> <script type="text/javascript" src="/delivery/js/global.js"></script> <script type="text/javascript" src="/delivery/js/jquery.autotextarea.js"></script> </head> <body> <div id="dcWrap"> <div id="dcHead"> <div id="head"> <div class="logo"><a href="index.html"><img width="100px" height="25px" src="/delivery/images/dclogo.gif"
8.3 JSP
8.3.1 JSP初识
JSP(Java Server Pages)是一种动态网页开发技术,它是由 Sun 公司提出的一种基于 Java 技术的 Web 页面制作技术,可以在 HTML 文件中嵌入 Java 代码,使得生成动态内容的编写更加简单。
JSP 最主要的作用是生成动态页面。它允许将 Java 代码嵌入到 HTML 页面中,以便使用 Java 进行数据库查询、处理表单数据和生成 HTML 等动态内容。另外,JSP 还可以与 Servlet 结合使用,实现更加复杂的 Web 应用程序开发。
JSP 的主要特点包括:
- 简单:JSP 通过将 Java 代码嵌入到 HTML 页面中,使得生成动态内容的编写更加简单。
- 高效:JSP 首次运行时会被转换为 Servlet,然后编译为字节码,从而可以启用 Just-in-Time(JIT)编译器,实现更高效的运行。
- 多样化:JSP 支持多种标准标签库,包括 JSTL(JavaServer Pages 标准标签库)、EL(表达式语言)等,可以帮助开发人员更加方便的处理常见的 Web 开发需求。
总之,JSP 是一种简单高效、多样化的动态网页开发技术,它可以方便地生成动态页面和与 Servlet 结合使用,是 Java Web 开发中常用的技术之一。
8.3.2 EL表达式
EL(Expression Language) 是为了使JSP写起来更加简单。表达式语言的灵感来自于 ECMAScript 和 XPath 表达式语言,它提供了在 JSP 中简化表达式的方法,让Jsp的代码更加简化。
语法结构:${expression}
EL获取变量数据的方法很简单,例如:${username}。它的意思是取出某一范围中名称为username的变量。
因为我们并没有指定哪一个范围的username,所以它会依序从Page、Request、Session、Application范围查找。由于它只会从作用域中获取数据,所以被展示的数据一定要先存储到作用域,才可以使用EL表达式获取
EL 提供".“和”[ ]“两种运算符来取数据。如果被访问的数据是对象时,可以使用”.“操作符获取属性,如果是数组,则可以使用”[]"获取数据。
案例代码:
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %>
<%@ page import="com.atguigu.schedule.pojo.SysUser" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
//1.变量测试
int age=10;
String name="张三丰";
SysUser user=new SysUser();
user.setUsername("李四");
String[] strs={"珍珍","爱爱","莲莲"};
request.setAttribute("strings",strs);
request.setAttribute("u1",user);
request.setAttribute("uage",age);
request.setAttribute("uname",name);
//2.内置对象测试
request.setAttribute("reqkey","request的数据");
session.setAttribute("sessionkey","session的数据");
application.setAttribute("applicationkey","application的数据");
%>
<h1>学生姓名:${uname},年龄:${uage}</h1>
<h1>用户姓名:${u1.username}</h1>
<h1>猪八戒喜欢:${strings[0]},${strings[1]},${strings[2]}</h1>
request中的数据:${reqkey}<br>
session中的数据:${sessionkey}<br>
application中的数据:${applicationkey}<br>
</body>
</html>
注意: EL表达式只负责取数据,无法循环遍历数据
8.3.3 JSTL标签库
JSP 标准标签库(JSP Standard Tag Library,JSTL)是一个实现 Web应用程序中常见的通用功能的定制标记库集,这些功能包括迭代和条件判断、数据管理格式化、XML 操作以及数据库访问。
JSTL使用步骤:
1.添加依赖包(这里的依赖包会因为tomcat版本以及配置环境的区别而有变化)
将两个 jar 文件拷贝到 /WEB-INF/lib/ 下
2.jsp页面中引入核心标签库
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3.页面中使用c:forEach标签循环数据
<c:forEach>标签有如下属性:
属性 | 描述 | 是否必要 | 默认值 |
---|---|---|---|
items | 要被循环的信息 | 否 | 无 |
begin | 开始的元素(0=第一个元素,1=第二个元素) | 否 | 0 |
end | 最后一个元素(0=第一个元素,1=第二个元素) | 否 | Last element |
step | 每一次迭代的步长 | 否 | 1 |
var | 代表当前条目的变量名称 | 否 | 无 |
varStatus | 代表循环状态的变量名称 | 否 | 无 |
<c:if>标签有如下属性:
属性 | 描述 | 是否必要 | 默认值 |
---|---|---|---|
test | 条件 | 是 | 无 |
var | 用于存储条件结果的变量 | 否 | 无 |
scope | var属性的作用域 | 否 | page |
案例代码:
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
List list=new ArrayList<>();
list.add("张三1");
list.add("李四2");
list.add("王五3");
list.add("赵六4");
request.setAttribute("ulist",list); //必须先存再取
%>
<table width="300px" border="1" cellspacing="0">
<tr>
<td>序号</td>
<td>姓名</td>
</tr>
<c:if test="${ulist.size()>0}">
<!--循环取出列表数据-->
<c:forEach items="${ulist}" var="uname" varStatus="i">
<tr>
<td>${i.count}</td>
<td>${uname}</td>
</tr>
</c:forEach>
</c:if>
<c:if test="${ulist.size()==0}">
<tr>
<td colspan="2">无数据</td>
</tr>
</c:if>
</table>
<!--从1循环到100-->
<c:forEach begin="1" end="100" var="num">${num}</c:forEach>
</body>
</html>
8.4 案例开发-快递管理-第四期
8.4.1 动态展示快递记录信息
-
项目导入jstl相关的依赖
-
将list.html页面改成list.jsp动态页面
注意:修改时可以新建一个jsp页面,将html页面的代码复制到新的jsp页面中,也可以将html页面的后缀改成jsp
jsp页面必须要包含头信息:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-
修改DeliveryListController资源跳转地址
@WebServlet("/data/list") public class DeliveryListController extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1.设置编码格式 req.setCharacterEncoding("utf-8"); resp.setContentType("text/html;charset=utf-8"); //2.获取登录用户ID SysUser sysUser = (SysUser) req.getSession().getAttribute(MyConstants.SESSION_USER_KEY); Integer userId = sysUser.getId(); //3.调用业务逻辑 DeliveryService deliveryService = new DeliveryServiceImpl(); List<Delivery> deliveries = deliveryService.findList(userId); //4.响应前端数据 req.setAttribute(MyConstants.DELIVERIES_DATA_KEY,deliveries); //此处将list.html改为list.jsp即可 req.getRequestDispatcher("/list.jsp").forward(req,resp); } }
-
修改list.jsp页面,进行快递记录展示
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>快递管理系统</title> <meta name="Copyright" content="Douco Design." /> <link href="/delivery/css/public.css" rel="stylesheet" type="text/css"> <script type="text/javascript" src="/delivery/js/jquery.min.js"></script> <script type="text/javascript" src="/delivery/js/global.js"></script> <script type="text/javascript" src="/delivery/js/jquery.autotextarea.js"></script> </head> <body> <div id="dcWrap"> <div id="dcHead"> <div id="head"> <div class="logo"><a href="index.html"><img width="100px" height="25px" src="/delivery/images/dclogo.gif" alt="logo"></a></div> <div class="nav"> <ul class="navRight"> <li class="M noLeft"><a href="JavaScript:void(0);">您好,admin</a> <div class="drop mUser"> <a href="password.html">修改密码</a> </div> </li> <li class="noRight"><a href="login.html">退出</a></li> </ul> </div> </div> </div> <!-- dcHead 结束 --> <div id="dcLeft"><div id="menu"> <ul class="top"> <li><a href="index.html"><i class="home"></i><em>管理首页</em></a></li> </ul> <ul> <li><a href="password.html"><i class="system"></i><em>修改密码</em></a></li> <li><a href="/delivery/data/list"><i class="nav"></i><em>快递管理</em></a></li> </ul> </div></div> <div id="dcMain"> <!-- 当前位置 --> <div id="urHere">快递管理系统<b>></b><strong>快递列表</strong> </div> <div class="mainBox" style="height:auto!important;height:550px;min-height:550px;"> <h3><a href="add.html" class="actionBtn add">添加快递</a>快递列表</h3> <!-- 本阶段不实现!! <div class="filter"> <form action="#" method="post"> 物流公司 <select name="cat_id"> <option value>请选择</option> <option value="1"> 申通快递</option> <option value="2"> 圆通快递</option> <option value="2"> 韵达快递</option> </select> 收件人手机号 <input name="keyword" type="text" class="inpMain" value size="20" /> 签收状态 <select name="cat_id"> <option value>请选择</option> <option value="1">已签收</option> <option value="2"> 未签收</option> </select> <input name="submit" class="btnGray" type="submit" value="筛选" /> </form> </div> --> <div id="list"> <table width="100%" border="0" cellpadding="8" cellspacing="0" class="tableBasic"> <tr> <th width="40" align="center">编号</th> <th width="80" align="center">物流公司</th> <th width="80" align="center">收件人</th> <th width="80" align="center">手机号</th> <th width="80" align="center">送达日期</th> <th align="center">地址</th> <th width="80" align="center">签收状态</th> <th width="80" align="center">操作</th> </tr> <c:forEach items="${deliveries}" var="delivery" > <tr> <td align="center">${delivery.id}</td> <td>${delivery.company.companyName}</td> <td align="center">${delivery.deliveryName}</td> <td align="center">${delivery.phone}</td> <td align="center">${delivery.sendTime}</td> <td align="center">${delivery.address}</td> <td align="center">${delivery.state == 0 ?'未签收':'已签收'}</td> <td align="center"> <a href="#">编辑</a> | <a href="#">删除</a> </td> </tr> </c:forEach> </table> </div> <div class="clear"></div> <div class="pager"> <a href="article.php?page=1">第一页</a> <a href="article.php?page=1"> 上一页</a> <a href="article.php?page=1">下一页</a> <a href="article.php?page=1">最末页</a></div> </div> </div> <div class="clear"></div> <div id="dcFooter"> <div id="footer"> <div class="line"></div> <ul> 版权所有 © 2024-2025 尚硅谷教育,并保留所有权利。 </ul> </div> </div><!-- dcFooter 结束 --> <div class="clear"></div> </div> <script type="text/javascript"> onload = function() { document.forms['action'].reset(); } function douAction() { var frm = document.forms['action']; frm.elements['new_cat_id'].style.display = frm.elements['action'].value == 'category_move' ? '' : 'none'; } </script> </body> </html>
8.4.2 展示用户信息
展示登录的用户nickname,主要涉及页面: index.html / list.html / add.html / edit.html / password.html
将以上页面改成动态页面 .jsp,修改后缀名和添加固定头
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
需要修改登录成功(UserLoginController)跳转页面index.html -> index.jsp
@WebServlet("/user/login")
public class UserLoginController extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.设置编码格式
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
//2.获取请求参数(username,password)
String username = req.getParameter("username");
String password = req.getParameter("password");
//3.调用业务逻辑
UserService userService = new UserServiceImpl();
SysUser sysUser = userService.login(username,password);
//4.响应前端数据
if (sysUser == null) {
System.out.println("账号或者密码错误!");
req.getRequestDispatcher("/login.html").forward(req,resp);
}else{
//将登录用户数据存储到session 作用: 1.保留登录状态,用于查询登录快递信息 2.登录保护凭证
req.getSession().setAttribute(MyConstants.SESSION_USER_KEY,sysUser);
req.getRequestDispatcher("/index.jsp").forward(req,resp);
}
}
}
修改代码如下:
<li class="M noLeft"><a href="JavaScript:void(0);">您好,${sessionScope.user.nickname}</a>
8.4.3 修改密码功能
-
修改各个页面跳转到password.jsp的地址
-
修改password.jsp页面表单
<form action="/delivery/user/change" method="post" onsubmit="return checkForm()"> <table width="100%" border="0" cellpadding="8" cellspacing="0" class="tableBasic"> <tr> <td width="100" align="right">原始密码</td> <td> <input id="oldPwdInput" type="password" name="password" size="40" class="inpMain" onchange="checkOldPwd()" /> <span id="oldPwdMsg"></span> </td> </tr> <tr> <td width="100" align="right">新密码</td> <td> <input id="newPwdInput" type="password" size="40" class="inpMain" onchange="checkNewPwd()" name="newpassword" /> <span id="newPwdMsg"></span> </td> </tr> <tr> <td align="right">确认密码</td> <td> <input id="rePwdInput" type="password" name="repassowrd" size="40" class="inpMain" onchange="checkReUserPwd()"/> <span id="rePwdMsg"></span> </td> </tr> <tr> <td></td> <td> <input type="submit" name="submit" class="btn" value="提交" /> </td> </tr> </table> </form>
-
编写修改密码Controller
@WebServlet("/user/change") public class UserChangeController extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1.设置编码格式 req.setCharacterEncoding("utf-8"); resp.setContentType("text/html;charset=utf-8"); //2.获取请求参数(username,password) String oldPassword = req.getParameter("password"); String newPassword = req.getParameter("newPassword"); //获取登录用户id SysUser sysUser = (SysUser) req.getSession().getAttribute(MyConstants.SESSION_USER_KEY); Integer userId = sysUser.getId(); //3.调用业务逻辑 UserService userService = new UserServiceImpl(); boolean result = userService.changePassword(userId, oldPassword, newPassword); //4.响应前端数据 if (!result) { System.out.println("密码修改失败!"); req.setAttribute("errorMsg","密码修改失败!!"); req.getRequestDispatcher("/password.jsp").forward(req,resp); }else{ //将登录用户数据存储到session 作用: 1.保留登录状态,用于查询登录快递信息 2.登录保护凭证 req.setAttribute("successMsg","密码修改成功!!"); req.getRequestDispatcher("/password.jsp").forward(req,resp); } } }
-
编写修改密码Service
public interface UserService { /** * 修改用户密码 * @param userId 登录的用户id * @param oldPassword 原密码 * @param newPassword 新密码 * @return 密码是否修改成功 */ boolean changePassword(Integer userId,String oldPassword,String newPassword); } public class UserServiceImpl implements UserService { @Override public boolean changePassword(Integer userId, String oldPassword, String newPassword) { //1.校验三者不为null if (userId == null || StringUtils.isEmpty(oldPassword) || StringUtils.isEmpty(newPassword)) { //核心数据为null return false; } UserDao userDao = new UserDaoImpl(); //2.根据用户id查询用户对象 SysUser sysUser = userDao.queryById(userId); //3.校验原密码是否对应 oldPassword = MD5Util.encrypt(oldPassword); if (!oldPassword.equals(sysUser.getPassword())){ //原密码不匹配 return false; } //4.修改新密码 //秘密加密处理 newPassword = MD5Util.encrypt(newPassword); userDao.updatePassword(userId,newPassword); return true; } }
-
编写修改密码Dao
public interface UserDao { /** * 查询用户id查询用户对象 * @param id 用户id * @return */ SysUser queryById(Integer id); int updatePassword(Integer id, String newPassword); } public class UserDaoImpl extends BaseDao implements UserDao { @Override public SysUser queryById(Integer id) { String sql ="select id,username,password,nickname from sys_user where id = ? ;"; List<SysUser> list = baseQuery(SysUser.class,sql, id); SysUser sysUser = list.size()>0?list.get(0):null; return sysUser; } @Override public int updatePassword(Integer id, String newPassword) { String sql = "update sys_user set password = ? where id = ? ; "; return baseUpdate(sql,newPassword,id); } }
-
修改状态进行回显password.jsp
<tr> <td></td> <td> <input type="submit" name="submit" class="btn" value="提交" /> </td> </tr> </table> <font color="red">${errorMsg}</font> <font color="green">${successMsg}</font> </form>
8.4.3 用户退出功能
-
通用页面修改和提取
-
提取资源部分(css/js引入)
resource.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <link href="/delivery/css/public.css" rel="stylesheet" type="text/css"> <script type="text/javascript" src="/delivery/js/jquery.min.js"></script> <script type="text/javascript" src="/delivery/js/global.js"></script> <script type="text/javascript" src="/delivery/js/jquery.autotextarea.js"></script>
-
提取内容head(登录状态)
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <div id="dcHead"> <div id="head"> <div class="logo"><a href="/delivery/index.jsp"><img width="100px" height="25px" src="/delivery/images/dclogo.gif" alt="logo"></a></div> <div class="nav"> <ul class="navRight"> <li class="M noLeft"><a href="JavaScript:void(0);">您好,${sessionScope.user.nickname}</a> <div class="drop mUser"> <a href="/delivery/password.jsp">修改密码</a> </div> </li> <li class="noRight"><a href="/delivery/user/logout">退出</a></li> </ul> </div> </div> </div>
-
提取内容菜单(左侧菜单)
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <div id="dcLeft"> <div id="menu"> <ul class="top"> <li><a href="/delivery/index.jsp"><i class="home"></i><em>管理首页</em></a></li> </ul> <ul> <li><a href="/delivery/password.jsp"><i class="system"></i><em>修改密码</em></a></li> <li><a href="/delivery/data/list"><i class="nav"></i><em>快递管理</em></a></li> </ul> </div> </div>
-
所有页面引用
注意:删除原有位置,使用jsp:include技术动态引入,不要引错位置!
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>快递管理系统</title> <meta name="Copyright" content="Douco Design." /> <jsp:include page="resource.jsp" /> </head> <body> <div id="dcWrap"> <jsp:include page="head.jsp" /> <!-- dcHead 结束 --> <jsp:include page="menu.jsp" />
-
-
编写UserLogoutController类
@WebServlet("/user/logout") public class UserLogoutController extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //session失效 req.getSession().invalidate(); resp.sendRedirect(req.getContextPath() + "/login.html"); } }
8.5 过滤器
8.5.1 过滤器概述
Filter,即过滤器,是JAVAEE技术规范之一,作为目标资源的请求进行过滤的一套技术规范,是Java Web项目中
最为实用的技术之一
。
- Filter接口定义了过滤器的开发规范,所有的过滤器都要实现该接口;
- Filter的工作位置是项目中所有目标资源之前,容器在创建HttpServletRequest和HttpServletResponse对象后,会先调用Filter的doFilter方法;
- Filter的doFilter方法可以控制请求是否继续,如果放行,则请求继续,如果拒绝,则请求到此为止,由过滤器本身做出响应;
- Filter不仅可以对请求做出过滤,也可以在目标资源做出响应前,对响应再次进行处理;
- Filter是GOF中责任链模式的典型案例;
- Filter的常用应用包括但不限于:登录权限检查、解决网站乱码、过滤敏感字符、日志记录、性能分析… …;
生活举例: 公司前台,停车场安保,地铁验票闸机… …
- 公司前台对来访人员进行审核,如果是游客则拒绝进入公司,如果是客户则放行 。客户离开时提醒客户不要遗忘物品;
- 停车场保安对来访车辆进行控制,如果没有车位拒绝进入。如果有车位,发放停车卡并放行,车辆离开时收取请车费;
- 地铁验票闸机在人员进入之前检查票,没票拒绝进入,有票验票后放行,人员离开时同样验票;
过滤器工作位置图解:
Filter接口API:
- 源码
package jakarta.servlet;
import java.io.IOException;
public interface Filter {
default public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;
default public void destroy() {
}
}
- API目标
API | 目标 |
---|---|
default public void init(FilterConfig filterConfig) | 初始化方法,由容器调用并传入初始配置信息filterConfig对象 |
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | 过滤方法,核心方法,过滤请求,决定是否放行,响应之前的其他处理等都在该方法中 |
default public void destroy() | 销毁方法,容器在回收过滤器对象之前调用的方法 |
8.5.2 过滤器使用
目标:开发一个日志记录过滤器。
- 用户请求到达目标资源之前,记录用户的请求资源路径;
- 响应之前记录本次请求目标资源运算的耗时;
- 可以选择将日志记录进入文件,为了方便测试,这里将日志直接在控制台打印;
定义一个过滤器类,编写功能代码:
public class LoggingFilter implements Filter {
private SimpleDateFormat dateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 参数父转子
HttpServletRequest request =(HttpServletRequest) servletRequest;
HttpServletResponse response =(HttpServletResponse) servletResponse;
// 拼接日志文本
String requestURI = request.getRequestURI();
String time = dateFormat.format(new Date());
String beforeLogging =requestURI+"在"+time+"被请求了";
// 打印日志
System.out.println(beforeLogging);
// 获取系统时间
long t1 = System.currentTimeMillis();
// 放行请求
filterChain.doFilter(request,response);
// 获取系统时间
long t2 = System.currentTimeMillis();
// 拼接日志文本
String afterLogging =requestURI+"在"+time+"的请求耗时:"+(t2-t1)+"毫秒";
// 打印日志
System.out.println(afterLogging);
}
}
- 说明
- doFilter方法中的请求和响应对象是以父接口的形式声明的,实际传入的实参就是HttpServletRequest和HttpServletResponse子接口级别的,可以安全强转;
- filterChain.doFilter(request,response); 这行代码的功能是放行请求,如果没有这一行代码,则请求到此为止;
- filterChain.doFilter(request,response);在放行时需要传入request和response,意味着请求和响应对象要继续传递给后续的资源,这里没有产生新的request和response对象;
定义两个Servlet作为目标资源:
- ServletA
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理器请求
System.out.println("servletA处理请求的方法,耗时10毫秒");
// 模拟处理请求耗时
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
- ServletB
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理器请求
System.out.println("servletB处理请求的方法,耗时15毫秒");
// 模拟处理请求耗时
try {
Thread.sleep(15);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
配置过滤器以及过滤器的过滤范围:
- web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<!--配置filter,并为filter起别名-->
<filter>
<filter-name>loggingFilter</filter-name>
<filter-class>com.atguigu.filters.LoggingFilter</filter-class>
</filter>
<!--为别名对应的filter配置要过滤的目标资源-->
<filter-mapping>
<filter-name>loggingFilter</filter-name>
<!--通过映射路径确定过滤资源-->
<url-pattern>/servletA</url-pattern>
<!--通过后缀名确定过滤资源-->
<url-pattern>*.html</url-pattern>
<!--通过servlet别名确定过滤资源-->
<servlet-name>servletBName</servlet-name>
</filter-mapping>
</web-app>
-
说明
- filter-mapping标签中定义了过滤器对那些资源进行过滤;
- 子标签url-pattern通过映射路径确定过滤范围;
- /servletA 精确匹配,表示对servletA资源的请求进行过滤;
- *.html 表示对以.action结尾的路径进行过滤;
- /* 表示对所有资源进行过滤;
- 一个filter-mapping下可以配置多个url-pattern;
- 子标签servlet-name通过servlet别名确定对那些servlet进行过滤;
- 使用该标签确定目标资源的前提是servlet已经起了别名;
- 一个filter-mapping下可以定义多个servlet-name;
- 一个filter-mapping下,servlet-name和url-pattern子标签可以同时存在;
过滤过程图解:
8.5.3 过滤器生命周期
过滤器作为web项目的组件之一,和Servlet的生命周期类似,略有不同,没有servlet的load-on-startup的配置,默认就是系统启动立刻构造。
阶段 | 对应方法 | 执行时机 | 执行次数 |
---|---|---|---|
创建对象 | 构造器 | web应用启动时 | 1 |
初始化方法 | void init(FilterConfig filterConfig) | 构造完毕 | 1 |
过滤请求 | void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) | 每次请求 | 多次 |
销毁 | default void destroy() | web应用关闭时 | 1次 |
测试代码:
@WebServlet("/*")
public class LifeCycleFilter implements Filter {
public LifeCycleFilter(){
System.out.println("LifeCycleFilter constructor method invoked");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("LifeCycleFilter init method invoked");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("LifeCycleFilter doFilter method invoked");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
System.out.println("LifeCycleFilter destory method invoked");
}
}
8.5.4 过滤器链的使用
一个web项目中可以同时定义多个过滤器,多个过滤器对同一个资源进行过滤时,工作位置有先后,整体形成一个工作链,称为过滤器链。
- 过滤器链中的过滤器的顺序由filter-mapping顺序决定;
- 每个过滤器过滤的范围不同,针对同一个资源来说,过滤器链中的过滤器个数可能是不同的;
- 如果某个Filter是使用ServletName进行匹配规则的配置,那么这个Filter执行的优先级要更低;
图解过滤器链:
过滤器链功能测试:
-
定义三个过滤器,对目标资源Servlet的请求进行过滤;
-
目标Servlet资源代码;
@WebServlet("/servletC")
public class ServletC extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("servletC service method invoked");
}
}
- 三个过滤器代码:
public class Filter1 implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter1 before chain.doFilter code invoked");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("filter1 after chain.doFilter code invoked");
}
}
public class Filter2 implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter2 before chain.doFilter code invoked");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("filter2 after chain.doFilter code invoked");
}
}
public class Filter3 implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter3 before chain.doFilter code invoked");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("filter3 after chain.doFilter code invoked");
}
}
- 过滤器配置代码:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<filter>
<filter-name>filter1</filter-name>
<filter-class>com.atguigu.filters.Filter1</filter-class>
</filter>
<filter>
<filter-name>filter2</filter-name>
<filter-class>com.atguigu.filters.Filter2</filter-class>
</filter>
<filter>
<filter-name>filter3</filter-name>
<filter-class>com.atguigu.filters.Filter3</filter-class>
</filter>
<!--filter-mapping的顺序决定了过滤器的工作顺序-->
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/servletC</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>filter2</filter-name>
<url-pattern>/servletC</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>filter3</filter-name>
<url-pattern>/servletC</url-pattern>
</filter-mapping>
</web-app>
工作流程图解:
8.5.5 注解方式配置过滤器
@WebFilter注解的使用:
注解源码通过idea查看
- 一个比较完整的Filter的XML配置。
<!--配置filter,并为filter起别名-->
<filter>
<filter-name>loggingFilter</filter-name>
<filter-class>com.atguigu.filters.LoggingFilter</filter-class>
<!--配置filter的初始参数-->
<init-param>
<param-name>dateTimePattern</param-name>
<param-value>yyyy-MM-dd HH:mm:ss</param-value>
</init-param>
</filter>
<!--为别名对应的filter配置要过滤的目标资源-->
<filter-mapping>
<filter-name>loggingFilter</filter-name>
<!--通过映射路径确定过滤资源-->
<url-pattern>/servletA</url-pattern>
<!--通过后缀名确定过滤资源-->
<url-pattern>*.html</url-pattern>
<!--通过servlet别名确定过滤资源-->
<servlet-name>servletBName</servlet-name>
</filter-mapping>
- 将xml配置转换成注解方式实现。
@WebFilter(
filterName = "loggingFilter",
initParams = {@WebInitParam(name="dateTimePattern",value="yyyy-MM-dd HH:mm:ss")},
urlPatterns = {"/servletA","*.html"},
servletNames = {"servletBName"}
)
public class LoggingFilter implements Filter {
/* 内部代码 略 */
}
8.6 案例开发-快递管理-第五期
8.6.1 过滤器控制登录校验
在权限管理中,将资源分为两类通常是为了更好地控制和管理访问权限:
- 匿名资源(Anonymous Resources):
- 这类资源是指不需要进行认证或身份验证就可以访问的资源。
- 通常用于公开信息或对所有用户开放的资源,例如公共网站上的一些信息页面或图片文件等。
- 匿名资源的特点是不需要用户登录或提供特定的身份认证信息即可访问,因此访问门槛低,但也可能限制了一些功能和敏感信息的展示。
- 认证资源(Authenticated Resources):
- 这类资源是指需要进行认证或身份验证后才能访问的资源。
- 认证资源通常包含用户的个人信息、私密数据或需要特定权限才能访问的功能。
- 用户需要通过登录或其他认证手段提供有效的身份信息来访问这类资源,这样可以确保资源的安全性和隐私性,只有经过验证的用户才能够获取。
-
现有资源分类
资源类型 具体资源 匿名资源 login.html /user/login 认证资源 *.jsp /user/logout /user/change /data/ * -
登录保护拦截器实现
@WebFilter(urlPatterns = {"*.jsp","/data/*","/user/change","/user/logout"})
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request =(HttpServletRequest) servletRequest;
HttpServletResponse response =(HttpServletResponse) servletResponse;
HttpSession session = request.getSession();
Object sysUser = session.getAttribute(MyConstants.SESSION_USER_KEY);
if(null != sysUser){
// session中如果存在登录的用户 代表用户登录过,则放行
filterChain.doFilter(servletRequest,servletResponse);
}else{
//用户未登录,重定向到登录页
response.sendRedirect(request.getContextPath()+"/login.html");
}
}
}
8.7 AJAX
8.7.1 什么是AJAX
-
AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML);
-
AJAX 不是新的编程语言,而是一种使用现有标准的新方法;
-
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容;
-
AJAX 不需要任何浏览器插件,但需要用户允许 JavaScript 在浏览器上执行;
-
XMLHttpRequest 只是实现 Ajax 的一种方式;
AJAX 工作原理:
- 简单来说,我们之前发的请求通过类似 form表单标签、a标签这种方式。现在通过运行js代码动态决定什么时候发送什么样的请求;
- 通过运行JS代码发送的请求浏览器可以不用跳转页面 ,我们可以在JS代码中决定是否要跳转页面;
- 通过运行JS代码发送的请求,接收到返回结果后,我们可以将结果通过dom编程渲染到页面的某些元素上,实现局部更新;
8.7.2 如何实现AJAX 请求
原生javascript方式进行ajax(了解):
<script>
function loadXMLDoc(){
var xmlhttp=new XMLHttpRequest();
// 设置回调函数处理响应结果
xmlhttp.onreadystatechange=function(){
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
}
}
// 设置请求方式和请求的资源路径
xmlhttp.open("GET","/try/ajax/ajax_info.txt",true);
// 发送请求
xmlhttp.send();
}
</script>
8.8 监听器
8.8.1 监听器概述
监听器:专门用于对域对象对象身上发生的事件或状态改变进行监听和相应处理的对象;
- 监听器是GOF设计模式中,观察者模式的典型案例;
- 监听器使用的感受类似JS中的事件,被观察的对象发生某些情况时,自动触发代码的执行;
- 监听器并不监听web项目中的所有组件,仅仅是对三大域对象做相关的事件监听;
监听器的分类:
-
web中定义八个监听器接口作为监听器的规范,这八个接口按照不同的标准可以形成不同的分类;
-
按监听的对象划分:
- application域监听器 ServletContextListener ServletContextAttributeListener ;
- session域监听器 HttpSessionListener HttpSessionAttributeListener HttpSessionBindingListener HttpSessionActivationListener ;
- request域监听器 ServletRequestListener ServletRequestAttributeListener ;
-
按监听的事件分:
- 域对象的创建和销毁监听器 ServletContextListener HttpSessionListener ServletRequestListener ;
- 域对象数据增删改事件监听器 ServletContextAttributeListener HttpSessionAttributeListener ServletRequestAttributeListener ;
- 其他监听器 HttpSessionBindingListener HttpSessionActivationListener ;
8.8.2 监听器的六个主要接口
1、application域监听器
ServletContextListener 监听ServletContext对象的创建与销毁。
方法名 | 作用 |
---|---|
contextInitialized(ServletContextEvent sce) | ServletContext创建时调用 |
contextDestroyed(ServletContextEvent sce) | ServletContext销毁时调用 |
- ServletContextEvent对象代表从ServletContext对象身上捕获到的事件,通过这个事件对象我们可以获取到ServletContext对象。
ServletContextAttributeListener 监听ServletContext中属性的添加、移除和修改。
方法名 | 作用 |
---|---|
attributeAdded(ServletContextAttributeEvent scab) | 向ServletContext中添加属性时调用 |
attributeRemoved(ServletContextAttributeEvent scab) | 从ServletContext中移除属性时调用 |
attributeReplaced(ServletContextAttributeEvent scab) | 当ServletContext中的属性被修改时调用 |
- ServletContextAttributeEvent对象代表属性变化事件,它包含的方法如下:
方法名 | 作用 |
---|---|
getName() | 获取修改或添加的属性名 |
getValue() | 获取被修改或添加的属性值 |
getServletContext() | 获取ServletContext对象 |
测试代码
- 定义监听器:
@WebListener
public class ApplicationListener implements ServletContextListener , ServletContextAttributeListener {
// 监听初始化
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext application = sce.getServletContext();
System.out.println("application"+application.hashCode()+" initialized");
}
// 监听销毁
@Override
public void contextDestroyed(ServletContextEvent sce) {
ServletContext application = sce.getServletContext();
System.out.println("application"+application.hashCode()+" destroyed");
}
// 监听数据增加
@Override
public void attributeAdded(ServletContextAttributeEvent scae) {
String name = scae.getName();
Object value = scae.getValue();
ServletContext application = scae.getServletContext();
System.out.println("application"+application.hashCode()+" add:"+name+"="+value);
}
// 监听数据移除
@Override
public void attributeRemoved(ServletContextAttributeEvent scae) {
String name = scae.getName();
Object value = scae.getValue();
ServletContext application = scae.getServletContext();
System.out.println("application"+application.hashCode()+" remove:"+name+"="+value);
}
// 监听数据修改
@Override
public void attributeReplaced(ServletContextAttributeEvent scae) {
String name = scae.getName();
Object value = scae.getValue();
ServletContext application = scae.getServletContext();
Object newValue = application.getAttribute(name);
System.out.println("application"+application.hashCode()+" change:"+name+"="+value+" to "+newValue);
}
}
- 定义触发监听器的代码:
// ServletA用于向application域中放入数据
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 向application域中放入数据
ServletContext application = this.getServletContext();
application.setAttribute("k1","v1");
application.setAttribute("k2","v2");
}
}
// ServletB用于向application域中修改和移除数据
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext appliation = getServletContext();
// 修改application域中的数据
appliation.setAttribute("k1","value1");
// 删除application域中的数据
appliation.removeAttribute("k2");
}
}
2、session域监听器
HttpSessionListener 监听HttpSession对象的创建与销毁:
方法名 | 作用 |
---|---|
sessionCreated(HttpSessionEvent hse) | HttpSession对象创建时调用 |
sessionDestroyed(HttpSessionEvent hse) | HttpSession对象销毁时调用 |
- HttpSessionEvent对象代表从HttpSession对象身上捕获到的事件,通过这个事件对象我们可以获取到触发事件的HttpSession对象。
HttpSessionAttributeListener 监听HttpSession中属性的添加、移除和修改:
方法名 | 作用 |
---|---|
attributeAdded(HttpSessionBindingEvent se) | 向HttpSession中添加属性时调用 |
attributeRemoved(HttpSessionBindingEvent se) | 从HttpSession中移除属性时调用 |
attributeReplaced(HttpSessionBindingEvent se) | 当HttpSession中的属性被修改时调用 |
- HttpSessionBindingEvent对象代表属性变化事件,它包含的方法如下:
方法名 | 作用 |
---|---|
getName() | 获取修改或添加的属性名 |
getValue() | 获取被修改或添加的属性值 |
getSession() | 获取触发事件的HttpSession对象 |
测试代码:
- 定义监听器:
@WebListener
public class SessionListener implements HttpSessionListener, HttpSessionAttributeListener {
// 监听session创建
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("session"+session.hashCode()+" created");
}
// 监听session销毁
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("session"+session.hashCode()+" destroyed");
}
// 监听数据增加
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
String name = se.getName();
Object value = se.getValue();
HttpSession session = se.getSession();
System.out.println("session"+session.hashCode()+" add:"+name+"="+value);
}
// 监听数据移除
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
String name = se.getName();
Object value = se.getValue();
HttpSession session = se.getSession();
System.out.println("session"+session.hashCode()+" remove:"+name+"="+value);
}
// 监听数据修改
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
String name = se.getName();
Object value = se.getValue();
HttpSession session = se.getSession();
Object newValue = session.getAttribute(name);
System.out.println("session"+session.hashCode()+" change:"+name+"="+value+" to "+newValue);
}
}
- 定义触发监听器的代码:
// servletA用于创建session并向session中放数据
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 创建session,并向session中放入数据
HttpSession session = req.getSession();
session.setAttribute("k1","v1");
session.setAttribute("k2","v2");
}
}
// servletB用于修改删除session中的数据并手动让session不可用
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
// 修改session域中的数据
session.setAttribute("k1","value1");
// 删除session域中的数据
session.removeAttribute("k2");
// 手动让session不可用
session.invalidate();
}
}
3、request域监听器
ServletRequestListener 监听ServletRequest对象的创建与销毁:
方法名 | 作用 |
---|---|
requestInitialized(ServletRequestEvent sre) | ServletRequest对象创建时调用 |
requestDestroyed(ServletRequestEvent sre) | ServletRequest对象销毁时调用 |
- ServletRequestEvent对象代表从HttpServletRequest对象身上捕获到的事件,通过这个事件对象我们可以获取到触发事件的HttpServletRequest对象。另外还有一个方法可以获取到当前Web应用的ServletContext对象。
ServletRequestAttributeListener 监听ServletRequest中属性的添加、移除和修改:
方法名 | 作用 |
---|---|
attributeAdded(ServletRequestAttributeEvent srae) | 向ServletRequest中添加属性时调用 |
attributeRemoved(ServletRequestAttributeEvent srae) | 从ServletRequest中移除属性时调用 |
attributeReplaced(ServletRequestAttributeEvent srae) | 当ServletRequest中的属性被修改时调用 |
- ServletRequestAttributeEvent对象代表属性变化事件,它包含的方法如下:
方法名 | 作用 |
---|---|
getName() | 获取修改或添加的属性名 |
getValue() | 获取被修改或添加的属性值 |
getServletRequest () | 获取触发事件的ServletRequest对象 |
- 定义监听器:
@WebListener
public class RequestListener implements ServletRequestListener , ServletRequestAttributeListener {
// 监听初始化
@Override
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest request = sre.getServletRequest();
System.out.println("request"+request.hashCode()+" initialized");
}
// 监听销毁
@Override
public void requestDestroyed(ServletRequestEvent sre) {
ServletRequest request = sre.getServletRequest();
System.out.println("request"+request.hashCode()+" destoryed");
}
// 监听数据增加
@Override
public void attributeAdded(ServletRequestAttributeEvent srae) {
String name = srae.getName();
Object value = srae.getValue();
ServletRequest request = srae.getServletRequest();
System.out.println("request"+request.hashCode()+" add:"+name+"="+value);
}
// 监听数据移除
@Override
public void attributeRemoved(ServletRequestAttributeEvent srae) {
String name = srae.getName();
Object value = srae.getValue();
ServletRequest request = srae.getServletRequest();
System.out.println("request"+request.hashCode()+" remove:"+name+"="+value);
}
// 监听数据修改
@Override
public void attributeReplaced(ServletRequestAttributeEvent srae) {
String name = srae.getName();
Object value = srae.getValue();
ServletRequest request = srae.getServletRequest();
Object newValue = request.getAttribute(name);
System.out.println("request"+request.hashCode()+" change:"+name+"="+value+" to "+newValue);
}
}
- 定义触发监听器的代码:
// servletA向请求域中放数据
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 向request中增加数据
req.setAttribute("k1","v1");
req.setAttribute("k2","v2");
// 请求转发
req.getRequestDispatcher("servletB").forward(req,resp);
}
}
// servletB修改删除域中的数据
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 修改request域中的数据
req.setAttribute("k1","value1");
// 删除session域中的数据
req.removeAttribute("k2");
}
}
8.8.3 session域的两个特殊监听器
1、session绑定监听器
HttpSessionBindingListener 监听当前监听器对象在Session域中的增加与移除:
方法名 | 作用 |
---|---|
valueBound(HttpSessionBindingEvent event) | 该类的实例被放到Session域中时调用 |
valueUnbound(HttpSessionBindingEvent event) | 该类的实例从Session中移除时调用 |
- HttpSessionBindingEvent对象代表属性变化事件,它包含的方法如下:
方法名 | 作用 |
---|---|
getName() | 获取当前事件涉及的属性名 |
getValue() | 获取当前事件涉及的属性值 |
getSession() | 获取触发事件的HttpSession对象 |
测试代码:
- 定义监听器:
public class MySessionBindingListener implements HttpSessionBindingListener {
// 监听绑定
@Override
public void valueBound(HttpSessionBindingEvent event) {
HttpSession session = event.getSession();
String name = event.getName();
System.out.println("MySessionBindingListener"+this.hashCode()+" binding into session"+session.hashCode()+" with name "+name);
}
// 监听解除绑定
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
HttpSession session = event.getSession();
String name = event.getName();
System.out.println("MySessionBindingListener"+this.hashCode()+" unbond outof session"+session.hashCode()+" with name "+name);
}
}
- 定义触发监听器的代码:
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
// 绑定监听器
session.setAttribute("bindingListener",new MySessionBindingListener());
// 解除绑定监听器
session.removeAttribute("bindingListener");
}
}
2、钝化活化监听器
HttpSessionActivationListener 监听某个对象在Session中的序列化与反序列化:
方法名 | 作用 |
---|---|
sessionWillPassivate(HttpSessionEvent se) | 该类实例和Session一起钝化到硬盘时调用 |
sessionDidActivate(HttpSessionEvent se) | 该类实例和Session一起活化到内存时调用 |
- HttpSessionEvent对象代表事件对象,通过getSession()方法获取事件涉及的HttpSession对象。
什么是钝化活化:
- session对象在服务端是以对象的形式存储于内存的,session过多,服务器的内存也是吃不消的;
- 而且一旦服务器发生重启,所有的session对象都将被清除,也就意味着session中存储的不同客户端的登录状态丢失;
- 为了分摊内存 压力并且为了保证session重启不丢失,我们可以设置将session进行钝化处理;
- 在关闭服务器前或者到达了设定时间时,对session进行序列化到磁盘,这种情况叫做session的钝化;
- 在服务器启动后或者再次获取某个session时,将磁盘上的session进行反序列化到内存,这种情况叫做session的活化;
如何配置钝化活化?
-
在web目录下,添加 META-INF下创建Context.xml。
-
文件中配置钝化。
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="d:\mysession"></Store>
</Manager>
</Context>
- 请求servletA,获得session,并存入数据,然后重启服务器:
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
// 添加数据
session.setAttribute("k1","v1");
}
}
- 请求servletB获取session,获取重启前存入的数据:
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
Object v1 = session.getAttribute("k1");
System.out.println(v1);
}
}
如何监听钝化活化?
- 定义监听器:
package com.atguigu.listeners;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSessionActivationListener;
import jakarta.servlet.http.HttpSessionEvent;
import java.io.Serializable;
public class ActivationListener implements HttpSessionActivationListener, Serializable {
// 监听钝化
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("session with JSESSIONID "+ session.getId()+" will passivate");
}
// 监听活化
@Override
public void sessionDidActivate(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("session with JSESSIONID "+ session.getId()+" did activate");
}
}
- 定义触发监听器的代码:
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
// 添加数据
session.setAttribute("k1","v1");
// 添加钝化活化监听器
session.setAttribute("activationListener",new ActivationListener());
}
}
ntManager" maxIdleSwap=“1”>
+ 请求servletA,获得session,并存入数据,然后重启服务器:
```java
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
// 添加数据
session.setAttribute("k1","v1");
}
}
- 请求servletB获取session,获取重启前存入的数据:
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
Object v1 = session.getAttribute("k1");
System.out.println(v1);
}
}
如何监听钝化活化?
- 定义监听器:
package com.atguigu.listeners;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSessionActivationListener;
import jakarta.servlet.http.HttpSessionEvent;
import java.io.Serializable;
public class ActivationListener implements HttpSessionActivationListener, Serializable {
// 监听钝化
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("session with JSESSIONID "+ session.getId()+" will passivate");
}
// 监听活化
@Override
public void sessionDidActivate(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("session with JSESSIONID "+ session.getId()+" did activate");
}
}
- 定义触发监听器的代码:
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
// 添加数据
session.setAttribute("k1","v1");
// 添加钝化活化监听器
session.setAttribute("activationListener",new ActivationListener());
}
}