游侠网云服务,免实名免备案服务器 游侠云域名,免实名免备案域名

统一声明:

1.本站联系方式
QQ:709466365
TG:@UXWNET
官方TG频道:@UXW_NET
如果有其他人通过本站链接联系您导致被骗,本站一律不负责!

2.需要付费搭建请联系站长QQ:709466365 TG:@UXWNET
3.免实名域名注册购买- 游侠云域名
4.免实名国外服务器购买- 游侠网云服务
JavaWeb Servlet Filter过滤器详解|核心原理实战与避坑指南

Filter到底是啥?先把原理掰碎了讲,不然用的时候容易懵

很多人学Filter只记“能拦截请求”,但原理没搞懂,用的时候总踩坑。我先给你打个比方:Filter就像小区门口的保安——快递(请求)来了,先过保安(Filter)检查,没问题再送到你家(Servlet);你要寄快递(响应),也得先过保安(Filter),再发出去。它是运行在Servlet之前的“中间件”,专门处理那些“所有请求/响应都要做的通用逻辑”。

先搞懂Filter的3个生命周期方法,不然初始化就出问题

Filter的一生就3步:init→doFilter→destroy,服务器管着它的生老病死:

  • init():服务器启动时执行一次,用来初始化资源(比如加载配置文件、连接数据库)。我之前有个同事,把数据库连接池放在init里,结果服务器启动时数据库没开,整个项目直接崩了——记住,init是“服务器一启动就执行”,得确保资源初始化稳当。
  • doFilter():每次请求都执行,是Filter的核心。你要做的登录验证、编码设置都在这儿。这里有个关键:必须调用FilterChain的doFilter()方法,不然请求就被“卡住”,到不了Servlet。我第一次写Filter时,光顾着写登录验证,忘了加chain.doFilter(request, response),结果请求到了Filter就停住了,Servlet根本没收到,调试半小时才发现。
  • destroy():服务器关闭时执行,用来释放资源(比如关闭数据库连接、IO流)。别嫌麻烦,我之前写日志Filter时忘了在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一次性解决:

  • 写Filter类:实现Filter接口,在doFilter里设置编码:
  •  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(比如字符编码)要放在前面,业务相关的Filter(比如登录验证)放在后面——我帮朋友调项目时,把登录Filter放在编码Filter后面,结果登录验证的用户名是乱码,调了半小时才改过来。
  • 用注解配置更方便,但要注意顺序:用@WebFilter注解的话,Filter的执行顺序是按类名的字母顺序(比如AFilter比BFilter先执行),如果顺序重要,还是用web.xml配置,因为web.xml里的顺序就是执行顺序。
  • 写完Filter一定要测试:测试这几点:请求能不能到Servlet?响应有没有经过Filter处理?并发情况下有没有数据串?资源有没有释放?可以用Postman发几个请求,或者用JMeter压测,确保没问题。
  • 你要是按这些方法试了,或者遇到了其他坑,欢迎在评论区告诉我——毕竟踩过的坑多了,多少有点经验。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种:

  • /:拦截所有请求(包括静态资源、Servlet、JSP);
  • /user/
  • :拦截以/user开头的所有请求(如/user/login、/user/info);3. .do:拦截以.do 的请求(如/order.do、/product.do)。注意:url-pattern不能同时包含路径前缀和后缀(如/user/.do是无效写法)。

    多个Filter的执行顺序是怎么确定的?

    执行顺序分两种场景:

  • 用web.xml配置时,filter-mapping标签的顺序就是Filter的执行顺序——先配置的Filter先拦截请求;
  • 用@WebFilter注解时,Filter的执行顺序由类名的字母顺序决定(如AFilter会比BFilter先执行)。若业务对顺序有强要求(比如「编码Filter」必须先于「登录Filter」), 用web.xml配置,避免注解的不可控性。
  • Filter里能直接获取HttpSession吗?

    可以,但需要先「类型转换」。Filter的doFilter方法参数是ServletRequest(通用请求对象),而HttpSession是HTTP协议的特有对象, 需将ServletRequest强转为HttpServletRequest,再调用getSession()方法。示例:HttpServletRequest httpReq = (HttpServletRequest) request; HttpSession session = httpReq.getSession();——这样就能获取当前请求的Session,用于登录状态校验、用户信息存储等场景。