JavaWeb 中 Filter过滤器

Filter过滤器

每博一文案

师傅说:人生无坦途,累是必须的背负,看多了,人情人暖,走遍了离合聚散,有时会
在心里对自己说,我想,我是真的累了,小时候有读不完的书,长大后有赚不尽的力。
白天在外要奋斗打拼,把心事都藏起来,笑脸相迎,做一个合格的员工,晚上回家要照顾家人。
把家务都打理的井井有条,做一个称职的伴侣,习惯了所有事情,自己扛,习惯了所有委屈自己消化,
有时候莫名的低落,什么话都不想说,只想一个静静的发呆,有时会突然的烦躁,什么事都不想做,
只想让自己好好的放松,偶尔也会向往过一份属于自己的生活。
没有那么多责任,要背负只做自己想做的事,累了就停下类休息吧,烦了就给自己放个假吧。
这个世上没有铁打的身体,该休息时就得休息。
这个世上没有坚强的心灵,该哭泣时就该哭泣。
看看碧海蓝天,听听轻歌曼舞,会会知己老友,品品清茶,美酒,生活本就可以多姿多彩。
人生说到底,活的是心气,为累过,方知闲,为苦过,方知甜,随缘自在,勿忘心安,便是活着的最美状态。
                                       ——————《一禅心灵庙语1》

@

1. Filter 过滤器的概述

在一个比较复杂的Web应用程序中,通常都有很多URL映射,对应的,也会有多个Servlet来处理URL。

我们考察这样一个论坛应用程序:

            ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
               /             ┌──────────────┐
            │ ┌─────────────>│ IndexServlet │ │
              │              └──────────────┘
            │ │/signin       ┌──────────────┐ │
              ├─────────────>│SignInServlet │
            │ │              └──────────────┘ │
              │/signout      ┌──────────────┐
┌───────┐   │ ├─────────────>│SignOutServlet│ │
│Browser├─────┤              └──────────────┘
└───────┘   │ │/user/profile ┌──────────────┐ │
              ├─────────────>│ProfileServlet│
            │ │              └──────────────┘ │
              │/user/post    ┌──────────────┐
            │ ├─────────────>│ PostServlet  │ │
              │              └──────────────┘
            │ │/user/reply   ┌──────────────┐ │
              └─────────────>│ ReplyServlet │
            │                └──────────────┘ │
             ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─

各个Servlet设计功能如下:

  • IndexServlet:浏览帖子;
  • SignInServlet:登录;
  • SignOutServlet:登出;
  • ProfileServlet:修改用户资料;
  • PostServlet:发帖;
  • ReplyServlet:回复。

其中,ProfileServlet、PostServlet和ReplyServlet都需要用户登录后才能操作,否则,应当直接跳转到登录页面。

我们可以直接把判断登录的逻辑写到这3个Servlet中,但是,同样的逻辑重复3次没有必要,并且,如果后续继续加Servlet并且也需要验证登录时,还需要继续重复这个检查逻辑。

为了把一些公用逻辑从各个Servlet中抽离出来,JavaEE的Servlet规范还提供了一种Filter组件,即过滤器,它的作用是,在HTTP请求到达Servlet之前,可以被一个或多个Filter预处理,类似打印日志、登录检查等逻辑,完全可以放到Filter中。

什么是 Filter 过滤器:

  1. Filter 过滤器它是 JavaWeb 的三大组件之一。
    三大组件分别是:Servlet 程序、Listener 监听器、Filter 过滤器
  2. Filter 过滤器它是 JavaEE 的规范。也就是接口
  3. Filter 过滤器它的作用是:拦截请求,过滤响应。

拦截请求常见的应用场景有:
1.权限检查 2.日记操作 3.事务管理 ……等等

一般情况下,都是在过滤器当中编写公共代码。提高代码的复用性.

2. Filter 过滤器的编写

  • 第一步:编写一个Java类实现一个接口:jarkata.servlet.Filter。并且实现这个接口当中所有的方法。

  • init( )方法:在Filter对象第一次被创建之后调用,并且只调用一次。与Servlet中的init()方法类似,filter中的init()方法用于初始化过滤器。开发者可以在init()方法中完成与构造方法类似的初始化功能。如果初始化代码中要用到FilterConfig对象,则这些初始化代码只能在filter的init()方法中编写,而不能在构造方法中编写。 default 是接口中的一个默认方法,基于 jdk8 新特性,默认方法可以不用重写,如果有需要也是可以重写的.
