概念
在容器调用Servlet的service()方法前,Servlet并不会知道有请求的到来,而在Servlet的service()方法运行后,容器真正对浏览器进行HTTP响应之前,浏览器也不会知道Servlet真正的响应是什么。过滤器(Filter)正如其名称所示,是介于Servlet之前,可拦截过滤浏览器对Servlet的请求,也可以改变Servlet对浏览器的响应。
性能评测、用户验证、字符替换、编码设置等需求,基本上与应用程序的业务需求没有直接的关系,只是应用程序额外的元件服务之一。你可能只是短暂需要它,或者需要整个系统应用相同设置,不应该为了一时的需要而修改代码强加入原有业务流程中。例如,性能的评测也许只是开发阶段才需要的,上线之后就要拿掉性能评测的功能,如果直接将性能评测的代码编写在业务流程中,那么要拿掉这个功能,就又得再修改一次源代码。因此,如性能评测、用户验证、字符替换、编码设置这类的需求,应该设计为独立的元件,随时可以加入应用程序中,也随时可以移除,或随时可以修改设置而不用修改原有的程序。这类元件就像是一个过滤器,安插在浏览器与Servlet中间,可以过滤请求与响应而作进一步的处理。并可以视需求抽换过滤器或调整过滤器的顺序,也可以针对不同的URL应用不同的过滤器。甚至在不同的Servlet间请求转发或包含时应用过滤器。
小提示
性能评测,记录请求与响应间的时间差。
字符替换,假设有个留言版程序已经上线并正常运作中,但是现在发现,有些用户会在留言中输入一些HTML标签。基于安全性的考量,不希望用户输入的HTML标签直接出现在留言中而被浏览器当作HTML的一 部分。例如,并不希望用户在留言中输入< a href=" ">www.sina.com.cn</ a>这样的信息,不想信息在留言显示中直接变成超链接,让用户有机会在留言版中打广告。这时我们希望将一些HTML字符过滤掉,如将<、> 角括号置换为HTML实体字符<与>。
声明
方法一:在实现HttpFilter的类上标注@WebFilter
@WebFilter("/*") public class TestFilter extends HttpFilter { @Override protected void doFilter( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { // ... } }
方法二:在web.xml中声明
<web-app ...> <filter> <filter-name>testFilter</filter-name> <filter-class>com.Test.TestFilter</filter-class> </filter> <filter-mapping> <filter-name>testFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
过滤策略的指定
指定过滤策略时,除了URL模式之外,也可以通过Servlet名称来指定,这可以通过@WebServlet的servletNames来设置。
方式一:注解方式
@WebFilter(servletNames={"SomeServlet"})
方式二:web.xml方式,通过 <filter-mapping> 中的 <servlet-name> 标签來設定。下面的Demo会使用这种方式。
<filter-mapping> <filter-name>performance</filter-name> <servlet-name>SomeServlet</servlet-name> </filter-mapping>
如果同时具备<url-pattern>与<servlet-name>,则先比对<url-pattern>,再比对<servlet-name>。
过滤器初始化参数设置
方式一:注解方式
@WebFilter( urlPatterns={"/*"}, initParams={ @WebInitParam(name = "PARAM1", value = "VALUE1"), @WebInitParam(name = "PARAM2", value = "VALUE2") } ) public class TestFilter extends HttpFilter { private String PARAM1; private String PARAM2; @Override public void init() throws ServletException { PARAM1 = getInitParameter("PARAM1"); PARAM2 = getInitParameter("PARAM2"); } //... }
方式二:web.xml方式,web.xml中的设置会覆盖注解方式的设置
<filter> <filter-name>TestFilter</filter-name> <filter-class>com.test.TestFilter</filter-class> <init-param> <param-name>PARAM1</param-name> <param-value>VALUE1</param-value> </init-param> <init-param> <param-name>PARAM2</param-name> <param-value>VALUE2</param-value> </init-param> </filter>
触发策略
触发过滤器的时机,默认是浏览器发出请求。如果是那些通过RequestDispatcher的forward()或include()来转发或包含的请求,需要设置@WebFilter的dispatcherTypes。
方式一:注解方式
@WebFilter( urlPatterns={"/some"}, dispatcherTypes={ DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.REQUEST, DispatcherType.ERROR, DispatcherType.ASYNC } )
方式二:web.xml方式
<filter-mapping> <filter-name>SomeFilter</filter-name> <servlet-name>*.do</servlet-name> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>ERROR</dispatcher> <dispatcher>ASYNC</dispatcher> </filter-mapping>
如果不设置任何dispatcherTypes,默认为REQUEST。
FORWARD是指通过RequestDispatcher的forward()而来的请求可以套用过滤器。
INCLUDE是指通过RequestDispatcher的include()而来的请求可以套用过滤器。
ERROR是指由容器处理例外而转发过来的请求可以触发过滤器。
ASYNC是指异步处理的请求可以触发过滤器。
过滤器执行顺序
如果有某个URL或Servlet会应用多个过滤器,则根据<filter-mapping>在web.xml中出现的先后顺序,来决定过滤器的运行顺序。
性能过滤器Demo
package com.test; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter(filterName = "testFilter") public class TestFilter implements Filter { private FilterConfig filterConfig; public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { long begin = System.currentTimeMillis(); chain.doFilter(request, response); System.err.println("Request process in " + (System.currentTimeMillis() - begin) + " milliseconds"); } public void destroy() { } }
package com.test; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(urlPatterns = "/", name = "myServlet") public class MyServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) { doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }
<?xml version="1.1" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>Archetype Created Web Application</display-name> <filter-mapping> <filter-name>testFilter</filter-name> <servlet-name>myServlet</servlet-name> </filter-mapping> </web-app>
测试方法:
1、浏览器访问
2、查看控制台会输出字符串——“Request process in 3000 milliseconds”
API简介
在doFilter()方法中进行service()方法的前置处理,而后决定是否调用FilterChain的doFilter()方法。如果调用了FilterChain的doFilter()方法,就会运行下一个过滤器,如果没有下一个过滤器了,就调用请求目标Servlet的service()方法。如果因为某个情况(如用户没有通过验证)而没有调用FilterChain的doFilter(),则请求就不会继续交给接下来的过滤器或目标Servlet,这时就是所谓的拦截请求。
FilterChain的doFilter()实现,概念上类似以下代码:
Filter filter = filterIterator.next(); if (filter != null) { filter.doFilter(request, response, this); } else { targetServlet.service(request, response); }
在陆续调用完Filter实例的doFilter()仍至Servlet的service()之后,流程会以堆栈顺序返回,所以在FilterChain的doFilter()运行完毕后,就可以针对service()方法做后续处理。
Servlet 4.0
实现Filter接口是定义Filter的方式,然而,在Servlet4.0中,新增了GenericFilter类,目的类似于 GenericServlet,GenericFilter 將 FilterConfig 的設定、Filter 初始參數的取得做了封裝,并且在有参的init()方法里最后调用了一个无参的init()方法。
同时也新增了 HttpFilter,繼承自 GenericFilter。
因此,在 Servlet 4.0 中,若要定義 Filter,可以繼承 HttpFilter。
@WebFilter("/*")
public class TestFilter extends HttpFilter {
@Override
protected void doFilter(
HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
long begin = System.currentTimeMillis();
chain.doFilter(request, response);
getServletContext().log("Request process in " + (System.currentTimeMillis() - begin) + " milliseconds");
}
}