JSP 学习笔记 | 六、Filter & Listener
前文:JSP 学习笔记 | 五、会话技术 Session & Cookie
前文:JSP 学习笔记 | 四、JSP标准标签库(JSTL)个人使用指南
Filter
Filter概述
Filter 表示过滤器,可以把对资源的请求拦截下来,从而实现一些特殊的功能。
如下图所示,浏览器可以访问服务器上的所有的资源(servlet、jsp、html等)

而在访问到这些资源之前可以使过滤器拦截来下,也就是说在访问资源之前会先经过 Filter,如下图

拦截器拦截到后可以做什么功能呢?
过滤器一般完成一些通用的操作。比如每个资源都要写一些代码完成某个功能,我们总不能在每个资源中写这样的代码吧,而此时我们可以将这些代码写在过滤器中,因为请求每一个资源都要经过过滤器。
我们之前做的品牌数据管理的案例中就已经做了登陆的功能,而如果我们不登录能不能访问到数据呢?我们可以在浏览器直接访问首页 ,可以看到 查询所有 的超链接

当我点击该按钮,居然可以看到品牌的数据

这显然和我们的要求不符。我们希望实现的效果是用户如果登陆过了就跳转到品牌数据展示的页面;如果没有登陆就跳转到登陆页面让用户进行登陆,要实现这个效果需要在每一个资源中都写上这段逻辑,而像这种通用的操作,我们就可以放在过滤器中进行实现。这个就是权限控制,以后我们还会进行细粒度权限控制。过滤器还可以做 统一编码处理、 敏感字符处理 等等…
Filter快速入门
开发步骤
进行 Filter 开发分成以下三步实现
定义类,实现 Filter接口,并重写其所有方法

