29. Filter 过滤器 以及 Listener 监听器
JavaWeb的三大组件
组件 | 作用 | 实现接口 |
---|---|---|
Servlet | 小应用程序,在JavaWeb中主要做为控制器来使用 可以处理用户的请求并且做出响应 | javax.servlet.Servlet |
Filter | 过滤器,对用户发送的请求或响应进行集中处理,实现请求的拦截 | javax.servlet.Filter |
Listener | 监听器,在某些框架中会使用到监听器(比如spring),在Web执行过程中,引发一些事件,对相应事件进行处理 | javax.servlet.XxxListener 每个事件有一个接口 |
一、 概述
生活中的过滤器
净水器、空气净化器、地铁安检
web中的过滤器
当用户访问服务器资源时,过滤器将请求拦截下来,完成一些通用的操作
应用场景
如:登录验证、统一编码处理、敏感字符过滤
1592011832218
从上图可以简单说明一下,Filter 过滤器就是用来拦截 请求 或者 响应 的。下面我们首先来写一个快速入门的案例,从代码的角度来认识一下。
二、Filter过滤器 - 快速入门
首先我们创建一个HelloServlet,用来下面提供Filter进行拦截,如下:
image-20210304235335136
代码语言:javascript复制@WebServlet("/HelloServlet")
public class HelloServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("HelloServlet 被访问了...");
}
}
好了,下面我们来开始写 Filter 过滤器。而 Filter 跟 Servlet 一样,具有两种写法,一种是 xml 配置的方式,另一种是注解的方式。
下面我们首先来写 xml 配置的方式。
2.1 xml配置
2.1.1 编写java类,实现filter接口
image-20210304235727090
代码语言:javascript复制package com.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* 过滤器入门案例
* 1. 编写一个类 , 实现Filter接口,重写抽象方法
* 2. 配置web.xml / 注解
*
* @author Aron.li
* @date 2021/3/4 23:55
*/
public class MyFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 处理拦截器的相关拦截业务
System.out.println("filter执行了,然后放行");
// 放行拦截,执行后续Servlet程序
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {
}
}
2.1.2 配置web.xml
image-20210304235853975
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!--
filter的web.xml配置
1. filter和filter-mapping的子标签filter-name必须一致(可以自定义,通常与类名相同)
2. url-pattern : 当前filter要拦截的虚拟路径
-->
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/HelloServlet</url-pattern>
</filter-mapping>
</web-app>
2.1.3 启动服务,测试 filter 的效果
启动服务后,浏览器访问 http://localhost:8082/HelloServlet
image-20210305000354097
好了,我们已经知道 xml 如何配置 filter 了,那么下面再来看看注解的配置方式。
2.2 注解配置
2.2.1 编写java类,实现filter接口
首先我们将上面的 xml 配置进行注释,如下:
image-20210305000516205
然后给 MyFilter 设置注解,在注解中的参数就是配置需要拦截的请求路径,如下:
image-20210305000650221
代码语言:javascript复制package com.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* 过滤器入门案例
* 1. 编写一个类 , 实现Filter接口,重写抽象方法
* 2. 配置web.xml / 注解
*
* @author Aron.li
* @date 2021/3/4 23:55
*/
// 注解配置
@WebFilter("/HelloServlet")
public class MyFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 处理拦截器的相关拦截业务
System.out.println("注解的方式: filter执行了,然后放行");
// 放行拦截,执行后续Servlet程序
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {
}
}
2.2.2 重新部署服务,测试 filter 的效果
image-20210305000842746
可以看到注解的方式的 filter 过滤器也成功拦截了请求了。
2.3 Filter模板设置
2.3.1 设置 Filter 模板
上面我们已经成功编写了拦截器的示例,为了可以快速编写代码,我们还可以修改拦截器的自动生成模板,如下:
搜索 file and code template ,选择 Other 如下:
image-20210305001334388
模板如下:
代码语言:javascript复制#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
#parse("File Header.java")
@javax.servlet.annotation.WebFilter(urlPatterns = "/${Entity_Name}")
public class ${Class_Name} implements javax.servlet.Filter {
public void init(javax.servlet.FilterConfig config) throws javax.servlet.ServletException {
}
public void doFilter(javax.servlet.ServletRequest req, javax.servlet.ServletResponse resp, javax.servlet.FilterChain chain) throws javax.servlet.ServletException, java.io.IOException {
chain.doFilter(req, resp);
}
public void destroy() {
}
}
2.3.2 测试创建模板
image-20210305001446860
image-20210305001503238
image-20210305001517508
三、Filter过滤器 - 工作原理
代码语言:javascript复制1. 用户发送请求,请求Web资源(包括html,jsp,servlet等)
2. 如果Web资源的地址,匹配filter的地址,请求将先经过filter,并执行doFilter()
3. doFilter()方法中如果调用chain.doFilter(),则放行执行下一个Web资源。
4. 访问Web资源,响应回来会再次经过filter,执行过滤器中的代码,到达浏览器端。
四、Filter过滤器 - 使用细节
4.1 生命周期
生命周期:指的是一个对象从生(创建)到死(销毁)的一个过程
代码语言:javascript复制// filter 过滤器的声明周期主要有三个方法: init、doFilter、destory,分别如下:
// 初始化方法
public void init(FilterConfig config);
// 执行拦截方法
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain);
// 销毁方法
public void destroy();
代码语言:javascript复制* 创建
服务器启动项目加载,创建filter对象,执行init方法(只执行一次)
* 运行(过滤拦截)
用户访问被拦截目标资源时,执行doFilter方法
* 销毁
服务器关闭项目卸载时,销毁filter对象,执行destroy方法(只执行一次)
* 补充:
过滤器一定是优先于servlet创建的,后于Servlet销毁
下面个声明周期的案例。
4.1.1 编写一个演示声明周期的过滤器 LifeFilter
image-20210305003635786
代码语言:javascript复制package com.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* #Filter的生命周期
* 0. 先于Servlet创建,后于Servlet销毁
* 1. init方法
* filter 自动启动加载的,执行一次
* (先于Servlet的init方法执行)
* 2. doFilter 方法
* (先于Servlet的service方法执行)
* 浏览器每访问一次,就会执行一次
*
* chain.doFilter(req, resp); // 放行
*
* 3. destroy 方法
* (后于Servlet的destroy方法执行)
* tomcat关闭,会随之销毁, 只执行一次
*
* @author Aron.li
* @date 2021/3/5 0:31
*/
@WebFilter(urlPatterns = "/LifeServlet")
public class LifeFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
System.out.println("lifeFilter init");
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
/*
* 此方法决定了,后续资源是否被访问到
* 1. 如果调用此方法,后续资源就会被访问到
* 2. 如果没有调用,后续资源就不会被访问到
* -> 放行
*
* 类似于请求转发
* 不仅拦截对资源的请求,还拦截资源的响应
* */
System.out.println("lifeFilter doFilter before");
chain.doFilter(req, resp);
System.out.println("lifeFilter doFilter after");
}
public void destroy() {
System.out.println("lifeFilter destroy");
}
}
4.1.2 编写提供访问的 LifeServlet
image-20210305003841971
代码语言:javascript复制package com.web;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Aron.li
* @date 2021/3/5 0:37
*/
@WebServlet("/LifeServlet")
public class LifeServlet implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("LifeServlet init");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("LifeServlet service");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("LifeServlet destroy");
}
}
4.1.3 启动服务,测试请求 LifeServlet,查看声明周期函数的调用
首先访问 http://localhost:8082/LifeServlet ,查看声明周期函数打印如下:
image-20210305004139979
从上面我结果来看,filter 过滤器的声明周期 都是在 servlet 程序的前面执行。下面我们关闭 tomcat,看看结束时候的生命周期。
image-20210305004324089
在这里我们已经知道了 Filter 和 Servlet 之间的执行顺序,下面再来看看 Filter 的拦截路径。
4.2 拦截路径
在开发时,我们可以指定过滤器的拦截路径来定义拦截目标资源的范围
代码语言:javascript复制* 精准匹配
用户访问指定目标资源(/show.jsp)时,过滤器进行拦截
* 目录匹配
用户访问指定目录下(/user/*)所有资源时,过滤器进行拦截
* 后缀匹配
用户访问指定后缀名(*.html)的资源时,过滤器进行拦截
* 匹配所有
用户访问该网站所有资源(/*)时,过滤器进行拦截
4.2.1 精准匹配的案例
首先我们写一个 show.jsp
,如下:
image-20210305004855960
代码语言:javascript复制<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>show jsp</h1>
</body>
</html>
下面再来创建一个 PathFilter,专门来进行精确匹配,如下:
image-20210305005432652
代码语言:javascript复制package com.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
1. 精准匹配
@WebFilter(urlPatterns = "/LifeServlet")
*
* @author Aron.li
* @date 2021/3/5 0:52
*/
@WebFilter(urlPatterns = "/show.jsp")
public class PathFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截业务
System.out.println("被 PathFilter 拦截了 ....");
// 拦截放行
chain.doFilter(req, resp);
}
public void destroy() {
}
}
测试访问 show.jsp
查看拦截的情况,如下:
image-20210305005524070
4.2.2 目录匹配的案例
上面通过精确匹配,我们已经匹配拦截到了 show.jsp
, 下面我们再来创建一个路径,拦截这个路径下的请求,如下:
image-20210305005901515
下面再修改 PathFilter 为目录匹配的方式,如下:
image-20210305005942404
代码语言:javascript复制package com.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
1. 精准匹配
@WebFilter(urlPatterns = "/LifeServlet")
2. 目录匹配
@WebFilter(urlPatterns = "/abc/*")
*
* @author Aron.li
* @date 2021/3/5 0:52
*/
@WebFilter(urlPatterns = "/abc/*")
public class PathFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截业务
System.out.println("目录匹配: 被 PathFilter 拦截了 ....");
// 拦截放行
chain.doFilter(req, resp);
}
public void destroy() {
}
}
测试请求如下:
image-20210305010029599
4.2.3 后缀名匹配的案例
这里我们只要修改 PathFilter 的匹配路径就可以了,如下:
image-20210305010311534
代码语言:javascript复制package com.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
1. 精准匹配
@WebFilter(urlPatterns = "/LifeServlet")
2. 目录匹配
@WebFilter(urlPatterns = "/abc/*")
3. 后缀名匹配
@WebFilter(urlPatterns = "*.jsp")
*
* @author Aron.li
* @date 2021/3/5 0:52
*/
@WebFilter(urlPatterns = "*.jsp")
public class PathFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截业务
System.out.println("后缀名匹配: 被 PathFilter 拦截了 ....");
// 拦截放行
chain.doFilter(req, resp);
}
public void destroy() {
}
}
测试如下:
image-20210305010342602
4.2.4 匹配所有的案例
匹配所有也只需要修改 PathServlet 的匹配路径即可,如下:
image-20210305072039366
代码语言:javascript复制package com.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
1. 精准匹配
@WebFilter(urlPatterns = "/LifeServlet")
2. 目录匹配
@WebFilter(urlPatterns = "/abc/*")
3. 后缀名匹配
@WebFilter(urlPatterns = "*.jsp")
4. 匹配所有 (html,css,js,servlet...)
@WebFilter(urlPatterns = "/*")
*
* @author Aron.li
* @date 2021/3/5 0:52
*/
@WebFilter(urlPatterns = "/*")
public class PathFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截业务
System.out.println("匹配所有: 被 PathFilter 拦截了 ....");
// 拦截放行
chain.doFilter(req, resp);
}
public void destroy() {
}
}
测试如下:
- 首先访问 index.jsp 如下:
image-20210305072234706
- 再访问 LifeServlet 如下:
image-20210305072327860
- 最后再试试
/abc/hello.jsp
如下:
image-20210305072448207
可以从上面的测试中,匹配所有可以拦截所有路径的请求。
4.2.5 匹配多个路径的案例
在上面我们已经尝试了四种匹配路径的方式,在最后我们再来测试多个路径拦截。
image-20210305073004675
代码语言:javascript复制package com.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
1. 精准匹配
@WebFilter(urlPatterns = "/LifeServlet")
2. 目录匹配
@WebFilter(urlPatterns = "/abc/*")
3. 后缀名匹配
@WebFilter(urlPatterns = "*.jsp")
4. 匹配所有 (html,css,js,servlet...)
@WebFilter(urlPatterns = "/*")
*
* @author Aron.li
* @date 2021/3/5 0:52
*/
@WebFilter(urlPatterns = {"*.jsp", "/LifeServlet"})
public class PathFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截业务
System.out.println("多个路径匹配: 被 PathFilter 拦截了 ....");
// 拦截放行
chain.doFilter(req, resp);
}
public void destroy() {
}
}
测试如下:
- 访问匹配
*.jsp
的请求,如下:
image-20210305073336152
- 访问匹配
/LifeServlet
的请求,如下:
image-20210305073401834
好了,写到这里我们已经知道了拦截器的拦截路径该如何匹配了,下面我们再来看拦截器如何来拦截不同的请求,例如:request 请求、forward 请求转发。
4.3 拦截方式
在开发时,我们可以指定过滤器的拦截方式来处理不同的应用场景,比如:只拦截从浏览器直接发送过来的请求,或者拦截内部转发的请求
代码语言:javascript复制总共有五种不同的拦截方式,我们这里学习常见的两种
1. request(默认拦截方式)
浏览器直接发送请求时,拦截
2. forward
请求转发的时候,拦截
比如: 资源A转发到资源B时
我们可以配置 二个同时存在...
下面的案例,我们还是分为 xml 版本 和 注解版本 两种来分开演示一下。
4.3.1 xml版本
下面我们简单演示一下 xml 版本该如何配置,那么首先来创建一个拦截器:
image-20210305074037955
代码语言:javascript复制public class MethodFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截业务代码
System.out.println("被 method 拦截了");
// 放行
chain.doFilter(req, resp);
}
public void destroy() {
}
}
配置 xml 如下:
image-20210305074300734
代码语言:javascript复制<!--
filter的web.xml配置
1. filter和filter-mapping的子标签filter-name必须一致(可以自定义,通常与类名相同)
2. url-pattern : 当前filter要拦截的虚拟路径
-->
<filter>
<filter-name>MethodFilter</filter-name>
<filter-class>com.filter.MethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MethodFilter</filter-name>
<url-pattern>/HelloServlet</url-pattern>
<!--
dispatcher : 用来指定拦截方式的
1. REQUEST(默认) : 浏览器直接发送过来的请求
2. FORWARD: 请求转发过来的请求
可以同时设置
-->
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
测试请求如下:
image-20210305074512188
在这里我们只演示该如何配置,下面我们在注解版本的案例中,来演示拦截请求转发的请求。
4.3.2 注解版本
4.3.2.1 首先创建一个注解版本的拦截器 DispatcherFilter,然后创建两个Servlet,其中为 AServlet 和 BServlet,访问 AServlet 的时候请求转发至 BServlet ,查看 DispatcherFilter 是否会拦截请求转发。
- 拦截器 DispatcherFilter
image-20210305075446687
代码语言:javascript复制package com.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* @author Aron.li
* @date 2021/3/5 7:49
*/
@WebFilter(urlPatterns = "*.do")
public class DispatcherFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截
System.out.println("DispatcherFilter 拦截 *.do 请求..");
// 放行
chain.doFilter(req, resp);
}
public void destroy() {
}
}
在这里设置拦截路径 *.do
, 下面我们将两个Servlet 的路径都加上 .do
路径,那么就可以被拦截了。但是请求转发的 forward请求 会被拦截么?
- AServlet
image-20210305075939890
代码语言:javascript复制package com.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;
/**
* @author Aron.li
* @date 2021/3/5 7:47
*/
@WebServlet("/AServlet.do")
public class AServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("访问 AServlet.....");
// 请求转发至 BServlet。。。
request.getRequestDispatcher("/BServlet.do").forward(request, response);
}
}
- BServlet
image-20210305080009243
代码语言:javascript复制package com.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;
/**
* @author Aron.li
* @date 2021/3/5 7:48
*/
@WebServlet("/BServlet.do")
public class BServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("访问 BServelet...");
}
}
- 最后,我们测试一下,只访问 AServlet,然后 AServlet 自动转发 BServlet, 看看会不会拦截两次
image-20210305080138845
可以从拦截的结果来看,默认只拦截了 request 请求,而不会去拦截 请求转发的 forward 请求。那么如果我们需要拦截 forward 请求,则需要配置 拦截方式。
4.3.2.2 配置同时拦截 forward 和 request 请求
image-20210305080408797
代码语言:javascript复制package com.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* 拦截方式
* 1. REQUEST(默认) : 浏览器发起的请求
* 2. FORWARD : 请求转发
* 可以同时设置
*
* @author Aron.li
* @date 2021/3/5 7:49
*/
@WebFilter(urlPatterns = "*.do", dispatcherTypes = {DispatcherType.FORWARD, DispatcherType.REQUEST})
public class DispatcherFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截
System.out.println("DispatcherFilter 拦截 *.do 请求..");
// 放行
chain.doFilter(req, resp);
}
public void destroy() {
}
}
再次测试访问 AServlet, 确认是否拦截请求转发:
image-20210305080518449
4.4 过滤器链
在一次请求中,若我们请求匹配到了多个filter,通过请求就相当于把这些filter串起来了,形成了过滤器链
代码语言:javascript复制* 需求
用户访问目标资源 show.jsp时,经过 FilterA FilterB
* 过滤器链执行顺序 (先进后出)
1.用户发送请求
2.FilterA拦截,放行
3.FilterB拦截,放行
4.执行目标资源 show.jsp
5.FilterB增强响应
6.FilterA增强响应
7.封装响应消息格式,返回到浏览器
* 过滤器链中执行的先后问题....
配置文件
谁先声明,谁先执行
<filter-mapping>
注解【不推荐】
根据过滤器类名进行排序,值小的先执行
FilterA FilterB 进行比较, FilterA先执行...
1592017660642
下面我们来基于上面的需求来写一个案例。
4.4.1 创建访问资源 ServletA
image-20210305224731864
代码语言:javascript复制@WebServlet("/ServletA")
public class ServletA extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("访问 ServletA.....");
}
}
4.4.2 创建拦截器 FilterA
image-20210305224844070
代码语言:javascript复制public class FilterA implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
System.out.println("FilterA执行了");
/*
* 放行: 允许请求往后继续传递
* 1. 等价于请求转发
* 2. 如果后续还有过滤器,先执行过滤器, 知道过滤器执行完毕,才到资源里去
* */
chain.doFilter(req, resp);
}
public void destroy() {
}
}
4.4.3 创建拦截器 FilterB
image-20210305225107452
代码语言:javascript复制public class FilterB implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
System.out.println("FilterB执行了");
chain.doFilter(req, resp);
}
public void destroy() {
}
}
4.4.4 在xml配置拦截器
image-20210305225444671
代码语言:javascript复制 <!-- 过滤器链是按照xml从上到下的配置顺序进行逐步拦截的 -->
<!-- 配置过滤器FilterB -->
<filter>
<filter-name>FilterB</filter-name>
<filter-class>com.filter.FilterB</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterB</filter-name>
<url-pattern>/ServletA</url-pattern>
</filter-mapping>
<!-- 配置过滤器 FilterA -->
<filter>
<filter-name>FilterA</filter-name>
<filter-class>com.filter.FilterA</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterA</filter-name>
<url-pattern>/ServletA</url-pattern>
</filter-mapping>
4.4.5 访问 ServletA,查看拦截器链的执行顺序
image-20210305225533705
可以从结果来看,首先 FilterB 进行了拦截,然后 FilterA 拦截,最后访问到了 ServletA
五、Filter案例
5.1 统一网站编码
需求
tomcat8.5版本中已经将get请求的中文乱码解决了,但是post请求还存在中文乱码
浏览器发出的任何请求,通过过滤器统一处理中文乱码
5.1.1 需求分析
1592020764180
5.1.2 中文乱码的问题
首先我们来写注册页面、登录页面,还有对应的 Servlet,查看相关的中文乱码问题。
注册页面
image-20210306234155527
代码语言:javascript复制<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
</head>
<body>
<h1>注册页面</h1>
<form action="RegisterServlet" method="post">
<input type="text" name="username" placeholder="请输入要注册的用户名"> <br>
<input type="password" name="pwd" placeholder="请输入要注册的密码"> <br>
<input type="submit">
</form>
</body>
</html>
登录页面
image-20210306234847655
代码语言:javascript复制<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h2>登录页面</h2>
<form action="LoginServlet" method="post">
<input type="text" name="username" placeholder="请输入用户名"> <br>
<input type="password" name="pwd" placeholder="请输入的密码"> <br>
<input type="submit">
</form>
</body>
</html>
注册 RegisterServlet
image-20210307000228838
代码语言:javascript复制@WebServlet("/RegisterServlet")
public class RegisterServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取注册用户所需的字段参数
String username = request.getParameter("username");
String pwd = request.getParameter("pwd");
System.out.println("RegisterServlet:" username "," pwd);
}
}
登录 LoginServlet
image-20210307000809869
代码语言:javascript复制@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取登录参数
String username = request.getParameter("username");
String pwd = request.getParameter("pwd");
System.out.println("LoginServlet:" username "," pwd);
}
}
测试请求注册,查看中文乱码的情况
输入英文注册:
image-20210307001123826
点击提交,之后查看 RegisterServlet 的后台打印信息,如下:
image-20210307001225494
输入中文注册:
image-20210307001302046
image-20210307001324575
测试请求登录,查看中文乱码的情况
image-20210307001451657
image-20210307001518275
可以看到在 RegisterServlet 和 LoginServlet 都出现了中文乱码的请求参数问题,下面我们使用拦截器来统一解决。
5.1.2 定义解决中文乱码请求的过滤器 PostFilter
- 真实场景中,过滤器不会统一响应,因为响应的mime类型可能不同(有些返回html页面,有些返回JSON格式字符串)
image-20210307002746257
代码语言:javascript复制package com.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @author Aron.li
* @date 2021/3/7 0:16
*/
@WebFilter(urlPatterns = "/*") // 1. 使用 /* 拦截所有请求
public class PostFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截请求,设置中文编码格式,解决中文乱码问题
// 1. 获取请求 request
HttpServletRequest request = (HttpServletRequest) req;
// 2. 判断请求的方法类型
String method = request.getMethod();
if ("POST".equalsIgnoreCase(method)) {
//3. 当请求的方法为 POST,则设置编码格式
//解决请求参数的中文乱码
request.setCharacterEncoding("UTF-8");
}
// 4. 打印 req 和 request
System.out.println("req:" req);
System.out.println("request:" request);
// 放行
chain.doFilter(req, resp);
}
public void destroy() {
}
}
再次测试请求,查看是否还会出现中文乱码的情况:
- 请求登录
image-20210307002927068
image-20210307003002203
- 请求注册
image-20210307003025324
image-20210307003050291
5.2 非法字符拦截
需求
当用户发出非法言论的时候,提示用户言论非法警告信息
5.2.1 需求分析
1587622441357
5.2.2 代码实现
非法词库
创建一个 words.properties
的配置文件,如下:
image-20210307082253457
代码语言:javascript复制keyword=傻叉,大爷,二大爷的
“注意: properties文件编码问题 ”
image-20210307082502147
模拟发送留言的页面 bbs.jsp
image-20210307082937485
代码语言:javascript复制<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="WordServlet" method="get">
<input type="text" name="word" placeholder="请发言">
<input type="submit" value="发送"> <br>
</form>
</body>
</html>
接收留言信息的 WordServlet
image-20210307083315002
代码语言:javascript复制package com.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.util.List;
/**
* @author Aron.li
* @date 2021/3/7 8:30
*/
@WebServlet("/WordServlet")
public class WordServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 接收留言信息
String word = request.getParameter("word");
// 响应留言信息
response.setContentType("text/html;charset=utf-8");
response.getWriter().print(word);
}
}
此时我们启动服务,留个脏话来看看效果,如下:
image-20210307083724710
image-20210307083739197
可以看到脏话的词汇直接返回到浏览器,为了避免这种情况,下面我们用过滤器就行过滤
非法字符过滤器 WordsFilter
image-20210307084509519
代码语言:javascript复制package com.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
/**
* @author Aron.li
* @date 2021/3/7 8:38
*/
@WebFilter(urlPatterns = "/WordServlet")
public class WordsFilter implements Filter {
// 非法字符词库集合
List<String> words;
public void init(FilterConfig config) throws ServletException {
// 读取非法字符词库的单词
InputStream is = WordsFilter.class.getClassLoader().getResourceAsStream("words.properties");
Properties properties = new Properties();
try {
properties.load(is); // 加载输入流
String keyword = properties.getProperty("keyword"); // 读取属性
String[] strings = keyword.split(","); // 根据逗号 , 拆分字符串, 获取字符串数组
this.words = Arrays.asList(strings); // 将数组转为list集合
} catch (Exception e) {
e.printStackTrace();
}
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//1. 获取用户的发言
String word = req.getParameter("word");
//2. 是否包含敏感词
for (String dirty : this.words) {
//word字符串是否包含dirty
// 小瘪三 包含 瘪三, 返回true
if(word.contains(dirty)){
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().print("发言不友善,扣分10");
return;
}
}
//如果都不包含脏话, 放行
chain.doFilter(req, resp);
}
public void destroy() {
}
}
再次测试如下:
image-20210307084545234
image-20210307084657080
六、 Listener
1.1 概述
生活中的监听器
我们很多商场有摄像头,监视着客户的一举一动。如果客户有违法行为,商场可以采取相应的措施。
javaweb中的监听器
在我们的java程序中,有时也需要监视某些事情,一旦被监视的对象发生相应的变化,我们应该采取相应的操作。
监听web三大域对象:HttpServletRequest、HttpSession、ServletContext (创建和销毁)
场景
历史访问次数、统计在线人数、系统启动时初始化配置信息
监听器的接口分类
事件源 | 监听器接口 | 时机 |
---|---|---|
ServletContext | ServletContextListener | 上下文域创建和销毁 |
ServletContext | ServletContextAttributeListener | 上下文域属性增删改的操作 |
**HttpSession ** | HttpSessionListener | 会话域创建和销毁 |
**HttpSession ** | HttpSessionAttributeListener | 会话域属性增删改的操作 |
HttpServletRequest | ServletRequestListener | 请求域创建和销毁 |
HttpServletRequest | ServletRequestAttributeListener | 请求域属性增删改的操作 |
1.2 快速入门
监听器在web开发中使用的比较少,见的机会就更少了,下面我们使用ServletContextListenner来学习下监听器,因为这个监听器是监听器中使用率最高的一个,且监听器的使用方式都差不多。
我们使用这个监听器可以在项目启动和销毁的时候做一些事情,例如,在项目启动的时候加载配置文件。
步骤分析
代码语言:javascript复制1. 创建一个普通类,实现 ServletContextListener
2. 重写抽象方法
监听ServletContext创建
监听ServletContext销毁
3. 配置
web.xml
注解
① xml版本
创建 MyServletContextListener 如下:
image-20210307085949685
代码语言:javascript复制package com.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
*
* Listener
* 1. 创建一个类,实现相应接口(6个)
*
* 2. web.xml配置/注解配置
*
* 举例:
* ServletContextListener
上下文域创建和销毁
问题:
tomcat启动,加载项目时创建
tomcat关闭,销毁项目时消失
监听器: ServletContextListener
1. 创建
tomcat启动时,创建
(早于相应的域对象 ServletContext)
2. 运行
(监听对应的域对象 ServletContext)
ServletContext创建的时候, 这个监听器contextInitialized就会执行
ServletContext销毁的时候, 这个监听器contextDestroyed就会执行
3. 销毁
tomcat关闭时,销毁
(后于相应的域对象 ServletContext)
*
* @author Aron.li
* @date 2021/3/7 8:56
*/
public class MyServletContextListener implements ServletContextListener {
//当上下文域对象 初始化的时候
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("serlvetContext 创建了");
}
//当上下文域对象 销毁的时候
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("serlvetContext 销毁了");
}
}
配置 web.xml
image-20210307090205518
代码语言:javascript复制<!-- 配置监听器 -->
<listener>
<listener-class>com.listener.MyServletContextListener</listener-class>
</listener>
启动 tomcat,查看监听效果如下:
image-20210307090355698
停止 tomcat,查看监听效果如下:
image-20210307090434422
② 注解版本
代码语言:javascript复制@WebListener
public class MyServletContextListener implements ServletContextListener {
//当上下文域对象 初始化的时候
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("serlvetContext 创建了");
}
//当上下文域对象 销毁的时候
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("serlvetContext 销毁了");
}
}
监听属性增删改的监听器
代码语言:javascript复制import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.annotation.WebListener;
/*
* 监听: ServletContext的属性变化
* */
@WebListener
public class MyServletContextAttributeListener implements ServletContextAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("ServletContext 属性增加了");
}
@Override
public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("ServletContext 属性被移除了");
}
@Override
public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("ServletContext 属性替换了");
}
}
1.3 案例:模拟spring框架
需求:可以在项目启动时读取配置文件
1.3.1 在 xml 配置全局参数
image-20210307090803792
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!--全局配置参数-->
<context-param>
<param-name>configLocation</param-name>
<param-value>words.properties</param-value>
</context-param>
</web-app>
1.3.1 编写监听器读取配置信息
image-20210307091245950
代码语言:javascript复制package com.filter;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* 监听: ServletContext 的创建
*
* @author Aron.li
* @date 2021/3/7 9:09
*/
@WebListener
public class MySpringListener implements ServletContextListener {
// ServletContext对象创建就会执行
@Override
public void contextInitialized(ServletContextEvent sce) {
/*
* 1. 获取servletContext对象
* a. 小的域对象可以获取大的域对象(Servlet,Filter)
* b. 监听器: xxxEvent 就会 xxx 域对象
* */
ServletContext context = sce.getServletContext();
//2. 读取全局配置参数
String configLocation = context.getInitParameter("configLocation");
System.out.println("读取全局配置参数: " configLocation);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
1.4 案例:统计在线人数
需求
有用户使用网站,在线人数就 1;用户退出网站,在线人数就-1
代码语言:javascript复制# 分析:
1. 怎么判定用户在线还是离线?
a. 在线: 一个用户访问,应该给他生成一个session
假设第一个访问页面: index.jsp
(jsp底层: 默认获取session)
如果有其他页面: html之类的
(Filter进行拦截: request.getSession();)
b. 离线: 用户点击退出 , 把对应的session销毁 -> LogoutServlet
(如果用户直接X掉网页, 服务器默认等待30分钟,才会销毁session -> 时间太长了)
(长连接: 心跳包, 每隔30秒发一个请求 空包)
2. 监听session的创建和销毁
- > HttpSessionListener
1. 监听到创建 number 1
2. 监听到销毁 number - 1
3. number 存在哪里?
-> ServletContext 域对象 (全局)
4. 何时初始化number?
-> ServletContextListener
1. 监听到创建 : 初始化number,然后存进ServletContext
1.4.1 技术分析
使用 ServletContext域对象 存储在线总人数
使用 ServletContextListener监听器,在项目启动时,初始化总人数为0
使用 HttpSessionListener监听器,用户访问,人数 1,用户退出,人数-1
使用 LogoutServlet控制器,对当前会话的session销毁
1.4.2 需求分析
1587779813977
1.4.3 代码实现
初始化计数的监听器: InitCountServletContextListener
image-20210307091745955
代码语言:javascript复制@WebListener
public class InitCountServletContextListener implements ServletContextListener {
//监听到ServletContext创建就会执行
@Override
public void contextInitialized(ServletContextEvent sce) {
//初始化number,然后存进ServletContext
int number = 0;
sce.getServletContext().setAttribute("number",number);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
基于session计数的监听器: CountSessionListener
image-20210307092043325
代码语言:javascript复制package com.listener;
import javax.servlet.ServletContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* @author Aron.li
* @date 2021/3/7 9:18
*/
@WebListener
public class CountSessionListener implements HttpSessionListener {
// session创建,就会执行
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSession session = se.getSession(); // 获取HttpSession域对象
ServletContext servletContext = session.getServletContext(); // 获取 ServletContext
int number = (int) servletContext.getAttribute("number"); // 增加number值
number ;
servletContext.setAttribute("number", number);
System.out.println("有人上线了");
}
// session销毁,就会执行
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession(); // 获取HttpSession域对象
ServletContext servletContext = session.getServletContext(); // 获取 ServletContext
int number = (int) servletContext.getAttribute("number"); // 减少number值
number--;
servletContext.setAttribute("number", number);
System.out.println("有人下线了....");
}
}
显示在线人数的页面:count.jsp
image-20210307092320206
代码语言:javascript复制<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>当前在线人数:</h1>
<div> ${number} 人 </div>
<a href="LogoutServlet">退出登录</a>
</body>
</html>
用户退出: LogoutServlet
image-20210307092421035
代码语言:javascript复制@WebServlet("/LogoutServlet")
public class LogoutServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 销毁session
request.getSession().invalidate();
response.getWriter().write("logout");
}
}
1.4.4 测试效果
启动服务,访问 count.jsp 如下:
image-20210307092509074
多个浏览器访问:
image-20210307092622764
退出登录:
image-20210307092733541