public default void init(FilterConfig filterConfig) throws ServletException {}
  • doFilter( )方法:只要用户发送一次请求,则执行一次。发送N次请求,则执行N次。在这个方法中编写过滤规则。需要注意的是,服务器响应的时候,该方法也是会被执行的。doFilter方法类似于Servlet接口的service()方法。当客户端请求目标资源时,容器会筛选出符合<filter-mapping>标签中<url-pattern>的filter,并按照声明<filter-mapping>的顺序依次调用这些filter的doFilter()方法。doFilter()方法有多个参数,其中参数requestresponse为Web服务器或filter链中的上一个filter传递过来的请求和响应对象。参数chain代表当前filter链的对象,只有当前filter对象中的doFilter()方法内部需要调用FilterChain对象的doFilter方法时,才能把请求交付给filter链中的下一个filter或目标程序处理。这个是抽象方法,必须重写。
 public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;
  • destroy( )方法:在Filter对象被释放/销毁之前调用,并且只调用一次。 filter中的destroy()方法Servlet中的destroy()作用类似,在Web服务器卸载filter对象之前被调用,用于释放被filter对象打开的资源。 default 是接口中的一个默认方法,基于 jdk8 新特性,默认方法可以不用重写,如果有需要也是可以重写的.
 public default void destroy() {}
package com.RainbowSea.filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;

import java.io.IOException;

public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("TestFilter 中的 init() 方法 初始化 执行了");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("TestFilter 中的 doFilter() 方法执行了");
    }

    @Override
    public void destroy() {
        System.out.println("TestFilter 中的 destroy() 方法 销毁 执行了");
    }
}

  • 第二步:在web.xml文件中对 Filter进行配置。这个配置和 Servlet很像。
<?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">

    <filter>
        <!--        两个 name 名是要保持一致的-->
        <filter-name>filter</filter-name>
        <!--        对应的全类路径名,全类限定名-->
        <filter-class>com.RainbowSea.filter.TestFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>filter</filter-name>
        <!--        Filter 的映射路径,以 / 开始-->
        <url-pattern>/a.do</url-pattern>
    </filter-mapping>
    
    
</web-app>

如下是关于: Filter的所有的 web.xml 中的配置属性信息:

标签 作用
<filter> 指定一个过滤器
<filter-name> 用于为过滤器指定一个名称,该元素的内容不能为空
<filter-class> 用于指定过滤器的完整的限定类名
<init-param> 用于为过滤器指定初始化参数
<param-value> <init-param>的子参数,用于指定参数的名称
<filter-mapping> 用于设置一个filter所负责拦截的资源
<filter-name> <filter-mapping>子元素,用于设置filter的注册名称。该值必须是在<filter>元素中声明过的过滤器的名称
<url-pattern> 用于设置filter所拦截的请求路径
<servlet-name> 用于指定过滤器所拦截的Servlet名称

执行效果:

如果Servlet版本大于3.0,也可以使用注解 @WebFilter()的方式来配置filter。

如下:

package com.RainbowSea.filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;


@WebFilter("/a.do")
public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("TestFilter 中的 init() 方法 初始化 执行了");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("TestFilter 中的 doFilter() 方法执行了");
    }

    @Override
    public void destroy() {
        System.out.println("TestFilter 中的 destroy() 方法 销毁 执行了");
    }
}

如下是关于:@WebFilter() 的属性的说明:

属性 类型 是否必须 说明
asyncSupported boolean 指定filter是否支持异步模式
dispatcherTypes DispatcherType[] 指定filter对哪种方式的请求进行过滤
filterName String filter名称
initParams WebInitParam[] 配置参数
displayName String filter显示名
servletNames String[] 指定对哪些Servlet进行过滤
urlPatterns/value String[] 两个属性作用相同,指定拦截的路径

web.xml可以配置的filter属性都可以通过@WebServlet的方式进行配置。不过一般不推荐使用注解方式来配置filter,因为如果存在多个过滤器,使用web.xml配置filter可以控制过滤器的执行顺序,如果使用了注解方式,则不可以这样做了。该 Filter 执行顺序该文章的后面会详细说明,所以请大家,耐心阅读。谢谢。

3. Filter 过滤器的执行过程解析

