0%

Hello,Web Filter

同Listener,一篇随性发挥的笔记性质的博文

定义

白话:Web Filter是用于过滤用户请求的过滤器,请注意,它不能直接处理用户请求。
官方解释:过滤器,是一个服务器端的组件,它可以截取用户端的请求与响应信息,并对这些信息进行过滤。

用途

  • 对用户请求进行统一认证
  • 编码转换
  • 对用户发送的数据进行过滤转换
  • 转换图像格式
  • 对响应的内容进行压缩

原理

过滤器随着web容器的创建而创建,当有用户发送对web资源的请求时,过滤器会截取这些请求,经过处理后,在发送给web资源,而资源的响应也是先发送给过滤器,等过滤器处理完之后,再由过滤器发送给用户。

生命周期

在web容器初始化的时候,根据web.xml文件进行过滤器的实例化,所以过滤器只会实例化一次,实例化之后马上调用init()方法,进行初始化,初始化之后,每次捕捉到用户的请求,都去执行doFilter()方法,也就是过滤,当web容易关闭的时候,过滤器将会执行destroy()方法进行销毁。

第一个Web Filter

代码请见jsp-basic关于Filter的章节

init()

这是过滤器的初始化方法,Web容器创建过滤器实例后将调用这个方法。这个方法可以读取web.xml文件中过滤器的参数。

doFilter()

这个方法完成实际的过滤操作。这个地方是过滤器的核心方法。当用户请求访问与过滤器关联的URL时,Web容器将先调用过滤器的doFilter方法。
FilterChain参数可以调用chain.doFilter方法,将请求传给下一个过滤器(或目标资源),或利用转发、重定向将请求转发到其他资源。

destroy()

Web容器在销毁过滤器实例前调用该方法,在这个方法中可以释放过滤器占用的资源。(大多数情况下用不到)

web.xml配置

代码如下所示:

<filter>
    <filter-name>filter的名字</filter-name>
    <filter-class>filter类的名字</filter-class>
    <init-param>//初始化的参数,可以没有,也可以有很多
        <description>可以省略的描述信息</description>
        <param-name>参数名称</param-name>
        <param-value>参数的值</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>filter的名字</filter-name>
    <url-pattern>URL</url-pattern>//当用户请求的URL和指定的URL匹配时,将触发过滤器工作
    <dispatcher>默认为REQUEST</dispatcher>//可以没有,也可以有很多,值为:REQUEST|INCLUDE|FORWORD|ERROR
</filter-mapping>

这边给的例子如下配置:

<filter>
    <filter-name>FirstFilter</filter-name>
    <filter-class>com.liumapp.jspbasic.filter.FirstFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>FirstFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

多过滤器的情况

多个过滤器处理同一个web资源

这个时候,服务器会按照web.xml中过滤器定义的先后顺序组装成一条链:用户请求 => 过滤器1 => 过滤器2 => 过滤器3 => Web资源。
在具体一点:

比如我们定义两个过滤器A和过滤器B,A的代码如下:

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    System.out.println("do FilterA");
    filterChain.doFilter(servletRequest , servletResponse);
    System.out.println("filterA end");
}    

B的代码如下:

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    System.out.println("do FilterB");
    filterChain.doFilter(servletRequest , servletResponse);
    System.out.println("filterB end");
}

那么一个用户请求过来,首先是进入过滤器A的doFilter,系统先打印出”do FilterA”,随后进入filterChain.doFilter,然后进入过滤器B的doFilter,系统再打印出”do FilterB”,然后执行Web资源的响应,然后再回到过滤器B,系统打印出”filterB end”,再回到过滤器A,系统再打印出”filterA end”,这样的一个链式操作。

过滤器的分类

即filter-mapping下dispatcher的类别(在Servlet2.5的情况下)。

REQUEST

用户直接访问页面时,Web容器将会调用过滤器

INCLUDE

