过滤器 Filter 详解
想象一下,你正在搭建一个网站,用户需要登录才能访问某些页面。你肯定不希望未经授权的用户能够随意浏览或修改重要信息,这时,你就需要一个“安全门”来保护你的网站,而 Java Filter 就是扮演这个角色的最佳人选!
一、Filter 基本概念
1.1 什么是 Filter ?
Filter 就像一个尽职尽责的“保安”,专门负责检查进出网站的人员和货物。它拦截每一个来自客户端(例如浏览器)的请求,并在请求到达最终目的地(例如 Servlet 或 JSP 页面)之前进行一系列的检查和处理。同样地,当服务器准备将响应发送回客户端时,Filter 也会再次进行拦截,对响应内容进行修改或添加额外的信息。
1.2 Filter 的作用
-
拦截请求和响应: 就像保安检查来往人员一样,Filter 可以拦截所有进出网站的 HTTP 请求和响应,掌握网站动态。
-
预处理请求: 在请求到达目的地之前,Filter 可以进行一些预处理操作,例如:
-
检查用户是否登录
-
设置字符编码,避免乱码
-
记录请求日志,方便追踪问题
-
-
修改请求和响应: Filter 可以根据需要修改请求头、请求体、响应头和响应体的内容,例如:
-
添加或删除 Cookie
-
设置响应状态码
-
压缩响应数据,提高传输效率
-
-
拦截请求: 对于一些不符合条件的请求,Filter 可以直接拒绝,阻止其访问敏感资源,例如:
-
阻止未登录用户访问需要权限的页面
-
限制来自特定 IP 地址的访问
-
1.3 Filter 的生命周期
Filter 的生命周期由 Servlet 容器(例如 Tomcat)全权负责,就像保安在岗时间一样,有着严格的管理:
-
实例化: 当 Servlet 容器启动时,会根据配置文件创建 Filter 实例,就像保安上班打卡一样。
-
初始化: Filter 实例创建后,容器会调用 init() 方法,让 Filter 做好上岗前的准备工作,例如加载配置文件或初始化资源。
-
过滤请求: 每当有请求到达时,容器都会调用 doFilter() 方法,让 Filter 执行过滤逻辑,就像保安检查来往人员一样。
-
销毁: 当 Servlet 容器关闭时,会调用 destroy() 方法,让 Filter 释放资源,结束工作,就像保安下班打卡一样。
1.4 Filter 的使用
为了让 Filter 能够完成各种任务,Java Servlet API 提供了一套强大的武器库,让 Filter 能够应对各种挑战:
-
javax.servlet.Filter 接口: 这是所有 Filter 的“身份证明”,只有实现了这个接口的类才能被称为 Filter。它定义了 Filter 的基本行为,包括 init()、doFilter() 和 destroy() 三个核心方法。
-
javax.servlet.FilterChain 接口: Filter 并不是孤军奋战,它通常与其他 Filter 组成一个团队,协同工作。FilterChain 接口就代表了这个团队,它维护了一个 Filter 链条,并提供了 doFilter() 方法,用于将请求传递给链条中的下一个 Filter 或最终的目标资源。
-
javax.servlet.FilterConfig 接口: 每个 Filter 在工作时都需要一些配置信息,例如 Filter 的名称、初始化参数等。FilterConfig 接口就提供了获取这些配置信息的途径,让 Filter 能够根据配置信息进行个性化的工作。
二、实现 Filter
2.1 创建 Filter
打造一个专属 Filter 非常简单,只需三步即可完成:
-
实现 javax.servlet.Filter 接口: 这是成为 Filter 的必要条件,你需要实现 init()、doFilter() 和 destroy() 三个方法。
-
编写过滤逻辑: 在 doFilter() 方法中编写你的过滤逻辑,例如检查用户是否登录、记录请求日志等。
-
配置 Filter: 告诉 Servlet 容器你的 Filter 的信息,包括 Filter 的名称、类名以及要拦截的 URL 地址等。
2.2 代码示例
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;public class MyFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// 初始化操作,例如获取配置参数String param = filterConfig.getInitParameter("paramName");System.out.println("Filter 初始化参数:" + param);}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {// 预处理操作,例如打印请求路径HttpServletRequest req = (HttpServletRequest) request;System.out.println("请求路径:" + req.getRequestURI());// 调用 chain.doFilter() 方法,将请求传递给下一个 Filter 或目标资源chain.doFilter(request, response);// 后处理操作,例如打印响应内容System.out.println("响应内容:" + response.getContentType());}@Overridepublic void destroy() {// 销毁操作,例如释放资源System.out.println("Filter 销毁");}
}
-
init方法:过滤器的初始化方法。在web服务器启动的时候会自动的创建Filter过滤器对象,在创建过滤器对象的时候会自动调用init初始化方法,这个方法只会被调用一次。
-
doFilter方法:这个方法是在每一次拦截到请求之后都会被调用,所以这个方法是会被调用多次的,每拦截到一次请求就会调用一次doFilter()方法。
-
destroy方法: 是销毁的方法。当我们关闭服务器的时候,它会自动的调用销毁方法destroy,而这个销毁方法也只会被调用一次。
2.3 配置 Filter
配置 Filter 有两种方式:
-
使用 web.xml 文件配置: 这是传统的配置方式,需要在 web.xml 文件中添加 <filter> 和 <filter-mapping> 元素来定义 Filter 和其拦截路径。
<filter><filter-name>myFilter</filter-name> <filter-class>com.example.MyFilter</filter-class><init-param><param-name>paramName</param-name><param-value>paramValue</param-value></init-param>
</filter><filter-mapping><filter-name>myFilter</filter-name><url-pattern>/myServlet</url-pattern>
</filter-mapping>
-
使用 @WebFilter 注解配置: 这是 Servlet 3.0 之后提供的更简洁的配置方式,可以直接在 Filter 类上使用 @WebFilter 注解来定义 Filter 和其拦截路径。
@WebFilter(filterName = "myFilter", urlPatterns = "/myServlet", initParams = {@WebInitParam(name = "paramName", value = "paramValue")
})
public class MyFilter implements Filter {// ...
}
2.4 拦截路径
执行流程我们搞清楚之后,接下来再来介绍一下过滤器的拦截路径,Filter可以根据需求,配置不同的拦截资源路径:
拦截路径 | urlPatterns值 | 含义 |
---|---|---|
拦截具体路径 | /login | 只有访问 /login 路径时,才会被拦截 |
目录拦截 | /stus/* | 访问/stus下的所有资源,都会被拦截 |
拦截所有 | /* | 访问所有资源,都会被拦截 |
三、Spring Boot 中使用 Filter
在 Spring Boot 项目中,使用 Filter 更加方便,你只需要使用 @Component 注解将 Filter 注册为 Spring Bean,并使用 @WebFilter 注解配置其拦截路径即可。
3.1 大致步骤
-
第1步,定义过滤器 :1.定义一个类,实现 Filter 接口,并重写其所有方法。
-
第2步,配置过滤器:Filter类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加 @ServletComponentScan 开启Servlet组件支持。
-
第3步,当我们在Filter类上面加了@WebFilter注解之后,接下来我们还需要在启动类上面加上一个注解@ServletComponentScan,通过这个@ServletComponentScan注解来开启SpringBoot项目对于Servlet组件的支持。
3.2 示例代码:
@Component
@WebFilter(urlPatterns = "/*", filterName = "loginFilter") //配置过滤器要拦截的请求路径( /* 表示拦截浏览器的所有请求 )
public class LoginFilter implements Filter {@Override //初始化方法, 只调用一次public void init(FilterConfig filterConfig) throws ServletException {System.out.println("init 初始化方法执行了");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("DemoFilter 放行前逻辑.....");//放行请求filterChain.doFilter(servletRequest,servletResponse);System.out.println("DemoFilter 放行后逻辑.....");}@Override //销毁方法, 只调用一次public void destroy() {System.out.println("destroy 销毁方法执行了");}}
四、Filter 执行流程
4.1 FilterChain
当多个 Filter 同时拦截同一个请求时,它们会组成一个 FilterChain,就像一条链条一样,依次对请求进行处理。FilterChain 维护了 Filter 的执行顺序,并通过 doFilter() 方法将请求传递给下一个 Filter。
4.2 执行流程
-
当一个请求到达 Servlet 容器时,容器会根据请求的 URL 地址找到所有匹配的 Filter,并将它们添加到一个 FilterChain 中。
-
容器调用 FilterChain 的 doFilter() 方法,开始执行 Filter 链。
-
FilterChain 依次调用每个 Filter 的 doFilter() 方法,每个 Filter 都有机会对请求进行处理。
-
在 Filter 的 doFilter() 方法中,可以调用 chain.doFilter(request, response) 方法将请求传递给下一个 Filter,也可以选择不调用该方法,直接返回响应,结束请求处理流程。
-
如果 Filter 调用了 chain.doFilter(request, response) 方法,则 FilterChain 会将请求传递给下一个 Filter,直到最后一个 Filter 执行完毕。
-
最后一个 Filter 执行完毕后,FilterChain 会将请求传递给目标资源(例如 Servlet 或 JSP)。
-
目标资源执行完毕后,会生成响应,响应会按照 FilterChain 的反方向依次经过每个 Filter 的 doFilter() 方法,每个 Filter 都有机会对响应进行处理。
-
最终,响应会返回给客户端。
-
然而执行顺序其实是和过滤器的类名有关系。以注解方式配置的Filter过滤器,它的执行优先级是按时过滤器类名的自动排序确定的,类名排名越靠前,优先级越高。
4.3 图示
请求----->
[Filter 1] --> [Filter 2] --> [目标资源]<-----响应请求:1 -> 2
响应:2 -> 1完整:1 -> 2 -> 2 -> 1
五、小练习:Filter 实现登录校验
5.1 需求分析:
假设我们正在开发一个电商网站,用户需要登录才能进行下单操作。为了保证网站安全,我们需要使用 Filter 对所有请求进行拦截,检查用户是否登录,如果未登录,则跳转到登录页面。
5.2 代码实现:
@Component
@WebFilter(urlPatterns = {"/order/*"}, filterName = "authFilter")
public class AuthFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest) request;HttpServletResponse res = (HttpServletResponse) response;// 获取用户信息User user = (User) req.getSession().getAttribute("user");// 判断用户是否登录if (user == null) {// 未登录,跳转到登录页面res.sendRedirect("/login");return;}// 已登录,放行请求chain.doFilter(request, response);}
}
5.3 代码解读:
-
我们使用 @WebFilter 注解将 AuthFilter 声明为一个 Filter,并设置其拦截路径为 /order/*,表示拦截所有以 /order/ 开头的请求。
-
在 doFilter() 方法中,我们首先获取用户信息。
-
然后,我们判断用户是否登录,如果未登录,则跳转到登录页面,并使用 return 关键字结束请求处理流程。
-
如果用户已登录,则调用 chain.doFilter(request, response) 方法,将请求传递给下一个 Filter 或目标资源。
六、总结:
Filter 是 Java Web 开发中一个非常重要的组件,它可以帮助我们实现各种功能,例如用户登录校验、日志记录、字符编码转换等。掌握 Filter 的使用,可以让我们开发出更加安全、稳定、高效的 Web 应用。
最后,希望上文能够帮助各位看官全面了解和掌握 Filter,并在实际项目中灵活运用它来解决各种问题。感谢各位看官的观看,下期见,谢谢~