Filter 可认为是 Servlet 的一种“加强版”,它主要用于对用户请求进行预处理,也可以对 HttpServletResponse 进行后处理,是个典型的处理链。Filter 也可对用户请求生成响应,这一点与 Servlet 相同,但实际上很少会使用 Filter 向用户请求生成响应。使用 Filter 完整的流程是:Filter 对用户请求进行预处理,接着将请求交给 Servlet 进行处理并生成响应,最后 Filter 再对服务器响应进行后处理。
Filter 有如下几个用处。
➢ 在 HttpServletRequest 到达 Servlet 之前,拦截客户的 HttpServletRequest。
➢ 根据需要检查 HttpServletRequest,也可以修改 HttpServletRequest 头和数据。
➢ 在 HttpServletResponse 到达客户端之前,拦截 HttpServletResponse。
➢ 根据需要检查 HttpServletResponse,也可以修改 HttpServletResponse 头和数据。
Filter 有如下几个种类。
➢ 用户授权的 Filter:Filter 负责检查用户请求,根据请求过滤用户非法请求。
➢ 日志 Filter:详细记录某些特殊的用户请求。
➢ 负责解码的 Filter:包括对非标准编码的请求解码。
➢ 能改变 XML 内容的 XSLT Filter 等。
➢ Filter 可负责拦截多个请求或响应;一个请求或响应也可被多个 Filter 拦截。
创建一个 Filter 只需两个步骤。
① 创建 Filter 处理类。
② web.xml 文件中配置 Filter。
创建 Filter 类
创建 Filter 必须安现 javax.serviet.Filer 接口,在该接口中定义了如下三个方法。
➢ void init(FilterConfig config):用于完成 Filter 的初始化。
➢ void destroy():用于 Filter 销毁前,完成某些资源的回收。
➢ void doFilter(ServletRequcst recucst,ServletResponse response,FilterChain chain):实现过滤功能,该方法就是对每个请求及响应增加的额外处理。
下面介绍一个日志 Filter,这个 Filer 负责拦截所有的用户请求,并将请求的信息记录在日志中。
@WebFilter(filterName="log",urlPatterns={"/*"})
public class LogFilter implements Filter{
// FilterConfig 可用于访问 Filter 的配置信息
private FilterConfig config;
// 实现初始化方法
public void init(FilterConfig config){
this.config = config;
}
// 实现销毁方法
public void destroy(){
this.config = null;
}
// 执行过滤的核心方法
public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)
throws IOException,ServletException{
// ---------下面代码用于对用户请求执行预处理--------
// 获取 Servletcontext 对象,用于记录日志
ServletContext context = this.config.getServletContext();
long before = System.currentTimeMillis();
System.out.println("开始过滤...");
// 将请求转换成 HttpServletRequest 请求
HttpServletRequest hrequest = (HttpServletRequest)request;
// 输出提示信息
System.out.println("Filter 已经截获到用户的请求的地址:" + hrequest.getServletPath());
// Filter 只是链式处理,请求依然放行到目的地址
chain.doFilter(request,response);
// ---------下面代码用于对服务器响应执行后处理---------
long after = System.currentTimeMillis();
// 输出提示信息
System.out.println("过滤结束");
//输出提示信息
System.out.println("请求被定位到" + hrequest.getRequestURI() + " 所花的时间为:" + (after - before));
}
}
上面的程序中第一行代码实现了 doFilter()方法,实现该方法就可实现对用户请求进行预处理,也可实现对服务器响应进行后处理——它们的分界线为是否调用了 chain.doFilter(),执行该方法之前,即对用户请求进行预处理;执行该方法之后,即对服务器响应进行后处理。
在上面的请求 Filter 中,仅在日志中记录请求的 URL,对所有的请求都执行 chain.doFilter(request,reponse)方法,当 Filter 对请求过滤后,依然将请求发送到目的地址。如果需要检查权限,可以在 Filter 中根据用户请求的 HttpSession,判断用户权限是否足够。如果权限不够,直接调用重定向即可,无需调用 chain.doFilter(request,reponse) 方法。
配置 Filter
前面已经提到,Filter 可以认为是 Servlet 的“增强版”,因此配置 Filter 与配置 Servlet 非常相似,都需要配置如下两个部分。
➢ 配置 Filter 名。
➢ 配置 Filter 拦截 URL 模式。
区别在于:Servlet 通常只配置一个 URL,而 Filter 可以同时拦截多个请求的 URL。因此,在配置 Filter 的 URL 模式时通常会使用模式字符串,使得 Filter 可以拦截多个请求。与配置 Servlet 相似的是,配置 Filter 同样有两种方式。
➢ 在 Filter 类中通过注解进行配置。
➢ 在 web.xml 文件中通过配置文件进行配置。
上面 Filter 类的代码使用@WebFilter 配置该 Filter 的名字为 log,它会拦截向/*发送的所有的请求。
@WebFilter 修饰一个 Filter 类,用于对 Filter 进行配置,它支持如下表所示的常用属性。
属性 | 是否必需 | 说明 |
---|---|---|
asyncSupported | 否 | 指定该 Filter 是否支持异步操作模式。 |
dispatcherTypes | 否 | 指定该 Filter 仅对那种 dispatcher 模式的请求进行过滤。该属性支持 ASYNC、ERROR、FORWARD、INCLUDE、REQUEST 这5个值的任意组合。默认值为同时过滤 5 种模式的请求 |
displayName | 否 | 指定该 Filter 的显示名 |
filterName | 否 | 指定该 Filter 的名称 |
initParams | 否 | 用于为该 Filter 配置参数 |
servletNames | 否 | 该属性值可指定多个 Servlet 的名称,用于指定该 Filter 仅对这几个 Servlet 执行过滤 |
urlPatterns/value | 否 | 这两个属性的作用完全相同。都指定该 Filter 所拦截的 URL |
在 web.xml 文件中配置 Filter 与配置 Servlet 非常相似,需要为 Filter 指定它所过滤的 URL,并且也可以为 Filter 配置参数。
在 web.xml 文件中为该 Filter 增加如下配置片段:
<!-- 定义 Filter -->
<filter>
<!-- Filter 的名字,相当于指定@WebFilter 的 filterName 属性 -->
<filter-name>log</filter-name>
<!-- Filter 的实现类 -->
<filter-class>lee.LogFilter</filter-class>
</filter>
<!-- 定义Filter 拦截的 URL 地址-->
<filter-mapping>
<!-- Filter 的名字 -->
<filter-name>log</filter-name>
<!-- FIlter 负责拦截的 URL,相当于指定@WebFilter 的 urlPatterns 属性 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
上面的代码用于配置该 Filter,从这些代码中可以看出配置 Filter 与配置 Servlet 非常相似,只是配置 Filter 时指定 url-pattern 为/*,即表示该 Filter 会拦截所有用户请求。该 Filter 并未对客户端请求进行额外的处理,仅仅在日志中简要记录请求的信息。
为该 Web 应用提供任意一个 JSP 页面,并通过浏览器来访问该 JSP 页面,即可在 Tomcat 的控制台看到如下图所示信息。
实际上 Filer 和 Serviet 极其相似,区别只是 Fiter 的 doFilter()方法里多了一个 FilterChain 的参数,通过该参数可以控制是否放行用户请求。在实际项目中,Filter 里 doFilter()方法里的代码就是从多个 Servlet 的 service()方法里抽取的通用代码,通过使用 Filter 可以实现更好的代码复用。
假设系统有包含多个 Servlet,这些 Servlet 都需要进行一些的通用处理:比如权限控制、记录日志等,这将导致在这些 Servlet 的 sevice 方法中有部分代码是相同的——为了解决这种代码重复的问题,可以考虑把这些通用处理提取到 Filter 中完成,这样各 Servlet 中剩下的只是特定请求相关的处理代码,而通用处理则交给 Filter 完成。下图显示了 Filter 的用途。
由于 Filter 和 Servlet 如此相似,所以 Filter 和 Servlet 具有完全相同的生命周期行为,且 Filter 也可以通过<init-param.../>元素或@WebFilter 的 initParams 属性来配置初始化参数,获取 Filter 的初始化参数则使用 FilterConfig 的 getInitParameter() 方法。
下面将定义一个较为实用的 Filter,该 Filter 对用户请求进行过滤,Filter 将通过 doFilter 方法来设置 request 编码的字符集,从而避免每个 JSP、Servlet 都需要设置;而且还会验证用户是否登录,如果用户没有登录,系统直接跳转到登录页面。
下面是该 Filter 的源代码。
@WebFilter(filterName="athority",urlPatterns={"/*"},initParams={
@WebInitParam(name="encoding",value="UTF-8"),
@WebInitParam(name="loginPage",value="/login.jsp"),
@WebInitParam(name="proLogin",value="/proLogin.jsp"),
})
public class AuthorityFilter implements Filter{
// FilterConfig 可用于访问 Filter 的配置信息
private FilterConfig config;
// 实现初始化方法
public void init(FilterConfig config){
this.config = config;
}
// 实现销毁方法
public void destroy(){
this.config = null;
}
// 执行过滤的核心方法
public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)
throws IOException,ServletException{
// 获取该 Filter 的配置参数
String encoding = config.getInitParameter("encoding");
String loginPage = config.getInitParameter("loginPage");
String proLogin = config.getInitParameter("proLogin");
// 设置 request 编码用的字符集
request.setCharacterEncoding(encoding); // ①
HttpServletRequest requ = (HttpServletRequest)request;
HttpSession session = requ.getSession(true);
// 获取客户请求的页面
String requestPath = requ.getServletPath();
// 如果 session 范围的 user 为 null,即表明没有登录
// 且用户请求的既不是登录页面,也不是处理登录的页面
if(session.getAttribute("user") == null
&& !requestPath.endsWith(loginPage)
&& !requestPath.endsWith(proLogin)){
// forward到登录页面
request.setAttribute("tip","您还没有登录");
request.getRequestDispatcher(loginPage).forward(request,response);
}
// "放行"请求
else{
chain.doFilter(request,response);
}
}
}
上面 Filter 的 doFilter 方法开始的前三行代码用于获取 Filter 的配置参数,而程序中的粗体字代码则是此 Filter 的核心,①号代码按配置参数设置了 request 编码所用的字符集,接下来的代码判断 session 范围内是否有 user 属性——没有该属性即认为没有登录,如果既没有登录,而且请求地址也不是登录页和处理登录页,系统直接跳转到登录页面。
通过@WebFilter 的 initParams 属性可以为该 Filter 配置初始化参数,它可以接受多个@WebInitParam,每个@WebInitParam 指定一个初始化参数。
在 web.xml 文件中也使用<init-param.../>元素为该 Filter 配置参数,与配置 Servlet 初始化参数完全相同。
如果需要在 web.xml 文件中配置该 Filter,该 Filter 的配置片段如下:
<!-- 定义 Filter -->
<filter>
<!-- Filter 的名字 -->
<filter-name>authority</filter-name>
<!-- Filter 的实现类 -->
<filter-class>lee.AuthorityFilter</filter-class>
<!-- 下面三个 init-param 元素配置了三个参数 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>loginPage</param-name>
<param-value>/login.jsp</param-value>
</init-param>
<init-param>
<param-name>proLogin</param-name>
<param-value>/proLogin.jsp</param-value>
</init-param>
</filter>
<!-- 定义 Filter 拦截的 URL 地址 -->
<filter-mapping>
<!-- Filter 的名字 -->
<filter-name>authority</filter-name>
<!-- Filter 负责拦截的 URL -->
<url-pattern>/*</url-pattern>
</filter-mapping>
上面的配置片段中为该 Filter 指定了三个配置参数,指定 loginPage 为/login.jsp,proLogin 为/proLogin.jsp,这表明,如果没有登录该应用,普通用户只能访问/login.jsp 和/proLogin.jsp 页面。只有当用户登录该应用后才可自由访问其他页面。
评论