Filter 介绍
过滤器,顾名思义就是对事物进行过滤的,在Web中的过滤器,当然就是对请求进行过滤,对web服务器管理的所有web资源,我们使用过滤器,就可以对请求进行拦截,然后做相应的处理,实现许多特殊功能。如登录控制,权限管理,过滤敏感词汇等.
Servlet
API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过
Filter
技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,Filter接口源代码:
1 2 3 4 5 6
| public abstract interface Filter{ public abstract void init(FilterConfig paramFilterConfig) throws ServletException; public abstract void doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain paramFilterChain) throws IOException, ServletException; public abstract void destroy(); }
|
Filter接口中有一个doFilter方法,当我们编写好Filter,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:
- 调用目标资源之前,让一段代码执行。 -
是否调用目标资源(即是否让用户访问web资源)。
web服务器在调用doFilter
方法时,会传递一个filterChain
对象进来,filterChain
对象是filter接口中最重要的一个对象,它也提供了一个
doFilter
方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web资源的service方法,即web资源就会被访问,否则web资源不会被访问。
过滤器原理
当我们使用过滤器时,过滤器会对游览器的请求进行过滤,过滤器可以动态的分为3个部分
1.放行之前的代码,2.放行,3.放行后的代码
- 第一部分代码会对游览器请求进行第一次过滤,然后继续执行
- 第二部分代码就是将游览器请求放行,如果还有过滤器,那么就继续交给下一个过滤器
- 第三部分代码就是对返回的Web资源再次进行过滤处理
我们使用过滤器,也就是说,不止请求会经过过滤器,我们的响应也会经过过滤器。
img
img
使用过滤器
我们创建Filter,只需要继承Filter接口就行。
1 2 3 4 5 6 7 8 9 10 11 12 13
| import javax.servlet.*; import java.io.IOException;
@WebFilter("/*") public class MyFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("对request进行过滤"); filterChain.doFilter(servletRequest,servletResponse); System.out.println("对response进行过滤"); } }
|
我们实现了一个 doFilter
方法,这个方法就是我们写过滤代码的地方,具体逻辑就是和上面介绍的过滤器原理一样的。
我简单介绍下上面的代码,WebFilter("/*")
表示对所有请求进行过滤,而在doFilter中的放行代码,也就是filterChain.doFilter(servletRequest,servletResponse);
这行代码就是对拦截进行放行
过滤器并不会管资源是否存在,而只会对配置的拦截路径进行拦截。拦截不仅会对请求进行拦截,而且还会对相应进行拦截。
配置过滤器拦截路径
配置Filter的拦截路径有2种方式,一种是注解,一种是xml方式,我们分别进行讲解
注解方式
我们如果使用注解来进行配置,那么我们就需要使用 @WebFilter ,直接看该注解的源码
img
img
- filterName:该filter的名字
- initParams:初始化参数
- displayName:filter显示名称
- servletNames:指定对哪些servlet进行过滤
- asyncSupported:是否支持异步模式
- urlPatterns:指定拦截路径
- value:指定拦截路径
urlPatterns和value是一样的。urlPatterns和value只能配置一个,不能两个都配置,两个都配置就会报错。
对于使用@WebFilter,里面的多个参数用
, 进行分隔。
如果我们仅仅需要配置一个拦截路径,那么我们可以直接简写@WebLister(“拦截路径”),如@WebFilter(“/*”)就是拦截所有请求。
1 2
| @WebFilter(value = {"/login"}, filterName = "filtersss") public class CharSetFilter implements Filter {
|
多个参数配置,需要分隔
xml 方式
xml方式可以说是和Servlet使用xml配置方式一样了
1 2 3 4 5 6 7 8
| <filter> <filter-name>myFilter</filter-name> <filter-class>com.ergoutree.filter.MyFilter</filter-class> </filter> <filter-mapping> <filter-name>myFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
把注解换成了xml标签来配置,里面属性都是一样的,这个和Servlet的配置方式基本一样
过滤器生命周期
Filter 的生命周期和 Servlet 也十分相似
创建一个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import javax.servlet.*; import java.io.IOException; public class LifeCycleFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { } @Override public void destroy() { } }
|
Filter有3个阶段,分别是初始化,拦截和过滤,销毁。
- 初始化阶段:当服务器启动时,我们的服务器(Tomcat)就会读取配置文件,扫描注解,然后来创建我们的Filter。
- 拦截和过滤阶段:只要请求资源的路径和拦截的路径相同,那么过滤器就会对请求进行过滤,这个阶段在服务器运行过程中会一直循环。
- 销毁阶段:当服务器(Tomcat)关闭时,服务器创建的Filter也会随之销毁。
Filter的三个阶段就对应着Filter的3个方法,init 方法会在 Filter
创建时调用,doFilter 方法会在请求和拦截匹配时调用,destroy 方法会在
Filter 销毁时调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| @WebFilter(value = {"/login"}, filterName = "filtersss") public class CharSetFilter implements Filter { private static final Logger LOGGER = Logger.getLogger(CharSetFilter.class.getName()); private static final String CHARSET = "UTF-8";
public void init(FilterConfig config) throws ServletException { System.out.println("LoginOccasion过滤器初始化完成 - " + new Date()); }
public void destroy() { System.out.println("LoginOccasion过滤器被销毁 - " + new Date()); }
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { System.out.println("LoginOccasion过滤器: 预处理开始");
String ipAddress = request.getRemoteAddr(); System.out.println("请求来自IP: " + ipAddress + " - " + new Date());
request.setCharacterEncoding(CHARSET); response.setCharacterEncoding(CHARSET); response.setContentType("text/html; charset=" + CHARSET);
if(request instanceof HttpServletRequest) { HttpServletRequest httpRequest = (HttpServletRequest) request; System.out.println("访问的URL: " + httpRequest.getRequestURL()); System.out.println("HTTP方法: " + httpRequest.getMethod()); }
long startTime = System.currentTimeMillis();
chain.doFilter(request, response);
long endTime = System.currentTimeMillis(); System.out.println("请求处理耗时: " + (endTime - startTime) + "毫秒"); System.out.println("LoginOccasion过滤器: 后处理完成"); } }
|
FilterConfig和FilterChain
FilterConfig
和FilterConfig
这2个对象是由服务器(Tomcat)在创建和调用Filter对象时所传入的,这2个对象十分有用,FilterConfig
对象可以读取我们配置的初始参数,FilterChain
可以实现多个Filter之间的连接。
FilterConfig
img
img
img
img
里面的方法就4个,下面我们分别进行讲解
- getFilterName():获取filter的名称
- getServletContext():获取ServletContext
- getInitparamter(String var1):获取配置的初始参数的值
- getInitParamterNames():获取配置的所有参数名称
实际例子
我们在init方法中使用FilterConfig来读取配置的数据库的信息,然后输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import javax.servlet.*; import java.io.IOException; import java.util.Enumeration; public class MyFilterConfig implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("-----------获取全部key:value------------"); Enumeration<String> names = filterConfig.getInitParameterNames(); while (names.hasMoreElements()) { String name = names.nextElement(); System.out.println(name+" = "+filterConfig.getInitParameter(name)); } System.out.println("-----------end.....------------"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { } @Override public void destroy() { } }
|
xml 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <filter> <filter-name>myFilterConfig</filter-name> <filter-class>com.clucky.filter.MyFilterConfig</filter-class> <init-param> <param-name>driver</param-name> <param-value>com.mysql.jdbc.Driver</param-value> </init-param> <init-param> <param-name>url</param-name> <param-value>jdbc:mysql://localhost:3306/equip_employ_manage?serverTimezone=GMT</param-value> </init-param> <init-param> <param-name>username</param-name> <param-value>root</param-value> </init-param> <init-param> <param-name>password</param-name> <param-value>root</param-value> </init-param> </filter> <filter-mapping> <filter-name>myFilterConfig</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
img
img
我们使用FilterConfig提供的方法就成功实现了功能,FilterConfig就是用来读取配置文件的。
FilterChain
在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。web服务器根据Filter在
web.xml 文件中的注册顺序,决定先调用哪个Filter,当第一个 Filter 的
doFilter 方法被调用时,web服务器会创建一个代表 Filter 链的FilterChain
对象传递给该方法。在 doFilter 方法中,开发人员如果调用了 FilterChain
对象的 doFilter 方法,则 web 服务器会检查 FilterChain
对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。
img
img
img
img
我们查看类图,可以发现 FilterChain
就只有一个方法,其实这个方法就是用来对拦截进行放行的,如果有多个拦截器,那么就会继续调用下一个Filter进行拦截。doFilter
方法需要传入个参数,一个是 ServletRequest ,一个是 ServletResponse
参数,这个直接传入进行。
Tomcat 在调用过滤器时,默认就会传入 Request 和
Response,这个参数封装了请求和响应,我们直接使用就行。ServletResquest 和
ServletResponse 可以直接强转成 HttpServletRequest 和
HttpServletResponse,然后使用相应的方法。
将ServletRequest转成HttpServletRequest
img
img
应用实例
我们前面一直都是一个Filter,现在我们来配置2个Filter,通过FilterChain来进行多个过滤。
第一个 Filter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter("/*") public class Filter01 implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("调用过滤器01对请求进行过滤~~~~"); filterChain.doFilter(servletRequest,servletResponse); System.out.println("调用过滤器01对响应进行过滤~~~~"); } @Override public void destroy() { } }
|
第二个Filterl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter("/*") public class Filter02 implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("调用过滤器02对请求进行过滤~~~~"); filterChain.doFilter(servletRequest,servletResponse); System.out.println("调用过滤器02对响应进行过滤~~~~"); } @Override public void destroy() { } }
|
启动服务器,访问8080,查看控制台输出。
img
img
我们可以看见Filter01先进行过滤,然后交给Filter02,然后访问资源,然后Filter02对响应进行过滤,然后Filter01对响应进行过滤。图示如下:
img
img
多个 Filter 的执行顺序
上面我们配置了2个过滤器,那么我们怎么知道那个过滤器先执行呢?
- 如果我们是在web.xml中配置的过滤器,那么过滤器的执行顺序就是在web配置的顺序,配置在上面那么就会先执行。
- 如果我们是使用@WebFilter进行配置的,那么执行顺序就是字符比较顺序来执行,例如有2个过滤器,一个是AFilter,一个是BFilter,那么AFilter就会先执行。
- 如果注解和xml混用,那么在web.xml中配置的会先执行。
Filter应用实例—实现敏感词汇过滤
我们写一个评论页面,可以进行评论,如果评论中含有我们定义的敏感词汇,那么我们就进行过滤,使用星号来进行代替。
首先是一个简单的 jsp 页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%-- 引入JSTL核心标签库,简化页面数据处理(需确保项目已引入JSTL依赖) --%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>评论系统 - 敏感词汇过滤</title> <style> .container { width: 800px; margin: 50px auto; padding: 20px; border: 1px solid #eee; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.05); } textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; resize: vertical; margin-bottom: 15px; } input[type="submit"] { padding: 8px 20px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; } input[type="submit"]:hover { background-color: #45a049; } .result { margin-top: 20px; padding: 15px; border-radius: 4px; background-color: #f9f9f9; } .success { color: #4CAF50; font-weight: bold; } .warning { color: #f44336; font-weight: bold; } </style> </head> <body> <div class="container"> <h1>评论提交</h1> <%-- 评论表单:提交到/comment接口,POST方式提交(更安全,避免参数暴露在URL) --%> <form action="${pageContext.request.contextPath}/comment" method="post"> <label for="message">评论内容:</label> <textarea id="message" name="message" cols="30" rows="8" placeholder="请输入评论内容(敏感词汇将被过滤)" required></textarea> <input type="submit" value="提交评论"> </form>
<%-- 过滤结果展示区域:仅当有评论结果时显示 --%> <c:if test="${not empty requestScope.comment}"> <div class="result"> <p> <%-- 根据是否有敏感词,显示不同前缀样式 --%> <span class="${requestScope.hasSensitiveWord ? 'warning' : 'success'}"> ${requestScope.name} </span> 您的评论(已过滤敏感词):<br> ${requestScope.comment} </p> <%-- 若有敏感词,提示用户 --%> <c:if test="${requestScope.hasSensitiveWord}"> <p class="warning">提示:您的评论中包含敏感词汇,已自动替换处理</p> </c:if> </div> </c:if> </div> </body> </html>
|
然后是Filter代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| package com.example.filter;
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebInitParam; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.Objects;
@WebFilter( urlPatterns = {"/comment"}, // 拦截目标:仅拦截评论提交接口,精准拦截避免资源浪费 initParams = { // 初始化参数:敏感词汇列表,支持配置多个(用英文逗号分隔) @WebInitParam(name = "sensitiveWords", value = "zz,垃圾,脏话,敏感词1,敏感词2") }, dispatcherTypes = {DispatcherType.REQUEST} // 仅拦截直接请求(不拦截转发/包含等) ) public class SensitiveWordFilter implements Filter {
private List<String> sensitiveWordList;
@Override public void init(FilterConfig filterConfig) throws ServletException { String sensitiveWordsStr = filterConfig.getInitParameter("sensitiveWords"); if (Objects.isNull(sensitiveWordsStr) || sensitiveWordsStr.trim().isEmpty()) { throw new ServletException("敏感词汇配置不能为空!请在@WebInitParam中配置sensitiveWords参数"); } String[] sensitiveWordArr = sensitiveWordsStr.split(","); sensitiveWordList = Arrays.stream(sensitiveWordArr) .map(String::trim) .distinct() .toList(); System.out.println("[SensitiveWordFilter] 初始化完成,加载敏感词汇:" + sensitiveWordList); }
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); response.setCharacterEncoding("UTF-8");
String originalComment = request.getParameter("message"); if (Objects.isNull(originalComment) || originalComment.trim().isEmpty()) { chain.doFilter(request, response); return; }
String filteredComment = originalComment; boolean hasSensitiveWord = false; for (String sensitiveWord : sensitiveWordList) { if (filteredComment.contains(sensitiveWord)) { hasSensitiveWord = true; filteredComment = filteredComment.replace(sensitiveWord, "**"); System.out.println("[SensitiveWordFilter] 过滤敏感词:" + sensitiveWord + ",原始内容:" + originalComment + ",过滤后:" + filteredComment); } }
request.setAttribute("originalComment", originalComment); request.setAttribute("comment", filteredComment); request.setAttribute("hasSensitiveWord", hasSensitiveWord);
chain.doFilter(request, response); }
@Override public void destroy() { System.out.println("[SensitiveWordFilter] 销毁完成"); } }
|
Servlet代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| package com.example.servlet;
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.HashSet; import java.util.Objects;
@WebServlet( name = "CommentServlet", urlPatterns = {"/comment"}, // 访问路径:与前端表单的action对应 loadOnStartup = 1 // 服务器启动时初始化Servlet(默认是第一次访问时初始化) ) public class CommentServlet extends HttpServlet {
private HashSet<String> sensitiveIpSet = new HashSet<>();
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); }
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String originalComment = (String) request.getAttribute("originalComment"); String filteredComment = (String) request.getAttribute("comment"); boolean hasSensitiveWord = (boolean) request.getAttribute("hasSensitiveWord");
if (Objects.isNull(originalComment) || Objects.isNull(filteredComment)) { request.setAttribute("name", "错误:"); request.setAttribute("comment", "评论内容处理失败,请重试!"); request.getRequestDispatcher("/comment.jsp").forward(request, response); return; }
if (hasSensitiveWord) { String clientIp = getClientRealIp(request); sensitiveIpSet.add(clientIp); request.setAttribute("name", "注意:"); System.out.println("[CommentServlet] 检测到敏感词,客户端IP:" + clientIp + ",当前敏感词IP总数:" + sensitiveIpSet.size()); } else { request.setAttribute("name", "成功:"); System.out.println("[CommentServlet] 评论无敏感词,内容:" + originalComment); }
request.getRequestDispatcher("/comment.jsp").forward(request, response); }
private String getClientRealIp(HttpServletRequest request) { String ip = request.getHeader("X-Real-IP"); if (Objects.isNull(ip) || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Forwarded-For"); } if (Objects.isNull(ip) || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } if (ip != null && ip.contains(",")) { ip = ip.split(",")[0].trim(); } return ip; } }
|
我们输入http://localhost:8080/filter/comment.jsp,来访问jsp页面