JavaWeb Servlet 过滤器(Filter)完整教程
一、过滤器核心概念
1.1 什么是过滤器
过滤器(Filter)是Jakarta EE 规范定义的标准组件,它位于客户端请求和服务器目标资源之间,能够对请求和响应进行预处理和后处理。过滤器是 Java Web 开发中最实用的技术之一,完美体现了责任链设计模式。
工作机制:
客户端发送请求到服务器
容器创建
HttpServletRequest和HttpServletResponse对象请求先经过所有匹配的过滤器的
doFilter方法过滤器决定是否放行请求到目标资源(Servlet/JSP/静态资源)
目标资源处理完成后,响应再次经过过滤器链返回给客户端
生活类比:
机场安检:所有乘客登机前必须经过安检,安检人员检查行李和证件,符合要求才能放行
快递驿站:快递到达后,驿站先签收登记,然后通知收件人取件;寄件时驿站先检查包裹,再发往目的地
1.2 过滤器的典型应用场景
统一编码处理:解决全站中文乱码问题
登录权限验证:拦截未登录用户的受保护资源请求
敏感字符过滤:过滤用户输入中的非法或敏感内容
日志记录与审计:记录所有请求的访问信息和耗时
性能监控:统计接口响应时间,分析系统性能瓶颈
跨域资源共享(CORS):统一处理跨域请求
事务控制:为请求开启和提交事务,实现声明式事务
1.3 Filter 接口 API
所有自定义过滤器都必须实现 jakarta.servlet.Filter 接口,该接口定义了三个核心方法:
| 方法签名 | 执行时机 | 作用 |
|---|---|---|
default void init(FilterConfig filterConfig) |
Web 应用启动时,过滤器对象创建后立即执行 | 初始化过滤器,读取配置参数 |
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) |
每次匹配的请求到达时执行 | 核心过滤逻辑,决定是否放行 |
default void destroy() |
Web 应用关闭时,过滤器对象销毁前执行 | 释放过滤器占用的资源 |
接口源码(Jakarta Servlet 5.0):
package jakarta.servlet;
import java.io.IOException;
public interface Filter {
default public void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;
default public void destroy() {}
}
二、过滤器快速入门:日志记录过滤器
我们通过一个请求日志记录过滤器来演示过滤器的完整开发流程,该过滤器将记录所有请求的访问时间、路径和处理耗时。
2.1 步骤 1:创建过滤器类
package com.example.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 日志记录过滤器:记录所有请求的访问信息和处理耗时
*/
public class LoggingFilter implements Filter {
private SimpleDateFormat dateFormat;
// 初始化方法:Web应用启动时执行一次
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化日期格式化器
dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("日志过滤器初始化完成");
}
// 核心过滤方法:每次请求都会执行
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
// 1. 类型转换:将父接口转换为HTTP专用子接口
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 2. 请求预处理:记录请求开始时间和路径
String requestURI = request.getRequestURI();
String startTime = dateFormat.format(new Date());
long startMillis = System.currentTimeMillis();
System.out.printf("[请求开始] %s | 路径:%s%n", startTime, requestURI);
try {
// 3. 放行请求:将请求传递给下一个过滤器或目标资源
chain.doFilter(request, response);
} finally {
// 4. 响应后处理:记录请求耗时
long endMillis = System.currentTimeMillis();
long duration = endMillis - startMillis;
System.out.printf("[请求结束] %s | 路径:%s | 耗时:%dms%n", startTime, requestURI, duration);
}
}
// 销毁方法:Web应用关闭时执行一次
@Override
public void destroy() {
System.out.println("日志过滤器已销毁");
}
}
2.2 步骤 2:创建测试用 Servlet
为了验证过滤器效果,我们创建两个简单的 Servlet 作为目标资源:
ServletA.java
package com.example.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 模拟业务处理耗时
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
resp.getWriter().write("ServletA 处理完成");
}
}
ServletB.java
package com.example.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 模拟业务处理耗时
try {
Thread.sleep(15);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
resp.getWriter().write("ServletB 处理完成");
}
}
2.3 步骤 3:配置过滤器
过滤器有两种配置方式:web.xml 配置和注解配置,我们先介绍传统的 web.xml 方式。
在 WEB-INF/web.xml 文件中添加以下配置:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<!-- 1. 定义过滤器 -->
<filter>
<filter-name>loggingFilter</filter-name>
<filter-class>com.example.filter.LoggingFilter</filter-class>
</filter>
<!-- 2. 配置过滤器的拦截规则 -->
<filter-mapping>
<filter-name>loggingFilter</filter-name>
<!-- 拦截 /servletA 的请求 -->
<url-pattern>/servletA</url-pattern>
<!-- 拦截所有 .html 静态资源 -->
<url-pattern>*.html</url-pattern>
<!-- 拦截名为 servletB 的 Servlet(通过 Servlet 名称) -->
<servlet-name>com.example.servlet.ServletB</servlet-name>
</filter-mapping>
</web-app>
2.4 测试效果
启动 Tomcat 服务器,分别访问以下地址:
http://localhost:8080/your-webapp/servletAhttp://localhost:8080/your-webapp/servletBhttp://localhost:8080/your-webapp/index.html(如果存在)
控制台输出示例:
日志过滤器初始化完成
[请求开始] 2024-05-20 14:30:00 | 路径:/your-webapp/servletA
[请求结束] 2024-05-20 14:30:00 | 路径:/your-webapp/servletA | 耗时:12ms
[请求开始] 2024-05-20 14:30:05 | 路径:/your-webapp/servletB
[请求结束] 2024-05-20 14:30:05 | 路径:/your-webapp/servletB | 耗时:17ms
三、<url-pattern> 匹配规则详解
过滤器的拦截范围由 <url-pattern> 标签控制,Servlet 规范定义了 4 种匹配模式,优先级从高到低排列。
3.1 四种匹配模式
-
精确匹配(最高优先级)
写法:以
/开头,写死完整路径示例:
<url-pattern>/user/login</url-pattern>匹配:
http://localhost:8080/app/user/login不匹配:
http://localhost:8080/app/user/login/、http://localhost:8080/app/user/info
-
路径匹配(通配符匹配)
写法:以
/开头,以/*结尾-
示例:
<url-pattern>/user/*</url-pattern>:匹配/user下的所有子路径<url-pattern>/*</url-pattern>:匹配所有请求(最常用)
匹配:
/user/login、/user/info/123、/user/a/b/c不匹配:
/admin/login
-
扩展名匹配
写法:以
*.开头,后面跟扩展名(前面不能加 ****/)示例:
<url-pattern>*.do</url-pattern>、<url-pattern>*.action</url-pattern>匹配:
/login.do、/user/info.do不匹配:
/login.html、/user/info
-
默认匹配(最低优先级)
写法:仅写一个
/示例:
<url-pattern>/</url-pattern>作用:当请求没有匹配到任何其他 Servlet 时触发,通常用于处理静态资源或 404 页面
注意:与
/*不同,/不会拦截 JSP 页面(JSP 有专门的内置 Servlet 处理)
3.2 匹配优先级规则
当多个 <url-pattern> 同时匹配一个 URL 时,按以下优先级选择:
精确匹配 > 路径匹配 > 扩展名匹配 > 默认匹配
路径匹配中,路径越长优先级越高(如
/user/admin/*优先于/user/*)
3.3 常见错误写法
❌ 错误:/user/*.do(路径匹配和扩展名匹配不能混合使用)
❌ 错误:user/*(必须以 / 开头)
❌ 错误:*.do/(扩展名匹配后面不能加 /)
❌ 错误:/*.*(不支持这种通配符写法)
四、过滤器生命周期
过滤器的生命周期由 Web 容器管理,与 Servlet 类似但略有不同:
| 生命周期阶段 | 对应方法 | 执行时机 | 执行次数 |
|---|---|---|---|
| 对象创建 | 构造方法 | Web 应用启动时 | 1 次 |
| 初始化 | init(FilterConfig) |
构造方法执行后立即执行 | 1 次 |
| 请求处理 | doFilter() |
每次匹配的请求到达时 | 多次 |
| 对象销毁 | destroy() |
Web 应用关闭时 | 1 次 |
关键特点:
过滤器是单例的,整个应用中只有一个实例
所有请求共享同一个过滤器实例,因此要注意线程安全问题
过滤器在 Web 应用启动时就会被创建和初始化,而不是第一次请求时
过滤器的销毁发生在 Web 应用正常关闭时
生命周期测试代码:
package com.example.filter;
import jakarta.servlet.*;
import java.io.IOException;
public class LifecycleTestFilter implements Filter {
// 构造方法
public LifecycleTestFilter() {
System.out.println("1. 过滤器对象创建:构造方法执行");
}
// 初始化方法
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("2. 过滤器初始化:init 方法执行");
}
// 过滤方法
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("3. 处理请求:doFilter 方法执行");
chain.doFilter(request, response);
}
// 销毁方法
@Override
public void destroy() {
System.out.println("4. 过滤器销毁:destroy 方法执行");
}
}
五、过滤器链(FilterChain)
当多个过滤器同时匹配同一个请求时,它们会按照配置顺序形成一个过滤器链。
5.1 过滤器链执行顺序
请求按照
filter-mapping的从上到下顺序依次经过各个过滤器响应按照相反顺序依次经过各个过滤器
任何一个过滤器没有调用
chain.doFilter()方法,请求都会被中断,不再继续传递
5.2 过滤器链示例
我们创建三个过滤器来演示执行顺序:
Filter1.java
package com.example.filter;
import jakarta.servlet.*;
import java.io.IOException;
public class Filter1 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("Filter1:请求预处理");
chain.doFilter(request, response);
System.out.println("Filter1:响应后处理");
}
}
Filter2.java
package com.example.filter;
import jakarta.servlet.*;
import java.io.IOException;
public class Filter2 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("Filter2:请求预处理");
chain.doFilter(request, response);
System.out.println("Filter2:响应后处理");
}
}
Filter3.java
package com.example.filter;
import jakarta.servlet.*;
import java.io.IOException;
public class Filter3 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("Filter3:请求预处理");
chain.doFilter(request, response);
System.out.println("Filter3:响应后处理");
}
}
web.xml 配置:
<filter>
<filter-name>filter1</filter-name>
<filter-class>com.example.filter.Filter1</filter-class>
</filter>
<filter>
<filter-name>filter2</filter-name>
<filter-class>com.example.filter.Filter2</filter-class>
</filter>
<filter>
<filter-name>filter3</filter-name>
<filter-class>com.example.filter.Filter3</filter-class>
</filter>
<!-- filter-mapping 的顺序决定了过滤器的执行顺序 -->
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/test</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>filter2</filter-name>
<url-pattern>/test</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>filter3</filter-name>
<url-pattern>/test</url-pattern>
</filter-mapping>
创建测试 Servlet:
@WebServlet("/test")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("TestServlet:处理请求");
resp.getWriter().write("测试完成");
}
}
控制台输出:
Filter1:请求预处理
Filter2:请求预处理
Filter3:请求预处理
TestServlet:处理请求
Filter3:响应后处理
Filter2:响应后处理
Filter1:响应后处理
5.3 过滤器链执行顺序图解
客户端请求
↓
Filter1.doFilter() 前
↓
Filter2.doFilter() 前
↓
Filter3.doFilter() 前
↓
目标资源(Servlet)处理请求
↓
Filter3.doFilter() 后
↓
Filter2.doFilter() 后
↓
Filter1.doFilter() 后
↓
响应返回客户端
六、注解方式配置过滤器
除了 web.xml 配置外,还可以使用 @WebFilter 注解来配置过滤器,这种方式更简洁,适合现代 Web 开发。
6.1 @WebFilter 注解常用属性
| 属性 | 作用 | 对应 web.xml 标签 |
|---|---|---|
filterName |
过滤器名称 | <filter-name> |
urlPatterns / value
|
拦截的 URL 模式 | <url-pattern> |
servletNames |
拦截的 Servlet 名称 | <servlet-name> |
initParams |
初始化参数 | <init-param> |
dispatcherTypes |
拦截的请求分发类型 | <dispatcher> |
asyncSupported |
是否支持异步处理 | <async-supported> |
6.2 注解配置示例
将之前的日志过滤器改造成注解配置方式:
package com.example.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 日志记录过滤器(注解配置方式)
*/
@WebFilter(
filterName = "loggingFilter",
urlPatterns = {"/servletA", "*.html"},
servletNames = {"com.example.servlet.ServletB"},
initParams = {
@WebInitParam(name = "datePattern", value = "yyyy-MM-dd HH:mm:ss")
}
)
public class LoggingFilter implements Filter {
private SimpleDateFormat dateFormat;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 从注解中获取初始化参数
String datePattern = filterConfig.getInitParameter("datePattern");
dateFormat = new SimpleDateFormat(datePattern);
System.out.println("日志过滤器初始化完成(注解方式)");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestURI = request.getRequestURI();
String startTime = dateFormat.format(new Date());
long startMillis = System.currentTimeMillis();
System.out.printf("[请求开始] %s | 路径:%s%n", startTime, requestURI);
try {
chain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - startMillis;
System.out.printf("[请求结束] %s | 路径:%s | 耗时:%dms%n", startTime, requestURI, duration);
}
}
@Override
public void destroy() {
System.out.println("日志过滤器已销毁");
}
}
6.3 注解配置注意事项
使用
@WebFilter注解时,需要确保 Web 应用的web.xml文件中没有配置<metadata-complete="true">(该配置会关闭注解扫描)注解配置的过滤器执行顺序无法直接控制,如果需要严格控制顺序,建议使用 web.xml 配置
注解配置和 web.xml 配置可以混合使用
七、实战案例:统一中文编码处理过滤器
中文乱码是 Java Web 开发中最常见的问题之一,使用过滤器可以一次性解决全站的中文乱码问题。
7.1 问题分析
POST 请求的中文乱码:需要在读取参数前设置
request.setCharacterEncoding("UTF-8")响应的中文乱码:需要设置
response.setContentType("text/html;charset=UTF-8")Tomcat 8+ 已经默认将 GET 请求的编码设置为 UTF-8,无需额外处理
7.2 代码实现
package com.example.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 统一编码处理过滤器:解决全站中文乱码问题
*/
@WebFilter("/*") // 拦截所有请求
public class CharacterEncodingFilter implements Filter {
private String encoding = "UTF-8"; // 默认编码
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 支持通过初始化参数自定义编码
String encodingParam = filterConfig.getInitParameter("encoding");
if (encodingParam != null && !encodingParam.isEmpty()) {
encoding = encodingParam;
}
System.out.println("编码过滤器初始化完成,使用编码:" + encoding);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 1. 设置请求编码(解决 POST 请求中文乱码)
request.setCharacterEncoding(encoding);
// 2. 设置响应编码(解决响应中文乱码)
response.setContentType("text/html;charset=" + encoding);
response.setCharacterEncoding(encoding);
// 3. 放行请求
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 无需释放资源
}
}
7.3 注意事项
过滤器顺序:编码过滤器必须放在所有过滤器的最前面,否则其他过滤器在读取参数后再设置编码将无效
JSP 页面:JSP 页面顶部仍需添加
<%@ page contentType="text/html;charset=UTF-8" language="java" %>GET 请求:Tomcat 8+ 已默认处理 GET 请求编码,Tomcat 7 及以下版本需要修改
server.xml配置
八、实战案例:登录权限验证过滤器
在实际项目中,我们通常需要保护某些资源,只有登录用户才能访问。使用过滤器可以统一实现登录验证逻辑,避免在每个 Servlet 中重复编写代码。
8.1 实现思路
定义登录页面和登录处理 Servlet
用户登录成功后,将用户信息存入 Session
创建登录验证过滤器,拦截所有受保护资源
过滤器检查 Session 中是否有登录用户信息
如果已登录,放行请求;如果未登录,重定向到登录页面
8.2 代码实现
登录页面 login.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>
<h3>用户登录</h3>
<form action="login" method="post">
用户名:<input type="text" name="username" required><br>
密码:<input type="password" name="password" required><br>
<input type="submit" value="登录">
</form>
</body>
</html>
登录处理 Servlet LoginServlet.java:
package com.example.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
// 简单验证(实际项目中应查询数据库)
if ("admin".equals(username) && "123456".equals(password)) {
// 登录成功,将用户信息存入 Session
HttpSession session = req.getSession();
session.setAttribute("loginUser", username);
// 重定向到首页
resp.sendRedirect("index");
} else {
// 登录失败
resp.getWriter().write("用户名或密码错误!<a href='login.html'>返回登录</a>");
}
}
}
首页 Servlet IndexServlet.java:
package com.example.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = (String) req.getSession().getAttribute("loginUser");
resp.getWriter().write("<h3>欢迎您," + username + "!</h3>");
resp.getWriter().write("<a href='logout'>退出登录</a>");
}
}
退出登录 Servlet LogoutServlet.java:
package com.example.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 销毁 Session
req.getSession().invalidate();
// 重定向到登录页面
resp.sendRedirect("login.html");
}
}
登录验证过滤器 LoginFilter.java:
package com.example.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
/**
* 登录验证过滤器:保护受限制资源
*/
@WebFilter("/*") // 拦截所有请求
public class LoginFilter implements Filter {
// 不需要登录就能访问的路径(白名单)
private static final String[] WHITE_LIST = {
"/login.html",
"/login",
"/css/",
"/js/",
"/images/"
};
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestURI = request.getRequestURI();
// 1. 检查是否是白名单路径
for (String path : WHITE_LIST) {
if (requestURI.contains(path)) {
// 白名单路径,直接放行
chain.doFilter(request, response);
return;
}
}
// 2. 检查用户是否已登录
HttpSession session = request.getSession(false); // false 表示如果没有 Session 则返回 null
if (session != null && session.getAttribute("loginUser") != null) {
// 已登录,放行
chain.doFilter(request, response);
} else {
// 未登录,重定向到登录页面
response.sendRedirect(request.getContextPath() + "/login.html");
}
}
}
8.3 测试效果
直接访问
http://localhost:8080/your-webapp/index,会自动重定向到登录页面输入正确的用户名(admin)和密码(123456),登录成功后跳转到首页
此时再访问首页,可以正常看到欢迎信息
点击"退出登录",会销毁 Session 并返回登录页面
九、过滤器与 Servlet 对比
| 对比项 | 过滤器(Filter) | Servlet |
|---|---|---|
| 作用 | 对请求和响应进行预处理和后处理 | 处理具体的业务逻辑 |
| 执行时机 | 在目标资源之前执行 | 作为目标资源执行 |
| 能否拦截多个资源 | 可以,通过 url-pattern 配置 | 通常一个 Servlet 处理一个或一类请求 |
| 能否中断请求 | 可以,不调用 chain.doFilter() 即可 | 不能中断请求链 |
| 能否处理响应 | 可以,在 chain.doFilter() 后处理 | 只能生成响应 |
| 生命周期 | Web 应用启动时创建 | 默认第一次请求时创建 |
十、最佳实践
单一职责原则:每个过滤器只负责一个功能(如编码过滤器、登录过滤器、日志过滤器)
合理使用过滤器链:按照逻辑顺序排列过滤器,编码过滤器放在最前面
避免在过滤器中执行耗时操作:过滤器会影响所有匹配请求的性能
注意线程安全:过滤器是单例的,不要在过滤器中定义可变的成员变量
合理设置拦截范围:不要盲目使用
/*拦截所有请求,只拦截需要的资源使用白名单机制:对于登录验证等过滤器,使用白名单排除不需要拦截的路径
及时释放资源:在
destroy()方法中释放过滤器占用的资源
( )