循序渐进写一个 Servlet (5) - Filter

Servlet(Server Applet),全称 Java Servlet,是用 Java 编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态 Web 内容。本系列将一步步地写出一个 Servlet 程序。

这篇博文将演示如何创建和使用 filter。

什么是 Filter

当客户端向 servlet 容器发送请求时,请求通常会直接发送到 servlet 进行处理,就像下图这样:

Request flow without Filter

但是,如果希望在请求被 servlet 处理之前和之后,再进行一些附加的处理,就可以使用 Filter 完成。

Request flow with Filter

一个常见的使用场景是,在 filter 中定义如何检查请求是否合法,比如请求头中是否携带了有效的认证和鉴权信息;或者可以在 filter 中针对请求和响应记录日志。

怎么使用 Filter

javax.servlet.Filter 接口定义了一个 filter 的生命周期,要创建一个 filter,就要实现 Filter 接口。

Filter 接口包含下列方法声明:

  1. init(),用于定义在初始化这个 filter 时要执行的操作,该方法在 filter 的生命周期内只会执行一次;
  2. doFilter(),用于定义这个 filter 要进行的操作,每当有请求被发送到与该 filter 绑定的资源时,该方法都会被执行一次;
  3. destroy(),用于定义在停止这个 filter 时要执行的操作,只会在一个 filter 被销毁时执行。

创建一个实现 Filter 接口的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Filter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

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

}

@Override
public void destroy() {

}
}

定义这个 filter 的行为

doFilter() 方法内定义这个 filter 的行为。

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

// 在处理请求前打印信息
System.out.println("Request passing through Filter 1");

// 交由FilterChain将请求交给下一个filter或交给servlet处理
chain.doFilter(request, response);

// servlet发送响应后打印信息
System.out.println("Response passing througe Filter 1");
}

在容器中注册 filter

与 servlet 一样,filter 也需要在容器中注册之后才能发挥作用。注册 filter 也有两种方式:通过 web.xml,或者通过 @WebFilter 注解。

这里有一点需要注意,虽然 filter 之间没有依赖关系,但是如果要保证 filter 的执行顺序,那么必须使用 web.xml 来注册。

Servlet 3.0 规范8.2.3 节中有如下说明:

If the order in which the listeners, servlets, filters are invoked is important to an application then a deployment descriptor must be used.

因为使用注解注册的 filter,其调用顺序没有在规范中指定。

As described above, when using annotations to define the listeners, servlets and filters, the order in which they are invoked is unspecified.

如果一定要使用注解并保证 filter 的执行顺序,那么可以参考 Stack Overflow 中这篇回答

为了演示 filter 的执行顺序,这里再增加一个名为 Filter2 的 filter,内容与 Filter1 类似。

web.xml

web.xml 中增加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<filter>
<filter-name>filter1</filter-name>
<filter-class>com.boris.tomcatlistener.filter.Filter1</filter-class>
</filter>
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/demoServlet</url-pattern>
</filter-mapping>

<filter>
<filter-name>filter2</filter-name>
<filter-class>com.boris.tomcatlistener.filter.Filter2</filter-class>
</filter>
<filter-mapping>
<filter-name>filter2</filter-name>
<url-pattern>/demoServlet</url-pattern>
</filter-mapping>

filter 标签描述了一个 filter 的基本信息,其中 filter 名称 (filter-name) 和 filter 所在类 (filter-class) 为必填项。

filter-mapping 标签描述了一个 filter 将与哪个 URL 或者与哪个 servlet 绑定,filter-name 指定使用哪个 filter 处理请求,url-pattern 指定发往哪个 URL 的请求会触发这个 filter,servlet-name 指定发往哪个 servlet 的请求会触发这个 filter。url-patternservlet-name 可以同时存在,也可以同时存在多个。

filter-mapping 标签的先后顺序,将决定 filter 链中各个 filter 被调用的先后顺序。如上文中先配置了 filter1 后配置了 filter2,那么在请求到达时,会先执行 filter1 然后再执行 filter2

配置完毕后部署并运行该项目,向 http://localhost:8080/servletdemo/DemoServlet 发送一个请求,在控制台可以看到如下输出:

1
2
3
4
Request passing through Filter 1
Request passing through Filter 2
Response passing througe Filter 2
Response passing througe Filter 1

@WebFilter 注解

@WebFilterServlet 3.0 中新增的特性,在 Tomcat 7 及以前版本中将无法工作。

Filter1 为例,为其添加如下注解:

1
2
3
4
@WebFilter(
filterName = "filter1",
urlPatterns = "/demoServlet"
)

filter-name 属性指定了这个 filter 的名称。

有三个属性可以指定 filter 的触发条件:

  1. value
  2. urlPatterns
  3. servletNames

以上三个属性都可以接受一个字符串,或者用大括号包括起来的多个字符串。

在注解只有一个参数,并且该参数是指定要匹配的 URL 时,建议使用 value 属性,比如这样:

1
2
3
4
5
// value为默认的属性
@WebFilter("/demoServlet")

// 显式指定value属性
@WebFilter(value = "/demoServlet")

否则,建议使用 urlPatterns 属性和 servletNames 属性。不允许 valueurlPatterns 同时出现。

Servlet 3.0 规范8.1.2 @WebFilter 节中说明原文如下:

It is recommended to use value when the only attribute on the annotation is the url pattern and to use the urlPatterns attribute when the other attributes are also used. It is illegal to have both value and urlPatterns attribute used together on the same annotation.

配置完毕后部署并运行该项目,向 http://localhost:8080/servletdemo/DemoServlet 发送一个请求,在控制台可以看到如下输出:

1
2
Request passing through Filter 1
Response passing througe Filter 1

系列博文