

统一声明:
1.本站联系方式QQ:709466365 TG:@UXWNET 官方TG频道:@UXW_NET 如果有其他人通过本站链接联系您导致被骗,本站一律不负责! 2.需要付费搭建请联系站长QQ:709466365 TG:@UXWNET 3.免实名域名注册购买- 游侠云域名 4.免实名国外服务器购买- 游侠网云服务
Filter到底是啥?先把原理掰碎了讲,不然用的时候容易懵
很多人学Filter只记“能拦截请求”,但原理没搞懂,用的时候总踩坑。我先给你打个比方:Filter就像小区门口的保安——快递(请求)来了,先过保安(Filter)检查,没问题再送到你家(Servlet);你要寄快递(响应),也得先过保安(Filter),再发出去。它是运行在Servlet之前的“中间件”,专门处理那些“所有请求/响应都要做的通用逻辑”。
先搞懂Filter的3个生命周期方法,不然初始化就出问题
Filter的一生就3步:init→doFilter→destroy,服务器管着它的生老病死:
doFilter的3个参数,搞懂了才会“放行”和“拦截”
doFilter方法有3个参数:ServletRequest、ServletResponse、FilterChain。前两个不用多说,关键是FilterChain——它是“放行”的开关。你可以把FilterChain想象成“接力棒”:调用chain.doFilter(),就是把请求传给下一个Filter(如果有的话),最后传给Servlet;如果不调用,请求就被你“扣下”了(比如登录验证失败,直接返回错误页面)。
举个例子,登录验证的Filter逻辑:
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpSession session = request.getSession();
// 检查session里有没有用户信息
if (session.getAttribute("user") == null) {
// 没登录,跳转到登录页,不放行
request.getRequestDispatcher("/login.jsp").forward(req, resp);
return;
}
// 登录了,放行,让请求到Servlet
chain.doFilter(req, resp);
}
是不是很简单?但新手常犯的错是“顺序搞反”——比如把登录Filter放在字符编码Filter后面,结果登录验证时request的编码还没设置,拿到的用户名密码是乱码,验证失败。记住:通用的、基础的Filter(比如编码设置)要放在最前面,不然后面的Filter会用到错的资源。
Filter的实战场景:这3个常用需求,我手把手教你写
光懂原理不够,得落地到具体场景。我选了3个最常用的需求,把步骤和坑都给你讲清楚——都是我踩过的坑,你别再踩了。
场景1:字符编码设置——别再每个Servlet都写response.setContentType()
每个Servlet都要写response.setContentType("text/html;charset=UTF-8")
?太麻烦了,用Filter一次性解决:
public class CharacterEncodingFilter implements Filter {
private String encoding;
// 从web.xml里读配置的编码(比如UTF-8)
@Override
public void init(FilterConfig filterConfig) throws ServletException {
encoding = filterConfig.getInitParameter("encoding");
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
// 设置请求和响应的编码
req.setCharacterEncoding(encoding);
resp.setContentType("text/html;charset=" + encoding);
// 放行
chain.doFilter(req, resp);
}
@Override
public void destroy() {}
}
配置Filter:可以用web.xml,也可以用注解@WebFilter。比如web.xml配置: xml
CharacterEncodingFilter
com.example.filter.CharacterEncodingFilter
encoding
UTF-8
CharacterEncodingFilter
<!-
拦截所有请求 > /
坑提示:别只设置req的编码,resp的ContentType一定要加——我之前帮朋友调项目,只设了req.setCharacterEncoding("UTF-8"),结果前端拿到的中文还是乱码,因为resp的编码没设置,浏览器用默认的ISO-8859-1解析了。
场景2:登录验证——把重复代码抽出来,再也不用复制粘贴
每个Servlet都写登录检查?太low了,用Filter集中处理:
写Filter类:检查session里的用户信息: java
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
// 排除登录页面和静态资源(比如CSS、JS)
String url = request.getRequestURI();
if (url.contains("/login.jsp") || url.contains("/static/")) {
chain.doFilter(req, resp);
return;
}
// 检查session
if (request.getSession().getAttribute("user") == null) {
// 没登录,重定向到登录页
response.sendRedirect(request.getContextPath() + "/login.jsp");
return;
}
// 登录了,放行
chain.doFilter(req, resp);
}
// init和destroy方法省略
}
配置Filter:用@WebFilter注解的话,直接写在类上: java
@WebFilter(urlPatterns = {"/user/", "/order/*"}) // 拦截用户和订单相关的请求
public class LoginFilter implements Filter { ... }
坑提示:一定要排除登录页面和静态资源——我之前没排除login.jsp,结果登录页面的请求也被拦截,无限重定向,浏览器直接报错。
场景3:日志记录——记录请求的URL、IP,排查问题方便
想知道用户访问了哪些页面?用Filter记日志:
写Filter类:记录请求的URL、IP、时间: java
public class LogFilter implements Filter {
private PrintWriter logWriter;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化日志文件,存在项目的log目录下
String logPath = filterConfig.getServletContext().getRealPath("/log") + "/access.log";
try {
logWriter = new PrintWriter(new FileWriter(logPath, true));
} catch (IOException e) {
throw new ServletException("日志文件初始化失败", e);
}
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
// 获取请求信息
String url = request.getRequestURI();
String ip = request.getRemoteAddr();
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
// 写日志
logWriter.println(time + "
" + ip + " " + url); logWriter.flush(); // 及时刷新,避免日志丢失
// 放行
chain.doFilter(req, resp);
}
@Override
public void destroy() {
// 关闭日志流
if (logWriter != null) {
logWriter.close();
}
}
}
坑提示:logWriter一定要flush和close——我之前没flush,结果日志没写到文件里,排查问题时找不到记录;没close,服务器跑久了,文件流占着资源,磁盘容易满。
新手最容易踩的3个坑,我踩过,你别再踩
我 了3个新手常犯的错,都是我或同事踩过的坑,记住了能少走很多弯路:
坑1:忘了调用chain.doFilter(),请求被“卡住”
这是最基础但最容易忘的错——写Filter时,光顾着处理逻辑,忘了写chain.doFilter(request, response),结果请求到了Filter就停住了,Servlet根本收不到。解决办法:写完Filter先测试,看请求能不能到Servlet。
坑2:用成员变量存用户信息,并发时“串数据”
Filter是单例的——服务器启动时创建一个Filter实例,所有请求都用这个实例处理。如果在Filter里定义成员变量(比如String username),并发时多个线程会修改同一个变量,导致数据串了。比如:
java
// 错误示例:成员变量会被并发修改
public class BadFilter implements Filter {
private String username; // 线程不安全!
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
username = req.getParameter(“username”); // 多个线程修改同一个变量
…
}
}
正确做法:用request的attribute存数据,每个请求都有自己的attribute,不会串:
java
// 正确示例:用request的attribute存
request.setAttribute(“username”, req.getParameter(“username”));
Oracle的Java EE文档里明确说:“Filter的doFilter方法是多线程的,要避免使用非线程安全的成员变量”(链接:https://docs.oracle.com/javaee/7/api/javax/servlet/Filter.htmlnofollow)——权威说法,得听。
坑3:资源释放不及时,导致内存泄漏
在Filter里打开的IO流、数据库连接,一定要在finally块或destroy方法里关闭。比如日志Filter里的PrintWriter,要是忘了close,服务器运行几天,磁盘就满了。解决办法:用try-with-resources或者finally块关闭资源:
java
// 用try-with-resources自动关闭流
try (PrintWriter writer = new PrintWriter(new FileWriter(logPath, true))) {
writer.println(“日志内容”);
} catch (IOException e) {
e.printStackTrace();
}
最后给你几个可直接用的 确保Filter用得稳
你要是按这些方法试了,或者遇到了其他坑,欢迎在评论区告诉我——毕竟踩过的坑多了,多少有点经验。Filter不难,关键是把原理搞懂,避开那些新手坑,就能帮你省很多时间,写出更干净的代码。
Filter和Spring的Interceptor看着都是“拦请求的”,其实“出身”和“能管的事儿”差别老大了。Filter是Servlet规范自带的“原生工具”,从Tomcat这类服务器启动到关闭,全由容器攥着它的生命周期——比如你写个处理字符编码的Filter,服务器一启动就初始化好,不管是请求Servlet、JSP还是静态的CSS文件,只要路过它的url-pattern,都得先过一遍。而Interceptor是Spring自家“造的”,得靠Spring容器管着,它能钻到Spring的“肚子里”——比如你想拦Controller里的某个具体方法,或者要调Service层的Bean查用户权限,Interceptor张嘴就来,Filter可没这“钻劲儿”。
再说说干的活儿不一样。Filter是“直接碰字节流的拦截”,比如你要把所有请求的编码统一设成UTF-8,直接给request怼一句setCharacterEncoding就行;要是想把响应里的404页面换成自定义的,Filter也能直接改response的内容。但Interceptor更偏向“业务逻辑层面的拦截”——比如用户访问/admin开头的接口,Interceptor能先查Session里的用户角色,不够权限直接返回“没资格”;甚至还能记下来这个Controller方法执行了多久,用来调优性能。我之前做电商后台的时候,用Interceptor拦了所有带@RequiresAdmin注解的方法,省得每个Controller都写一遍权限检查,而编码那类“底层活儿”就扔给Filter,俩玩意儿分工特别清楚,代码也干净。
Filter和Spring的Interceptor有什么区别?
Filter是Servlet规范的原生组件,由Tomcat等Servlet容器管理,生命周期完全由容器控制;Interceptor是Spring框架的扩展,由Spring容器管理,能深入Spring生态(比如拦截Controller方法、访问Service层Bean)。功能上,Filter可直接操作请求/响应的字节流(如修改原始编码),Interceptor更适合处理业务逻辑层面的拦截(如接口权限校验、方法执行日志)。
Filter能处理响应吗?怎么操作?
能。Filter的核心是「双向拦截」:不仅能在请求到达Servlet前处理request,还能在Servlet返回响应后处理response。比如要修改响应的字符编码,可在doFilter中调用response.setContentType(“text/html;charset=UTF-8”);若要修改响应内容(如替换页面中的静态资源路径),可通过HttpServletResponseWrapper包装原始响应对象,重写getWriter()或getOutputStream()方法实现。
Filter的url-pattern有哪些常见写法?
url-pattern用于指定Filter拦截的请求路径,常见写法有3种:
多个Filter的执行顺序是怎么确定的?
执行顺序分两种场景:
Filter里能直接获取HttpSession吗?
可以,但需要先「类型转换」。Filter的doFilter方法参数是ServletRequest(通用请求对象),而HttpSession是HTTP协议的特有对象, 需将ServletRequest强转为HttpServletRequest,再调用getSession()方法。示例:HttpServletRequest httpReq = (HttpServletRequest) request; HttpSession session = httpReq.getSession();——这样就能获取当前请求的Session,用于登录状态校验、用户信息存储等场景。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别!
站长QQ:709466365 站长邮箱:709466365@qq.com