目标资源通过RequestDispatcher的include方法调用时,该过滤器被调用。
或者在.jsp页面中,通过

<jsp:include page="testFilter.jsp"></jsp:include>

来访问页面,也是可以的。

FORWORD

目标资源是通过RequestDispatcher的forward访问时,该过滤器将被调用。
或者在.jsp页面中,通过

<jsp:forward page="testFilter.jsp"></jsp:forward>

来访问页面,也是能够触发改过滤器。

ERROR

目标资源是通过申明式异常处理机制调用时,过滤器将被调用。
这里以404错误为栗子:
一般而言,我们可以在web.xml文件配置一个错误页面,当系统捕捉到404错误之后,直接跳转到这个页面就结束了,但如果我们使用ERROR类型的过滤器,那么这个错误页面在返回给用户界面时,会先经过过滤器的处理再放行。

web.xml添加如下代码:

<error-page>
    <error-code>404</error-code>
    <location>/error.jsp</location>
</error-page>

<filter>
    <filter-name>ErrorFilter</filter-name>
    <filter-class>com.liumapp.jspbasic.filter.ErrorFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>ErrorFilter</filter-name>
    <url-pattern>/error.jsp</url-pattern>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

然后过滤器里面的内容就不写了,想做什么都可以,但是别忘记了要执行:

filterChain.doFilter(servletRequest , servletResponse);

REQUEST跟FORWARD的区别

现在我们有一个TestFilter,其代码如下所示:

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    System.out.println("this is testFilter begin");
    HttpServletRequest req = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;

    response.sendRedirect(req.getContextPath() + "/testFilter.jsp");

    System.out.println("this is testFilter end");

}

对它进行配置后,我们访问testFilter.jsp这个脚本,毫无疑问,系统会进入一个死循环,不断的打印”this is testFilter begin”。但如果我们将代码改成:

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    System.out.println("this is testFilter begin");
    HttpServletRequest req = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;

    //转发
    req.getRequestDispatcher("testFilter.jsp").forward(servletRequest , servletResponse);

    System.out.println("this is testFilter end");

}

然后访问testFilter.jsp,那么页面将能够正常的显示。因为Filter如果是REQUEST类型的,那么就不会去处理forward转发的请求。这里呢,我们的直接跳转就相当于request直接请求。两者的区别就在于,重定向能够让用户看到跳转的URL,而转发不会改变用户原来的URL。

Servlet3.0下的新特性

过滤器将能够支持Async异步处理,这有什么好处呢?

好处就是,在过滤器执行的期间,如果说是一个处理数据量很大的过程,那么执行的时间相对就会比较长,那么用户就必须等待比较长的时间才能看到响应的结果,使用异步的过滤器之后,就可以解决这个问题。

具体的操作也是通过@WebFilter,它用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。

@WebFilter常用属性

  • filterName :String类型,相当于filter-name
  • value :String[]类型,相当于urlPatterns
  • urlPatterns :String[]类型,指定一组过滤器的URL匹配模式,等价于url-pattern,注意,它跟value不能同时使用
  • servletNames :String[]类型,指定过滤器将应用于哪些Servlet,取值是@WebFilter中的name属性的取值,或者是web.xml中servlet-name的值。
  • dispatcherTypes :指定过滤器的转发模式,有ASYNC、ERROR、FORWARD、INCLUDE、REQUEST
  • initParams :WebInitParam[],指定一组过滤器初始化参数,等价于init-param。
  • asyncSupported :boolean类型,声明过滤器是否支持异步操作模式,等价于async-supported
  • description :String类型,该过滤器的描述信息,等价于description
  • displayName :String类型,该过滤器的显示名称,等价于display-name,通常配合工具使用。

Async实例

源码请看 servlet3-jsp-basic下面的AsyncFilter。

注意

  • 过滤器能够改变用户请求的Web资源,即能够改变用户请求的路径。
  • 过滤器不是一个标准的Servlet,所以不能够直接返回数据,即不能直接处理用户请求。