当用户向服务器发送request请求时,服务器接受该请求,并将请求发送到第一个过滤器中进行处理。如果有多个过滤器,则会依次经过filter2,filter3,…,filter n。接着调用Servlet中的service()方法,调用完毕后,按照与进入时相反的顺序,从过滤器filter n开始,依次经过各个过滤器,直到过滤器filter 1.最终将处理后的结果返回给服务器,服务器再反馈给用户。

filter进行拦截的方式也很简单,在HttpServletRequest到达Servlet之前,filter拦截客户的HttpServletRequest,根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据。在HttpServletRequest到达客户端之前,拦截HttpServletRequest,根据需要检查HttpServletRequest,也可以修改HttpServletResponse头和数据。

3.1 Filter 过滤结合 Servlet 的使用

想要让 Filter 可以过滤用户对 Servlet 发送的请求,必须满足如下两个条件:

  • 第一个:在 Filter 过滤器当中的 doFilter() 方法中编写:chain.doFilter(request, response) 方法:该方法的作用是:执行下一个过滤器,如果下面没有过滤器了(Filter 过滤器之间的 映射路径是相同的情况下),执行最终的Servlet(在Servlet 与 Filter 过滤器的映射路径是相同的情况下。)
@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 执行下一个过滤器,如果下面没有过滤器了,执行最终的Servlet
        chain.doFilter(request,response);
    } 
  • 第二:用户发送的请求路径是否和Servlet的请求路径一致。而 Filter过滤器的映射路径是否包含/和Servlet的请求路径一致。只有 Filter 过滤器映射路径包含/和 Servlet 的请求映射路径是一致的,Filter才会过滤该用户方法的请求信息。
  • 注意:Filter的优先级,天生的就比Servlet优先级高。:比如:/a.do 对应一个Filter,也对应一个Servlet。那么一定是先执行Filter,然后再执行Servlet。

举例:

package com.RainbowSea.filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;


@WebFilter("/a.do")
public class TestFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("TestFilter 中的 doFilter() 方法 begin 开始执行了");

        // 表示:执行下一个 Filter(同一个 映射的路径,如果有下一个Filter 的话),没有就执行(同一个映射的路径的 Servlet )
        chain.doFilter(request,response);

        System.out.println("TestFilter 中的 doFilter() 方法 end 执行结束");
    }

}

package com.RainbowSea.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("/a.do")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {
        System.out.println("AServlet 执行了");
    }
}

测试效果:

案例举例:

LoginFilter 过滤器,获取到客户端发送过来的请求,进行一个过滤,判断用户的账号和密码是否正确,正确的话,LoginFilter 过滤器放行,到 LogServlet 表示登录成功。如果用户的账号和密码是错误的,则让提示用户密码错误,请重新登录。

这里为了方便演示核心,我们就将 账号和密码写死了,账号为: admin ,密码为 123

package com.RainbowSea.filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;


@WebFilter("/login")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest requ, ServletResponse resp, FilterChain chain) throws IOException,
            ServletException {


        HttpServletRequest request = (HttpServletRequest) requ;
        HttpServletResponse  response= (HttpServletResponse) resp;

        request.setCharacterEncoding("UTF-8");  // 设置获取到的请求信息的字符编码:

        // 获取到用户的请求信息

        String name = request.getParameter("user");
        String password = request.getParameter("password");


        // 过滤器:判断用户登录的账号和密码是否正确
        if ("admin".equals(name) && "123".equals(password)) {
            // 正确:放行
            // 表示:执行下一个 Filter(同一个 映射的路径,如果有下一个Filter 的话),没有就执行(同一个映射的路径的 Servlet )
            chain.doFilter(request,response);

        } else {
            // 跳转至登录失败的页面
            response.sendRedirect(request.getContextPath()+"/error.jsp");
        }
    }
}

package com.RainbowSea.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;
import java.io.PrintWriter;


@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charSet=utf-8");

        PrintWriter writer = response.getWriter();

        writer.println("登录成功");
    }
}


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

<h3>登录失败,请重新登录</h3>

</body>
</html>

Filter 过滤器的生命周期

当Web容器启动时,会根据web.xml中声明的filter顺序依次实例化这些filter。然后在Web应用程序加载时调用init()方法,随机客户端有请求时调用doFilter()方法,并且根据实际情况的不同,doFilter()方法可能被调用多次。最后Web应用程序卸载时调用destroy()方法。

