我是南城余!阿里云开发者平台专家博士证书获得者!
欢迎关注我的博客!一同成长!
一名从事运维开发的worker,记录分享学习。
专注于AI,运维开发,windows Linux 系统领域的分享!
本章节对应知识库
https://www.yuque.com/nanchengcyu/java
本内容来自尚硅谷课程,此处在知识库做了个人理解
————————————————
HTTP是无状态协议
无状态就是不保存状态,即无状态协议(stateless),HTTP协议自身不对请求和响应之间的通信状态进行保存,也就是说,在HTTP协议这个级别,协议对于发送过的请求或者响应都不做持久化处理
简单理解:浏览器发送请求,服务器接收并响应,但是服务器不记录请求是否来自哪个浏览器,服务器没记录浏览器的特征,就是客户端的状态
举例: 张三去一家饭馆点了几道菜,觉得味道不错,第二天又去了,对老板说,还点上次的那几道菜
无状态: 老板没有记录张三是否来过,更没有记录上次他点了那些菜,张三只能重新再点一遍
有状态: 老板把每次来吃饭的用户都做好记录,查阅一下之前的记录,查到了张三之前的菜单,直接下单
Cookie和Session配合解决
cookie是在客户端保留少量数据的技术,主要通过响应头向客户端响应一些客户端要保留的信息
session是在服务端保留更多数据的技术,主要通过HttpSession对象保存一些和客户端相关的信息
cookie和session配合记录请求状态
举例: 张三去银行办业务
张三第一次去某个银行办业务,银行会为张三开户(Session),并向张三发放一张银行卡(cookie)
张三后面每次去银行,就可以携带之间的银行卡(cookie),银行根据银行卡找到之前张三的账户(session)
cookie是一种客户端会话技术,cookie由服务端产生,它是服务器存放在浏览器的一小份数据,浏览器以后每次访问该服务器的时候都会将这小份数据携带到服务器去。
服务端创建cookie,将cookie放入响应对象中,Tomcat容器将cookie转化为set-cookie响应头,响应给客户端
客户端在收到cookie的响应头时,在下次请求该服务的资源时,会以cookie请求头的形式携带之前收到的Cookie
cookie是一种键值对格式的数据,从tomcat8.5开始可以保存中文,但是不推荐
由于cookie是存储于客户端的数据,比较容易暴露,一般不存储一些敏感或者影响安全的数据
原理图
应用场景举例
记录用户名
当我们在用户名的输入框中输入完用户名后,浏览器记录用户名,下一次再访问登录页面时,用户名自动填充到用户名的输入框.
保存电影播放进度
在网页上播放电影的时候,如果中途退出浏览器了,下载再打开浏览器播放同一部电影的时候,会自动跳转到上次退出时候的进度,因为在播放的时候会将播放进度保存到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()); ? ? ? ? ? } ? ? ? } ? } }
默认情况下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()); ? ? ? ? ? } ? ? ? } ? } }
访问互联网资源时不能每次都需要把所有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了
HttpSession是一种保留更多信息在服务端的一种技术,服务器会为每一个客户端开辟一块内存空间,即session对象. 客户端在发送请求时,都可以使用自己的session. 这样服务端就可以通过session来记录某个客户端的状态了
服务端在为客户端创建session时,会同时将session对象的id,即JSESSIONID以cookie的形式放入响应对象
后端创建完session后,客户端会收到一个特殊的cookie,叫做JSESSIONID
客户端下一次请求时携带JSESSIONID,后端收到后,根据JSESSIONID找到对应的session对象
通过该机制,服务端通过session就可以存储一些专门针对某个客户端的信息了
session也是域对象(后续详细讲解)
原理图如下
应用场景
记录用户的登录状态
用户登录后,将用户的账号等敏感信息存入session
记录用户操作的历史
例如记录用户的访问痕迹,用户的购物车信息等临时性的信息
用户提交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方法的处理逻辑
为什么要设置session的时效
用户量很大之后,Session对象相应的也要创建很多。如果一味创建不释放,那么服务器端的内存迟早要被耗尽。
客户端关闭行为无法被服务端直接侦测,或者客户端较长时间不操作也经常出现,类似这些的情况,就需要对session的时限进行设置了
默认的session最大闲置时间(两次使用同一个session中的间隔时间) 在tomcat/conf/web.xml配置为30分钟
我们可以自己在当前项目的web.xml对最大闲置时间进行重新设定
也可以通过HttpSession的API 对最大闲置时间进行设定
// 设置最大闲置时间 session.setMaxInactiveInterval(60);
也可以直接让session失效
// 直接让session失效 session.invalidate();
域对象: 一些用于存储数据和传递数据的对象,传递数据不同的范围,我们称之为不同的域,不同的域对象代表不同的域,共享数据的范围也不同
web项目中,我们一定要熟练使用的域对象分别是 请求域,会话域,应用域
请求域对象是HttpServletRequest ,传递数据的范围是一次请求之内及请求转发
会话域对象是HttpSession,传递数据的范围是一次会话之内,可以跨多个请求
应用域对象是ServletContext,传递数据的范围是本应用之内,可以跨多个会话
生活举例: 热水器摆放位置不同,使用的范围就不同
摆在张三工位下,就只有张三一个人能用
摆在办公室的公共区,办公室内的所有人都可以用
摆在楼层的走廊区,该楼层的所有人都可以用
三大域对象的数据作用范围图解
请求域
会话域
应用域
所有域在一起
域对象的API
API | 功能 |
---|---|
void setAttribute(String name,String value) | 向域对象中添加/修改数据 |
Object getAttribute(String name); | 从域对象中获取数据 |
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容器
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() | 销毁方法,容器在回收过滤器对象之前调用的方法 |
目标:开发一个日志记录过滤器
用户请求到达目标资源之前,记录用户的请求资源路径
响应之前记录本次请求目标资源运算的耗时
可以选择将日志记录进入文件,为了方便测试,这里将日志直接在控制台打印
定义一个过滤器类,编写功能代码
package com.atguigu.filters; ? ? import jakarta.servlet.*; import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; ? import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; 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子标签可以同时存在
过滤过程图解
过滤器作为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次 |
测试代码
package com.atguigu.filters; ? import jakarta.servlet.*; import jakarta.servlet.annotation.WebServlet; ? import java.io.IOException; ? ? @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"); ? } } ?
一个web项目中,可以同时定义多个过滤器,多个过滤器对同一个资源进行过滤时,工作位置有先后,整体形成一个工作链,称之为过滤器链
过滤器链中的过滤器的顺序由filter-mapping顺序决定
每个过滤器过滤的范围不同,针对同一个资源来说,过滤器链中的过滤器个数可能是不同的
如果某个Filter是使用ServletName进行匹配规则的配置,那么这个Filter执行的优先级要更低
图解过滤器链
过滤器链功能测试
定义三个过滤器,对目标资源Servlet的请求进行过滤
目标Servlet资源代码
package com.atguigu.servlet; ? import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; ? import java.io.IOException; ? @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>
工作流程图解
@WebFilter注解的使用
源码
package jakarta.servlet.annotation; ? import jakarta.servlet.DispatcherType; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; ? @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface WebFilter { ? ?String description() default ""; ? ? ?String displayName() default ""; ? ? ?WebInitParam[] initParams() default {}; ? ? ?String filterName() default ""; ? ? ?String smallIcon() default ""; ? ? ?String largeIcon() default ""; ? ? ?String[] servletNames() default {}; ? ? ?String[] value() default {}; ? ? ?String[] urlPatterns() default {}; ? ? ?DispatcherType[] dispatcherTypes() default {DispatcherType.REQUEST}; ? ? ?boolean asyncSupported() default false; } ?
一个比较完整的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配置转换成注解方式实现
package com.atguigu.filters; ? ? import jakarta.servlet.*; import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.annotation.WebInitParam; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; ? import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; ? ? ? @WebFilter( ? ? ? ?filterName = "loggingFilter", ? ? ? ?initParams = {@WebInitParam(name="dateTimePattern",value="yyyy-MM-dd HH:mm:ss")}, ? ? ? ?urlPatterns = {"/servletA","*.html"}, ? ? ? ?servletNames = {"servletBName"} ) public class LoggingFilter ?implements Filter { ? ?private SimpleDateFormat dateFormat ; ? ? ?/*init初始化方法,通过filterConfig获取初始化参数 ? ?* init方法中,可以用于定义一些其他初始化功能代码 ? ?* */ ? ?@Override ? ?public void init(FilterConfig filterConfig) throws ServletException { ? ? ? ?// 获取初始参数 ? ? ? ?String dateTimePattern = filterConfig.getInitParameter("dateTimePattern"); ? ? ? ?// 初始化成员变量 ? ? ? ?dateFormat=new SimpleDateFormat(dateTimePattern); ? } ? ?@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); ? ? } }