过滤器 Filter
1. 为什么要过滤器
我们先从一个业务说起:
如下图,假如我们有一个登录验证的业务,那么一般的思路就是当浏览器访问 login. jsp 时,保存一个 session,若满足相关登录条件(session 中的数据就是条件)就放行,允许其访问服务器的其他资源。

解读:
- 可以看出若要实现上述的业务,我们不得不在每一个地方都设置一个权限检验的代码,这样十分的繁杂,怎么优化呢?
- 一个很容易想到的思路就是将其封装起来,写成一个权限函数。这样虽然简洁了不少,但对于程序效率几乎没有任何提升;
- 但倘若能有一个方法能够实现所有文件的权限管理就好了,统一调度,统一管理,无疑能解决上述所有的问题,这就是过滤器的基本思想;
2. 基本介绍
- 过滤器就是一个中央控制器,通过程序员自己 DIY 的一系列逻辑来统一控制某一用户能够访问的资源范围;
- Filter 过滤器它是 JavaWeb 的三大组件之一 (Servlet 程序、Listener 监听器、Filter 过滤器);
- Filter 过滤器是 JavaEE 的规范,是接口,这就是我们实现控制的逻辑载体;
- Filter 过滤器作用:拦截请求,过滤响应。
2.1 基本原理
一图胜千言:

解读:
- 过滤器是 Tomcat 调用的;
2.2 入门示例
我们已经说了那么多原理了。是时候以新的视角来看待开头的业务需求了。
还是一图胜千言:

