写在前面
这一部分来学习Servlet中的Filters,Filter是拦截Request请求的对象: 在用户的请求访问资源前处理ServletRequest以及ServletResponse, 它可用于日志记录、 加解密、 Session检查、 图像文件保护等。 通过Filter可以拦截处理某个资源或者某些资源。Filter的配置可以通过Annotation或者部署描述来完成。当一个资源或者某些资源需要被多个Filter所使用到,且它的触发顺序很重要时, 只能通过部署描述来配置。
Filter API
Filter相关的接口, 包含Filter、 FilterConfg、 FilterChain。
Filter的实现必须继承javax.servlet.Filter接口。 这个接口包含了Filter的3个生命周期: init、 doFilter、destroy。
Servlet容器初始化Filter时, 会触发Filter的init方法, 一般来说是在应用开始时。 也就是说, init方法并不是在该Filter相关的资源使用到时才初始化的, 而且这个方法只调用一次, 用于初始化Filter。 init方法的定义如下:
void init(FilterConfig filterConfig)
当Servlet容器每次处理Filter相关的资源时, 都会调用该Filter实例的doFilter方法。 Filter的doFilter方法包含ServletRequest、 ServletResponse、 FilterChain这3个参数。doFilter的定义如下:
void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
doFilter的实现中访问ServletRequet、 ServletResponse。 这也就意味着允许给ServletRequest增加属性或者增加Header。 当然也可以修饰ServletRequest或者ServletRespone来改变它们的行为。
在Filter的doFilter的实现中, 最后一行需要调用FilterChain中的doChain方法。 注意Filter的doFilter方法里的第3个参数, 就是filterChain的实例:
filterChain.doFilter(request, response)
一个资源可能需要被多个Filter关联到(更专业一点来说, 这应该叫作Filter链条) , 这时Filter.doFilter()的方法将触发Filter链条中下一个Filter。 只有在Filter链条中最后一个Filter里调用的FilterChain.doFilter(), 才会触发处理资源的方法。
在Filter.doFilter()的实现中, 没有在结尾处调用FilterChain.doFilter()的方法, 那么该Request请求中止, 后面的处理就会中断。
Filter接口中, 最后一个方法是destroy, 它的定义如下:
void destroy()
该方法在Servlet容器要销毁Filter时触发, 一般在应用停止的时候进行调用。
除非Filter在部署描述中被多次定义到, 否则Servlet窗口只会为每个Filter创建单一实例。 由于Serlvet/JSP的应用通常要处理用户并发请求, 此时Filter实例需要同时被多个线程所关联到, 因此需要非常小心地处理多线程问题。
Filter配置
当完成Filter的实现后, 就可以开始配置Filter了。Filter的配置需要如下步骤:
- 确认哪些资源需要使用这个Filter拦截处理。
- 配置Filter的初始化参数值, 这些参数可以在Filter的init方法中读取到;
- 给Filter取一个名称。 一般来说, 这个名称没有什么特别的含义, 但在一些特殊的情况下, 这个名字十分有用。 例如, 要记录Filter的初始化时间, 但这个应用中有许多的Filter, 这时它就可以用来识别Filter了。
FilterConfig接口允许通过它的getServletContext的方法来访问ServletContext:
ServletContext getServletContext()
如果配置了Filter的名字, 在FilterConfig的getFilterName中就可以获取Filter的名字。 getFilterName的定义如下:
java.lang.String getFilterName()
最重要的还是要获取到开发者或者运维给Filter配置的初始化参数。 为了获取这些初始化参数,需要用到FilterConfig中的两个方法, 第一个方法是getParameterNames:
java.util.Enumeration<java.lang.String> getInitParameterNames()
这个方法返回Filter参数名字的Enumeration对象。如果没有给这个Filter配置任何参数, 该方法返回的是空的Enumeration对象。第二个方法是getParameter:
java.lang.String getInitParameter(java.lang.String parameterName)
有两种方法可以配置Filter: 一种是通过WebFilter的Annotation来配置Filter, 另一种是通过部署描述来注册。 使用@WebFilter的方法, 只需要在Filter的实现类中增加一个注解即可, 不需要重复地配置部署描述。 当然, 此时要修改配置参数, 就需要重新构建Filter实现类了。 换句话说, 使用部署描述意味着修改Filter配置只要修改一下文本文件就可以了。
使用@WebFilter, 你需要熟悉下表中所列出来的参数, 这些参数是在WebFilter的Annotation里定义的。所有参数都是可选的。
属性 | 描述 |
---|---|
asyncSupported | Filter是否支持异步操作 |
description | Filter的描述 |
dispatcerTypes | Filter所生效范围 |
displayName | Filter的显示名 |
filterName | Filter的名称 |
initParams | Filter的初始化参数 |
largeIcon | Filter的大图名称 |
servletName | Filter所生效的Servlet名称 |
smallIcon | Filter的小图名称 |
urlPatterns | Filter所生效的URL路径 |
value | Filter所生效的URL路径 |
下述@WebFilter标注配置了一个Filter, 该名称为DataCompressionFilter, 且适用于所有资源:
@WebFilter(filterName="DataCompressionFilter", urlPatterns={"/*"})
如果使用部署描述中的filter、 filter-mapping元素定义, 那么它的内容如下:
<filter>
<filter-name>DataCompressionFilter</filter-name>
<filter-class>
the fully-qualified name of the filter class
</filter-class>
</filter>
<filter-mapping>
<filter-name>DataCompresionFilter</filter-name>
<url-pattern>/ *</url-pattern>
</filter-mapping>
下述的Filter配置, 描述了两个初始化参数:
@WebFilter(filterName = "Security Filter", urlPatterns = { "/ *" },
initParams = {
@WebInitParam(name = "frequency", value = "1909"),
@WebInitParam(name = "resolution", value = "1024")
})
如果使用部署描述中的filter、 filter-mapping元素,那么该配置应该为:
<filter>
<filter-name>Security Filter</filter-name>
<filter-class>filterClass</filter-class>
<init-param>
<param-name>frequency</param-name>
<param-value>1909</param-value>
</init-param>
<init-param><param-name>resolution</param-name>
<param-value>1024</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DataCompresionFilter</filter-name>
<url-pattern>/ *</url-pattern>
</filter-mapping>
Filter顺序
如果多个Filter应用于同一个资源, Filter的触发顺序将变得非常重要, 这时就需要使用部署描述来管理Filter: 指定哪个Filter先被触发。 例如: Filter 1需要在Filter 2前被触发, 那么在部署描述中, Filter 1需要配置在Filter 2之前,通过部署描述之外的配置来指定Filter触发的顺序是不可能的。代码如下:
<filter>
<filter-name>Filter1</filter-name>
<filter-class>
the fully-qualified name of the filter class
</filter-class>
</filter>
<filter>
<filter-name>Filter2</filter-name>
<filter-class>
the fully-qualified name of the filter class
</filter-class>
</filter>