配置Filter拦截资源的路径:在类上定义
@WebFilter注解。而注解的value属性值/*表示拦截所有的资源
在
doFilter方法中输出一句话,并放行
上述代码中的
chain.doFilter(request,response);就是放行,也就是让其访问本该访问的资源。
代码演示
创建一个项目,项目下有一个 hello.jsp 页面,项目结构如下:

pom.xml 配置文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>filter-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>80</port>
</configuration>
</plugin>
</plugins>
</build>
</project>
hello.jsp 页面内容如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Hello.JSP</title>
</head>
<body>
<h1>hello JSP~</h1>
</body>
</html>
我们现在在浏览器输入 http://localhost/filter-demo/hello.jsp 访问 hello.jsp 页面,这里是可以访问到 hello.jsp 页面内容的。
接下来编写过滤器。过滤器是 Web 三大组件之一,所以我们将 filter 创建在 com.riotian.web.filter 包下,起名为 FilterDemo
@WebFilter("/*")
public class FilterDemo implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("FilterDemo...");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
重启启动服务器,再次重新访问 hello.jsp 页面,这次发现页面没有任何效果,但是在 idea 的控制台可以输出了 FilterDemo...
上述效果说明 FilterDemo 这个过滤器的 doFilter() 方法执行了,但是为什么在浏览器上看不到 hello.jsp 页面的内容呢?这是因为在 doFilter() 方法中添加放行的方法才能访问到 hello.jsp 页面。那就在 doFilter() 方法中添加放行的代码
//放行
chain.doFilter(request,response);
再次重启服务器并访问 hello.jsp 页面,发现这次就可以在浏览器上看到页面效果。
FilterDemo 过滤器完整代码如下:
@WebFilter("/*")
public class FilterDemo implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("1.FilterDemo...");
//放行
chain.doFilter(request,response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
Filter执行流程

如上图是使用过滤器的流程,我们通过以下问题来研究过滤器的执行流程:
放行后访问对应资源,资源访问完成后,还会回到Filter中吗?
从上图就可以看出肯定 会 回到Filter中
如果回到Filter中,是重头执行还是执行放行后的逻辑呢?
如果是重头执行的话,就意味着
放行前逻辑会被执行两次,肯定不会这样设计了;所以访问完资源后,会回到放行后逻辑,执行该部分代码。
通过上述的说明,我们就可以总结Filter的执行流程如下:

接下来我们通过代码验证一下,在 doFilter() 方法前后都加上输出语句,如下

同时在 hello.jsp 页面加上输出语句,如下

执行访问该资源打印的顺序是按照我们标记的标号进行打印的话,说明我们上边总结出来的流程是没有问题的。启动服务器访问 Hello.jsp 页面,在控制台打印的内容如下:

以后我们可以将对请求进行处理的代码放在放行之前进行处理,而如果请求完资源后还要对响应的数据进行处理时可以在放行后进行逻辑处理。
Filter拦截路径配置
拦截路径表示 Filter 会对请求的哪些资源进行拦截,使用 @WebFilter 注解进行配置。如:@WebFilter("拦截路径")
拦截路径有如下四种配置方式:
- 拦截具体的资源:/index.jsp:只有访问index.jsp时才会被拦截
- 目录拦截:/user/*:访问/user下的所有资源,都会被拦截
- 后缀名拦截:*.jsp:访问后缀名为jsp的资源,都会被拦截
- 拦截所有:/*:访问所有资源,都会被拦截
通过上面拦截路径的学习,大家会发现拦截路径的配置方式和 Servlet 的请求资源路径配置方式一样,但是表示的含义不同。
过滤器链
概述
过滤器链是指在一个Web应用,可以配置多个过滤器,这多个过滤器称为过滤器链。
如下图就是一个过滤器链,我们学习过滤器链主要是学习过滤器链执行的流程

上图中的过滤器链执行是按照以下流程执行:
- 执行
Filter1的放行前逻辑代码 - 执行
Filter1的放行代码 - 执行
Filter2的放行前逻辑代码 - 执行
Filter2的放行代码 - 访问到资源
- 执行
Filter2的放行后逻辑代码 - 执行
Filter1的放行后逻辑代码
以上流程串起来就像一条链子,故称之为过滤器链。
代码演示
编写第一个过滤器
FilterDemo,配置成拦截所有资源@WebFilter("/*")
public class FilterDemo implements Filter { @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //1. 放行前,对 request数据进行处理
System.out.println("1.FilterDemo...");
//放行
chain.doFilter(request,response);
//2. 放行后,对Response 数据进行处理
System.out.println("3.FilterDemo...");
} @Override
public void init(FilterConfig filterConfig) throws ServletException {
} @Override
public void destroy() {
}
}
编写第二个过滤器
FilterDemo2,配置炒年糕拦截所有资源@WebFilter("/*")
public class FilterDemo2 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//1. 放行前,对 request数据进行处理
System.out.println("2.FilterDemo...");
//放行
chain.doFilter(request,response);
//2. 放行后,对Response 数据进行处理
System.out.println("4.FilterDemo...");
} @Override
public void init(FilterConfig filterConfig) throws ServletException {
} @Override
public void destroy() {
}
}修改
hello.jsp页面中脚本的输出语句<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>hello JSP~</h1>
<%
System.out.println("3.hello jsp");
%>
</body>
</html>
启动服务器,在浏览器输入
http://localhost/filter-demo/hello.jsp进行测试,在控制台打印内容如下

从结果可以看到确实是按照我们之前说的执行流程进行执行的。
Q:请思考一下上面代码中为什么是先执行 FilterDemo ,后执行 FilterDemo2 呢?
A:我们现在使用的是注解配置Filter,而这种配置方式的优先级是按照过滤器类名(字符串)的自然排序。比如有如下两个名称的过滤器 : BFilterDemo 和 AFilterDemo 。那一定是 AFilterDemo 过滤器先执行。
案例
需求分析:访问服务器资源时,需要先进行登录验证,如果没有登录,则自动跳转到登录页面
我们要实现该功能是在每一个资源里加入登陆状态校验的代码吗?显然是不需要的,只需要写一个 Filter ,在该过滤器中进行登陆状态校验即可。而在该 Filter 中逻辑如下:

代码实现
创建Filter
在
brand-demo工程创建com.itheima.web.filter包,在该下创建名为LoginFilter的过滤器编写逻辑代码
在
doFilter()方法中编写登陆状态校验的逻辑代码。我们首先需要从
session对象中获取用户信息,但是ServletRequest类型的 requset 对象没有获取 session 对象的方法,所以此时需要将 request对象强转成HttpServletRequest对象。HttpServletRequest req = (HttpServletRequest) request;
然后完成以下逻辑
- 获取Session对象
- 从Session对象中获取名为
user的数据 - 判断获取到的数据是否是 null
- 如果不是,说明已经登陆,放行
- 如果是,说明尚未登陆,将提示信息存储到域对象中并跳转到登陆页面
代码如下:
@WebFilter("/*")
public class LoginFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
} public void destroy() {
} @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
// 1. 判断 Session 是否有 User 对象
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession();
Object user = session.getAttribute("user"); // 2. 判断 User 是否为null
if (user != null) {
// 登录过了
// 放行
// 注意登录页面相关的,如 CSS,JS 也要放行
chain.doFilter(request, response);
} else {
// 没有登录,存储提示信息,跳转登录页面
req.setAttribute("login_msg", "您尚未登录");
req.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
}
测试并抛出问题
在浏览器上输入
http://localhost:8080/brand-demo/,可以看到缺少 CSS 样式的问题,但确实是跳转到登陆页面了,但是登陆页面为什么展示成这种效果了呢?问题分析及解决
因为登陆页面需要
css/login.css这个文件进行样式的渲染,下图是登陆页面引入的css文件图解
而在请求这个css资源时被过滤器拦截,就相当于没有加载到样式文件导致的。解决这个问题,只需要对所以的登陆相关的资源进行放行即可。还有一种情况就是当我没有用户信息时需要进行注册,而注册时也希望被过滤器放行。
综上,我们需要在判断session中是否包含用户信息之前,应该加上对登陆及注册相关资源放行的逻辑处理
过滤器完整代码
@WebFilter("/*")
public class LoginFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
} public void destroy() {
} @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) request;
//判断访问资源路径是否和登录注册相关
String[] urls = {"/login.jsp","/imgs/","/css/","/loginServlet","/register.jsp","/registerServlet","/checkCodeServlet"};
// 获取当前访问的资源路径
String url = req.getRequestURL().toString(); //循环判断
for (String u : urls) {
if(url.contains(u)){
//找到了
//放行
chain.doFilter(request, response);
//break;
return;
}
} // 1. 判断 Session 是否有 User 对象
HttpSession session = req.getSession();
Object user = session.getAttribute("user"); // 2. 判断 User 是否为null
if (user != null) {
// 登录过了
// 放行
// 注意登录页面相关的,如 CSS,JS 也要放行
chain.doFilter(request, response);
} else {
// 没有登录,存储提示信息,跳转登录页面
req.setAttribute("login_msg", "您尚未登录");
req.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
}
Listener
Listener 概述
Listener 表示监听器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。
监听器可以监听就是在
application,session,request三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。request 和 session 我们学习过。而
application是ServletContext类型的对象。ServletContext代表整个web应用,在服务器启动的时候,tomcat会自动创建该对象。在服务器关闭时会自动销毁该对象。
分类
JavaWeb 提供了8个监听器:

这里面只有 ServletContextListener 这个监听器后期我们会接触到,ServletContextListener 是用来监听 ServletContext 对象的创建和销毁。
ServletContextListener 接口中有以下两个方法
void contextInitialized(ServletContextEvent sce):ServletContext对象被创建了会自动执行的方法void contextDestroyed(ServletContextEvent sce):ServletContext对象被销毁时会自动执行的方法
代码演示
我们只演示一下 ServletContextListener 监听器
- 定义一个类,实现
ServletContextListener接口 - 重写所有的抽象方法
- 使用
@WebListener进行配置
代码如下:
@WebListener
public class ContextLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
//加载资源
System.out.println("ContextLoaderListener...");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
//释放资源
}
}
启动服务器,就可以在启动的日志信息中看到 contextInitialized() 方法输出的内容,同时也说明了 ServletContext 对象在服务器启动的时候被创建了。
JSP 学习笔记 | 六、Filter & Listener的更多相关文章
- Spring Boot 学习笔记(六) 整合 RESTful 参数传递
Spring Boot 学习笔记 源码地址 Spring Boot 学习笔记(一) hello world Spring Boot 学习笔记(二) 整合 log4j2 Spring Boot 学习笔记 ...
- JSP学习笔记
JSP学习笔记 Jsp网页主要分为Elements与Template Data两部分. Template Data:JSP Container不处理的部分,例如HTML内容 Elements:必须经由 ...
- JSP学习笔记(三):简单的Tomcat Web服务器
注意:每次对Tomcat配置文件进行修改后,必须重启Tomcat 在E盘的DATA文件夹中创建TomcatDemo文件夹,并将Tomcat安装路径下的webapps/ROOT中的WEB-INF文件夹复 ...
- java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)
java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...
- java web jsp学习笔记--概述-常用语法,指令,动作元素,隐式对象,域对象
JSP学习笔记 1.什么是jsp JSP全称是Java Server Pages,它和servle技术一样,都是SUN公司定义的一种用于开发动态web资源的技术.JSP/Servlet规范.JS ...
- Learning ROS for Robotics Programming Second Edition学习笔记(六) indigo xtion pro live
中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...
- Typescript 学习笔记六:接口
中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...
- python3.4学习笔记(六) 常用快捷键使用技巧,持续更新
python3.4学习笔记(六) 常用快捷键使用技巧,持续更新 安装IDLE后鼠标右键点击*.py 文件,可以看到Edit with IDLE 选择这个可以直接打开编辑器.IDLE默认不能显示行号,使 ...
- Go语言学习笔记六: 循环语句
Go语言学习笔记六: 循环语句 今天学了一个格式化代码的命令:gofmt -w chapter6.go for循环 for循环有3种形式: for init; condition; increment ...
- 【opencv学习笔记六】图像的ROI区域选择与复制
图像的数据量还是比较大的,对整张图片进行处理会影响我们的处理效率,因此常常只对图像中我们需要的部分进行处理,也就是感兴趣区域ROI.今天我们来看一下如何设置图像的感兴趣区域ROI.以及对ROI区域图像 ...
随机推荐
- mySql中使用命令行建表基本操作
一:打开命令行启动mysql服务 注意事项:应该使用管理员身份打开命令行键入命令:net start mysql (鼠标右键使用管理员身份打开),否则会出现拒绝访问报错. 二:登陆数据库 登陆命令为& ...
- Go笔记(2)-5种运算符总结
运算符 (1)算术运算符 (2)关系运算符 (3)逻辑运算符 (4)位运算符 (5)赋值运算符
- 校验码——循环校验码CRC,海明校验码
- OkHttp3发送http请求
导入依赖 <!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp --> <dependency> ...
- MybatisPlus条件查询方法全解
1.是什么? MybatisPlus通过条件构造器可以组装复杂的查询条件,写一些复杂的SQL语句,从而简化我们的开发提升我们的开发效率 # 可以简单的理解为就是我们写SQL语句时where后面的条件 ...
- 2024年 为什么不建议新人学习ABAP
引言 每个应届生都希望自己有良好的职业发展,当他们发现前路难通时,便会寻找更好的出路. "转码"曾经是个很火热的话题.在互联网行业高速发展的年代,转行学代码,入职大厂,升职加薪,是 ...
- ElasticSearch快照备份、还原
快照备份 备份和还原的前提:在配置文件elasticsearch.yml中设置path.repo path.repo: ["D:\\elasticsearch-6.8.23\\elastic ...
- 文心一言 VS 讯飞星火 VS chatgpt (34)-- 算法导论5.3 1题
一.Marceau 教授不同意引理 5.5 证明中使用的循环不变式.他对第1次送代之前循环不变式是否为真提出质疑.他的理由是,我们可以很容易宣称一个空数组不包含0排列.因此一个空的子数组包含一个0排列 ...
- SaaS 营销,如何利用 RPA 实现自动化获客?
大家好,这次给大家带来如何利用 RPA 实现自动化获客. 一.RPA 是什么?难吗? RPA 对大家来说,可能挺陌生的,其实它很简单. Robotic Process Automation(简称 RP ...
- 基于AI的架构优化:创新数据集构造法提升Feature envy坏味道检测与重构准确率
本文分享自华为云社区<华为云基于AI实现架构坏味道重构取得业界突破,相应文章已被软工顶会FSE 2023收录>,作者: 华为云软件分析Lab. 基于AI技术实现架构坏味道检测与重构建议是当 ...