解读:
- 若浏览器直接根据 url 访问限制资源,就会被过滤器“发现”,强制其走过滤器的道路,成功的达到了限制的目的;
代码实例
public class ManageFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("ManageFilter init被调用...");
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
System.out.println("ManageFilter doFilter() 被调用=" + (++count));
//到每次调用该filter时,doFilter就会被调用
//如果这里,没有调用继续请求的方法filterChain.doFilter(),则就停止
//如果继续访问目标资源-> 等价于放行
//在调用过滤器前,servletRequest对象=request已经被创建并封装
//所以:我们这里就可以通过servletRequest获取很多信息, 比如访问url , session
//比如访问的参数 ... 就可以做事务管理,数据获取,日志管理等
//获取到session
//可以继续使用 httpServletRequest 方法.
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
System.out.println("输入密码=" + httpServletRequest.getParameter("password"));
HttpSession session = httpServletRequest.getSession();
//获取username session对象, 还可以继续使用
Object username = session.getAttribute("username");
if (username != null) {
//1. 继续访问目标资源url
//2. servletRequest 和 servletResponse 对象会传递给目标资源/文件
System.out.println("servletRequest=" + servletRequest);
System.out.println("日志信息==");
System.out.println("访问的用户名=" + username.toString());
System.out.println("访问的url=" + httpServletRequest.getRequestURL());
System.out.println("访问的IP=" + httpServletRequest.getRemoteAddr());
filterChain.doFilter(servletRequest, servletResponse);
} else {//说明没有登录过..回到登录页面
servletRequest.getRequestDispatcher("/login.jsp").
forward(servletRequest, servletResponse);
}
}
@Override
public void destroy() {
//当filter被销毁时,会调用该方法
System.out.println("ManageFilter destroy()被调用..");
}
}
解读:
- filter 在 web 顶目启动时,由 tomcat 来创建 filter 实例,只会创建一个;
- 会调用 filter 默认的无参构造器,同时会调用 init 方法,只会调用一次;
- 在创建 filter实例时,同时会创建一个 FilterConfig 对象,并通过 init 方法传入;
- 通过 FilterConfig 对象,程序员可以获取该 filter, 的相关配置信息;
- 当一个http请求和该filter的urL-patter,匹配时,就会调用doFilter方法;
- 在调用doFilter方法时,tomcat会同时创建ServletRequest和ServletResponse和FilterChain对象,并通过doFilter传入;
- 如果后面的请求目标资源(jsp,servlet.·)会使用到request,和response,那么会继续传递;
2.3 过滤器的 web. xml 配置
2.3.1 基本说明
以下是配置文件截取的一部分:
<filter>
<filter-name>topicFilter</filter-name>
<filter-class>com.yelanyanyu.homework.topicFilter</filter-class>
<init-param>
<param-name>forbiddenWords</param-name>
<param-value>苹果,香蕉</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>topicFilter</filter-name>
<url-pattern>/homework/*</url-pattern>
</filter-mapping>
解读:
- filter 的配置和 Servlet 十分的相似。
<filter-name>就代表着 filter 的名字,Tomcat 通过名字得到<url-pattern>,假如 tomcat 发现你的请求的资源在/homework目录下,就会强迫你走名字为 topicFilter 的过滤器(底层通过<filter-class>反射生成 filter 实例); <init-param>是初始化参数,常用来存储一些经常用的值,例如文本验证中违禁词;
2.3.2 url-pattern 说明
- url-pattern : Filter 的拦截路径, 即浏览器在请求什么位置的资源时,过滤器会进行拦截过滤;
- 精确匹配 /a.jsp 对应的请求地址
http://ip [域名]: port/工程路径/a.jsp会拦截; - 目录匹配 /manage/对应的请求地址
http://ip [域名]: port/工程路径/manage/xx, 即 web 工程 manage 目录下所有资源会拦截; - 后缀名匹配
*.jsp后缀名可变,比如*.action *.do等等对应的请求地址http://ip [域名]: port/工程路径/xx.jsp, 后缀名为 . jsp 请求会拦截;
5、Filter 过滤器它只关心请求的地址是否匹配,不关心请求的资源是否存在;
2.4 生命周期
2.4.1 init 初始化
当 web 工程启动的时候,会执行构造器和 init 方法,ManageFilter 实例常驻内存。
2.4.2 执行阶段
当 HTTP 请求到 TOMCAT,如果匹配到 Filter 的 url-pattern,doFilter () 方法就会被调用。
2.4.3 结束阶段
当停止 web 工程时,调用 destroy() 方法,销毁 Filter 实例。
2.5 FIlterConfig 类
2.5.1 基本介绍
- FilterConfig 是 Filter 过滤器的配置类;
- Tomcat 每次创建 Filter 的时候,也会创建一个 FilterConfig 对象,这里包含了 Filter 配置文件的配置信息;
- FilterConfig 对象作用是获取 filter 过滤器的配置内容;
2.5.2 方法一览

解读:
- getFilterName (),获取过滤器名称;
- getServletContext (),获取 ServletContext 对象;
- getInitParameterNames (),获取所有初始化参数,返回一个枚举;
- getInitParameter ("ip"),获取值为 ip 的初始化参数;
2.5.3 应用实例
public class MyFilterConfig implements Filter {
private String ip; //从配置获取的ip
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MyFilterConfig init() 被调用..");
//获取过滤器名称
String filterName = filterConfig.getFilterName();
ip = filterConfig.getInitParameter("ip");
ServletContext servletContext = filterConfig.getServletContext();
//获取到该filter所有的配置参数(init-params)名
Enumeration<String> initParameterNames =
filterConfig.getInitParameterNames();
//遍历枚举
while (initParameterNames.hasMoreElements()) {
System.out.println("名字=" + initParameterNames.nextElement());
}
System.out.println("filterName= " + filterName);
System.out.println("ip= " + ip);
System.out.println("servletContext= " + servletContext);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//通过forbidden ip 来进行控制
//先获取到访问ip
String remoteAddr = servletRequest.getRemoteAddr();
if(remoteAddr.contains(ip)) {
System.out.println("封杀该网段..");
servletRequest.getRequestDispatcher("/login.jsp").
forward(servletRequest,servletResponse);
return; //直接返回
}
//继续访问目标资源
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
2.6 过滤器链 FilterChain
在处理某些复杂业务时,一个过滤器不够,可以设计多个过滤器共同完成过滤任务,形成过滤器链。
原理分析
如下图:

解读:
- 类似于递归的调用,假若浏览器请求 url 满足这 n 个过滤器的 url-pattern,在执行完每个过滤器的前置程序后就会调用 doFilter 方法,进入下一个过滤器;
- 当执行到最后一个过滤器的 doFilter 方法之后(已经访问到了相关资源了),会执行后置代码,返回到第 n-1 个过滤器,继续执行其后置程序;
- 第 n-1 个过滤器的后置程序执行完之后就会执行第 n-2 个的,以此类推直到执行完 A 过滤器的后置代码;
- 最后 tomcat 把相关资源返回给浏览器;
- tomcat 怎么知道那么过滤器应该先匹配哪一个(顺序问题)?根据 web. xml 文件中过滤器配置的相对位置决定谁被匹配,越接近文件首的越先被匹配到;
2.7 注意事项
- 多个 filter 和目标资源在一次 http 请求,在同一个线程中;
- 当一个请求 url 和 filter 的 url-pattern 匹配时, 才会被执行, 如果有多个匹配上,就会顺序执行,形成一个 filter 调用链 (底层可以使用一个数据结构搞定) ;
- 多个 filter 共同执行时, 因为是一次 http 请求, 使用同一个 request 对象;
- 多个 filter 执行顺序,和 web. xml 配置顺序保持一致;
- chain. doFilter (req, resp) 方法将执行下一个过滤器的 doFilter 方法, 如果后面没有过滤器,则执行目标资源;
- 小结:注意执行过滤器链时, 顺序是 (用前面的案例分析) Http 请求 -> A 过滤器 dofilter () -> A 过滤器前置代码 -> A 过滤器 chain. doFilter () -> B 过滤器 dofilter () -> B 过滤器前置代码 -> B 过滤器 chain. doFilter () -> 目标文件 -> B 过滤器后置代码 -> A 过滤器后置代码 -> 返回给浏览器页面/数据;
3. 应用实例-关键词过滤
3.1 需求
- 浏览评论页面 topic. jsp,输入相关评论,可以在 showTopic. jsp 显示评论内容;
- 如果发表的评论内容, 有关键字比如 "苹果" "香蕉", 就返回 topic. jsp, 并提示有禁用词;
- 要求发表评论到 showTopic. jsp 时,经过过滤器的处理;
- 禁用词, 配置在过滤器, 在启动项目时动态的获取, 注意处理中文;
3.2 代码实现
配置如下过滤器:
<filter>
<filter-name>topicFilter</filter-name>
<filter-class>com.yelanyanyu.homework.topicFilter</filter-class>
<init-param>
<param-name>forbiddenWords</param-name>
<param-value>苹果,香蕉</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>topicFilter</filter-name>
<url-pattern>/homework/*</url-pattern>
</filter-mapping>
topic. jsp 页面:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%--
Created by IntelliJ IDEA. Date: 2022/10/27 Time: 21:11 To change this template use File | Settings | File Templates.--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false"%>
<html>
<head>
<title>评论</title>
</head>
<body>
<h1>发表对阿凡达电影的评论</h1><br>
过滤词:苹果,香蕉.${requestScope.get("errorInfo")}<br>
<form method="post" action="<%=request.getContextPath()%>/homework/showTopic.jsp">
内容:<input type="text" name="topic">
<input type="submit" value="提交">
</form>
</body>
</html>
showTopic. jsp 页面
<%--
Created by IntelliJ IDEA. Date: 2022/10/27 Time: 21:11 To change this template use File | Settings | File Templates.--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>showTopic</title>
</head>
<body>
<%
System.out.println("showTopic被调用~");
%>
<h1>你输入的文章内容为:</h1><br>
[<%=request.getParameter("topic")%>]
</body>
</html>
过滤器 topicFilter. java:
package com.yelanyanyu.homework;
import com.sun.deploy.net.HttpRequest;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @author yelanyanyu@zjxu.edu.cn
* @version 1.0
* */
public class topicFilter implements Filter {
private String[] forbiddenWords;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
/*
得到过滤器的初始化参数(违禁词列表)
*/
String forbiddenWord = filterConfig.getInitParameter("forbiddenWords");
forbiddenWords = forbiddenWord.split(",");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
request.setCharacterEncoding("utf-8");
String topic = request.getParameter("topic");
System.out.println("topic= " + topic);
//没有评论或者评论为空就打回评论页面
//topic为null同时防止用户直接访问限制资源
if (topic == null || "".equals(topic)) {
request.getRequestDispatcher("/topic.jsp").forward(servletRequest, servletResponse);
return;
}
//topic有禁用词就设置request参数,从而在login.jsp显示相关错误信息
for (String forbiddenWord : forbiddenWords) {
if (topic.contains(forbiddenWord)) {
request.setAttribute("errorInfo", "有禁用词!!!");
request.getRequestDispatcher("/topic.jsp").forward(servletRequest, servletResponse);
return;
}
}
//若什么都不违反就放行到showTopic.jsp 页面
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}

浙公网安备 33010602011771号