Filter 过滤器与 Servlet 的区别:

  • servlet对象默认情况下,在服务器启动的时候是不会新建对象的。
  • Filter对象默认情况下,在服务器启动的时候会新建对象。
  • Servlet是单例的。Filter也是单例的。(单实例,但是它们都是假单例,因为真单例的构造器是 private 的,而这两个类都不是私有的构造器)
  • Filter 和Servlet对象生命周期一致。
  • 唯一的区别:Filter默认情况下,在服务器启动阶段就实例化。Servlet不会。
  • Filter的优先级,天生的就比Servlet优先级高。比如:/a.do 对应一个Filter,也对应一个Servlet。那么一定是先执行Filter,然后再执行Servlet。

4. Filter 过滤器的拦截路径:

关于Filter的配置路径:不同的 Filter 映射路径,所拦截用户请求的也是不一样的。

关于 Filter 过滤器路径的配置:大致可以分别如下四种:

  • 精确匹配路径:/a.do、/b.do、/dept/save。这些配置方式都是精确匹配。

  • 目录匹配:/admin/*

  • 匹配所有路径: /*
  • 前后缀名路径匹配:后缀:*.do 后缀匹配。不要以 / 开始,前缀:/dept/* 前缀匹配。

4.1 精确匹配路径

只有完美匹配,一个符号,一个字符,不能错误的路径。Filter 只会过滤用户访问该:/target/dep路径的信息才会拦截判断是否放行。其他用户访问的路径一概不会进行 Filter 过滤器过滤

<filter-mapping>
    <filter-name>Filter</filter-name>
    <url-pattern>/target/dep</url-pattern>  <!--精确匹配-->
</filter-mapping>

4.2 目录匹配

Filter 会过滤用户访问该:/admin/* admin子路径包含 admin路径的信息:比如:/admin/test/admin/test/test2才会拦截判断是否放行。其他用户访问的路径一概不会进行 Filter 过滤器过滤。

<filter-mapping>
    <filter-name>Filter</filter-name>
    <url-pattern>/admin/*</url-pattern> <!--目录匹配-->
</filter-mapping>

4.3 前后缀名路径匹配

后缀路径匹配:以上配置的路径,表示请求地址必须以.do 结尾才会被 Filter 过滤器拦截判断是否放行。

注意的是:不要以 / 开始 ,不然就失效了。

<filter-mapping>
    <filter-name>Filter</filter-name>
    <url-pattern>*.do</url-pattern> <!--目录匹配-->
</filter-mapping>

前缀路径匹配:以上配置的路径,表示请求地址必须以/admin/ 开头才会被 Filter 过滤器拦截判断是否放行

<filter-mapping>
    <filter-name>Filter</filter-name>
    <url-pattern>/admin/*</url-pattern> <!--目录匹配-->
</filter-mapping>

4.4 所有路径匹配

/* 表示对应任何请求地址,Filter 过滤器都会进行一个拦截判断是否放行,那么这个路径的资源不存在也会,进行一个拦截判断是否放行。这种方式虽然简单,但是,这个代价比较到,效率低,对于特殊的路径请求要放行的你可能需要编写大量的逻辑判断进行一个拦截放行。不建议使用。

<filter-mapping>
    <filter-name>Filter</filter-name>
    <url-pattern>/*</url-pattern> <!--目录匹配-->
</filter-mapping>

5. 设置 Filter 执行顺序

一个 Servelt 是可以设置多个 Filter 过滤器的,当我们设置了多个 Filter 过滤器,其中 Filter 过滤器的执行顺序该如何设置呢?

从上面文章的内容,我们知道了 Filter 的映射路径设置有两种方式:

  1. 注解:@WebFilter()
  2. 配置 web.xml 文件的方式。这种方式 推荐使用

两种设置 Filter的方式不同,对于设置的执行顺序的方式也是不一样的。

对于 Filter 执行顺序的设置,虽然有两种方式,但是推荐使用 web.xml 配置文件的方式,因为使用 注解@WebFilter() 的方式的话,我们需要更加定义的类名的字母顺序的方式来,设置Filter的执行顺序,这样会改变一个类名的见名之意。而如果是使用 web.xml 配置文件的方式,直接更加 Filtet 编写的配置是先后顺序就可以了。

过滤器的调用顺序,遵循栈数据结构。

第一种:注解的方式:设置Filter过滤器的执行顺序:

注解的方式:

执行顺序是:比较 Filter 这个类名。就是你定义的这个类名 implements (实现) Filter 的类名

  • 比如:FilterA和FilterB,则先执行FilterA
  • 比如:Filter1和Filter2,则先执行Filter1

举例验证:

通过:配置 web.xml 文件的方式,如何设置 Filter 的执行顺序:

web.xml 中是:依靠 <filter-mapping>标签的配置位置,越靠上优先级越高,就越先执行其中的 Filter的 doFilter() 方法。

举例证实:

<?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">

    <filter>
        <filter-name>FilterB</filter-name>
        <filter-class>com.RainbowSea.filter.FilterB</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>FilterB</filter-name>
        <url-pattern>/A</url-pattern>
    </filter-mapping>

    <filter>
        <!--        两个 name 名是要保持一致的-->
        <filter-name>FilterA</filter-name>
        <!--        对应的全类路径名,全类限定名-->
        <filter-class>com.RainbowSea.filter.FilterA</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>FilterA</filter-name>
        <!--        Filter 的映射路径,以 / 开始-->
        <url-pattern>/A</url-pattern>
    </filter-mapping>




</web-app>
package com.RainbowSea.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("/A")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException,
            IOException {
        System.out.println("AServlet doGet()  执行了");


    }
}

package com.RainbowSea.filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;


import java.io.IOException;



public class FilterB implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        System.out.println("FilterB doFilter() 执行了");

        // 表示执行后面的(映射路径相同的Filter 过滤器),如果后面没有的话执行(映射路径相同的 Servlet)
        chain.doFilter(request,response);

    }
}

package com.RainbowSea.filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;


import java.io.IOException;




public class FilterA implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("FilterA  doFilter() 执行了");

        // 表示执行后面的(映射路径相同的Filter 过滤器),如果后面没有的话执行(映射路径相同的 Servlet)
        chain.doFilter(request,response);
    }
}

6. Filter 过滤器中的责任链设计模式思想

23种设计模式 : 责任链设计模式

过滤器最大的优点:

在程序编译阶段不会确定调用顺序。因为Filter的调用顺序是配置到web.xml文件中的,只要修改web.xml配置文件中filter-mapping的顺序就可以调整Filter的执行顺序。显然Filter的执行顺序是在程序运行阶段动态组合的。那么这种设计模式被称为责任链设计模式。

责任链设计模式最大的核心思想:在程序运行阶段,动态的组合程序的调用顺序。在上面对于 Filter的使用当中,我们已经体验到了,Filter 的动态调用其中的 doFilter() 方法,通过修改其中的 web.xml 对 Filter 的配置顺序。

下面我们演示一下不是 :责任链设计模式的方式:

如下这种方式:是我们写死了的,想要改变其中的执行顺序,就必须通过修改其中的源码当中的,代码的执行顺序,无法通过通过配置文件的方式,修改调用的顺序。

package com.RainbowSea.filter;

public class Test {
    public static void main(String[] args) {
        m1();
    }

    private static void m1() {
        System.out.println("m1() begin ");
        m2();
        System.out.println("m1() end ");
    }

    private static void m2() {
        System.out.println("m2() begin ");
        m3();
        System.out.println("m2() end ");
    }

    private static void m3() {
        System.out.println("m3() begin ");

        System.out.println("m3() end ");
    }
}

7. 运用 Filter 过滤器的方式优化 oa 项目的一个登录验证:

关于 oa 项目的,大家可以移步至🔜🔜🔜 B/S 结构系统的 缓存机制(Cookie) 以及基于 cookie 机制实现 oa 十天免登录的功能_ChinaRainbowSea的博客-CSDN博客 先了解,有助于后面的阅读。

思路:

有什么情况下不能拦截: 要过滤通过:

目前编写的路径是/* 表示所有请求均拦截:

用户访问 index.jsp 的时候不能拦截, 要放行:
用户登录过了,这个时候,需要放行。
用户要去登录,这个也是不能拦截,要放行:让用户登录。
其他情况:比如,没有登录过,就想访问资源的话,过滤拦截,
登录失败,过滤拦截
用户退出,不能拦截,要让其过去

需要注意的是:这里我们 Filter 过滤器当中的 doFilter() 方法中的 request 是来自: ServletRequest 接口的,当时如下使用的一些方法 sendRedirect() 是来自于: HttpServletRequest 这个接口的,所以我们需要强制类型转换一下。 HttpServletRequest 是 extends 继承了 ServletRequest 接口的。

package com.RainbowSea.Filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
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 LoginCheckFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        // 需要强制类型转换以下,因为 下面的一些getServletPath 方法是来自 HttpServletRequest的
        //不是 来自 ServletRequest 的
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        String servletPath = request.getServletPath();  // 获取到浏览器当中的uri

        // 获取session 这个 session 是不不需要新建的
        // 只是获取当前session ,获取不到这返回null,
        HttpSession session = request.getSession(false);  // 获取到服务器当中的session ,没有不会创建的



        // 过滤器:
        /*
        有什么情况下不能拦截: 要过滤通过:
          1. 目前编写的路径是/* 表示所有请求均拦截:

          用户访问 index.jsp 的时候不能拦截, 要放行:
          用户登录过了,这个时候,需要放行。
          用户要去登录,这个也是不能拦截,要放行:让用户登录。
          其他情况:比如,没有登录过,就想访问资源的话,过滤拦截,
          登录失败,过滤拦截
          用户退出,不能拦截,要让其过去


           if ("/dept/list".equals(servletPath)) {
                doList(request, response);
            } else if ("/dept/detail".equals(servletPath)) {
                doDetail(request, response);
            } else if ("/dept/delete".equals(servletPath)) {
                doElete(request,response);
            } else if("/dept/save".equals(servletPath)) {
                doSave(request,response);
            } else if("/dept/modify".equals(servletPath)) {
                doModify(request,response);


         */

        if("/index.jsp".equals(servletPath) || (("/welcome").equals(servletPath)) ||
                ( session != null && session.getAttribute("username") != null)
        || "/user/login".equals(servletPath) || "/user/exit".equals(servletPath)) {
            // 双重的判断,一个是 session 会话域要存在,其次是 会话域当中存储了名为 "username" 的信息

            chain.doFilter(request,response);  // 放行,让其向下一个过滤器,或者是Servlet 执行
        } else {
            response.sendRedirect(request.getContextPath() + "/index.jsp");  // 访问的web 站点的根即可,自动找到的是名为 index.jsp
            // 的欢迎页面(注意这里被优化修改了:局部优先)注意:这里修改了,需要指明index.jsp登录页面了,因为局部优先
        }
    }
}

8. 总结:

  1. Filter 过滤器它是 JavaEE 的规范。也就是接口Filter 过滤器它的作用是:拦截请求,过滤响应。
  2. Filter 过滤器的常用的三个方法:
  • init方法:在Filter对象第一次被创建之后调用,并且只调用一次。
  • doFilter方法:只要用户发送一次请求,则执行一次。发送N次请求,则执行N次。在这个方法中编写过滤规则。
  • destroy方法:在Filter对象被释放/销毁之前调用,并且只调用一次。
  1. 目标Servlet是否执行,取决于两个条件:
  • 第一:在过滤器当中是否编写了:chain.doFilter(request, response); 代码。
  • 第二:用户发送的请求路径是否和Servlet的请求路径一致。
  1. chain.doFilter(request, response); 这行代码的作用:执行下一个过滤器,如果下面没有过滤器了,执行最终的Servlet。

  2. 注意:Filter的优先级,天生的就比Servlet优先级高。

  • /a.do 对应一个Filter,也对应一个Servlet。那么一定是先执行Filter,然后再执行Servlet。
  1. Filter 的执行原理图:

  2. Filter 过滤器拦截路径:

    • /a.do、/b.do、/dept/save。这些配置方式都是精确匹配。
    • /* 匹配所有路径。
    • *.do 后缀匹配。不要以 / 开始
    • /dept/* 前缀匹配
  3. Filter 过滤器的设置执行顺序:两种方式:

  • 在web.xml文件中进行配置的时候,依靠filter-mapping标签的配置位置,越靠上优先级越高。

  • @WebFilter的时候,执行顺序是:比较Filter这个类名。

    • 比如:FilterA和FilterB,则先执行FilterA。
    • 比如:Filter1和Filter2,则先执行Filter1
  1. Filter 过滤器中的责任链设计模式思想:责任链设计模式最大的核心思想:在程序运行阶段,动态的组合程序的调用顺序
  2. 编写 Filter 过滤器的技巧:先想明白一个就是:哪些资源/功能方法是需要验证才能放行的,哪些是不需要验证直接就放行通过的。想明白这两点,基本上编写的 Filter 过滤器没有任何问题了。

9. 最后:

⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐ 感谢如下博主的分享: ⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,江湖再见,后会有期!!!

热门相关:斗神战帝   名门天后:重生国民千金   战神   学霸女神超给力   战神