Filter 可认为是 Servlet 的一种加强版,它主要用于对用户请求进行预处理,也可以对 HttpServletResponse 进行后处理,是个典型的处理链。
Filter 也可对用户请求生成响应,这一点与 Servlet 相同,但实际上很少会使用 Filter 向用户请求生成响应。使用 Filter 完整的流程是:Filter 对用户请求进行预处理,接着将请求交给 Servlet 进行处理并生成响应,最后 Filter 再对服务器响应进行后处理。
Filter 有如下几个用处:
1、在 HttpServletRequest 到达 Servlet 之前,拦截用户的 HttpServletRequest
2、根据需要检查 HttpServletRequest,也可以修改 HttpServletRequest 头和数据
3、在 HttpServletResponse 到达客户端之前,拦截 HttpServletResponse
4、根据需要检查 HttpServletResponse,也可以修改 HttpServletResponse 头和数据
Filter 主要有如下几个种类:
1、用户授权的 Filter:Filter 负责检查用户请求,根据请求过滤用户非法请求。
2、日志 Filter:详细记录某些特殊的用户请求。
3、负责解码的 Filter:包括对非标准编码的请求解码。
4、能改变 XML 内容的 XSLT Filter 等。
5、Filter 可负责拦截多个请求或响应:一个请求或响应也可被多个 Filter 拦截。
创建一个 Filter 只需两个步骤:
1、创建 Filter 处理类
2、web.xml 中配置 Filter
创建 Filter 类
创建 Filter 必须实现 javax.servlet.Filter 接口,在该接口中定义了如下三个方法:
1、void init(FilterConfig config):用于完成 Filter 的初始化
2、void destroy():用于 Filter 销毁前,完成某些资源的回收
3、void doFilter(ServletRequest request, ServletResponse response, FilterChain chain):实现过滤功能,该方法就是对每个请求及响应增加的额外处理。
LogFilter.java
package com.baiguiren; import java.io.IOException; import javax.servlet.*; @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.gerRequestURI() + " 所花的时间为:" + (after - before)); } }
配置 Filter
配置 FIlter 与配置 Servlet 非常相似,都需要配置如下两个部分。
1、配置 Filter 名
2、配置 Filter 拦截 URL 模式
区别在于:Servlet 通常只配置一个 URL,而 Filter 可以同时拦截多个请求的 URL。因此,在配置 Filter 的 URL 模式时通常会使用模式字符串,使得 Filter 可以拦截多个请求。与配置 Servlet 相似的是,配置 Filter 同样有两种方式。
1、在 Filter 中通过注解进行配置
2、在 web.xml 文件中通过配置文件进行配置。
上面 Filter 类使用了 @WebFilter 配置该 Filter 的名字为 log,它会拦截向 /* 发送的所有的请求。
@WebFilter 属性:
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 --> <filter> <!-- Filter 的名字,相当于指定 @WebFilter 的 filterName 属性 --> <filter-name>log</filter-name> <!-- Filter 的实现类 --> <filter-class>com.baiguiren.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 里 doFilter() 方法里的代码就是从多个 Servlet 的 service() 方法里抽取的通用代码,通过使用 Filter 可以实现更好的代码复用。
假设系统包含多个 Servlet,这些 Servlet 都需要进行的一些通用处理:比如权限控制、记录日志等,这将导致在这些 Servlet 的 service 方法中有部分代码是相同的 -- 为了解决这种代码重复的问题,可以考虑把这些通用处理提取到 FIlter 中完成,这样各 Servlet 中剩下的只是特定请求相关的处理代码,而通用处理则交给 FIlter 完成。
Filter 与 Servlet 具有完全相同的生命周期。
下面将定义一个 Filter,该 Filter 对用户请求进行过滤,Filter 将通过 doFilter 方法来设置 request 编码的字符集,从而避免每个 JSP、Servlet 都需要设置;而且还会验证用户是否登录,没有登录则跳转到登录页:
AuthorityFilter.java
package com.baiguiren; import java.io.IOException; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.jsp.*; import javax.servlet.annotation.*; @WebFilter(filterName="authority", 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); } } }
上面的代码使用注解配置,也可以在 web.xml 中配置:
<filter> <filter-name>authority</filter-name> <filter-class>com.baiguiren.Authority</filter-class> <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-mapping> <filter-name>authority</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
上面的配置保证了普通用户只能访问 login.jsp 和 proLogin.jsp,其他页面都需要登录之后才能访问。
使用 URL Rewrite 实现网站伪静态
对于以 JSP 为表现层开发的动态网站来说,用户访问的 URL 通常有如下形式:
xxx.jsp?name=value...
大部分搜索引擎都会优先考虑收录静态的 HTML 页面,而不是这种动态的 *.jsp、*.php 页面。但实际上大部分网站都是动态的,不可能全部都是静态的 HTML 页面,因此大部分网站都会考虑使用伪静态 -- 就是将 *.jsp 这种动态 URL 伪装成静态的 HTML 页面。
对于 JavaWeb 应用来说,要实现这种伪静态非常简单:可以通过 Filter 拦截所有发向 *.html 的请求,然后按某种规则将请求 forward 到实际的 *.jsp 页面即可。
实现伪静态步骤:
1、到 http://www.tuckey.org/urlrewrite/ 下载 URL Rewrite 的最新版本
2、把上一步下载的 jar 包放在 WEB-INF/lib 目录下
3、在 web.xml 文件中配置启用 URLRewrite Filter,在 web.xml 中增加如下配置片段
4、在应用的 WEB-INF 路径下增加 urlrewrite.xml 文件,该文件定义了伪静态映射规则,这份伪静态规则是基于正则表达式的
urlwrite.xml
<?xml version="1.0" encoding="UTF-8"?> <urlrewrite> <rule> <!-- 所有配置如下正则表达式的请求 --> <from>/userinf-(\w*).html</from> <!-- 将被 forward 到如下 JSP 页面,其中 $1 代表上面第一个正则表达式所匹配的字符串 --> <to type="forward">/userinf.jsp?username=$1</to> </rule> </urlrewrite>
上面的规则文件中只定义了一个简单的规则:所有发向 /userinf-(\w*).html 的请求都被 forward 到 userinf.jsp 页面,并将 (\w*) 正则表达式所匹配的内容作为 username 参数值。
<%@ page contentType="text/html; charset=UTF-8" %> <% // 获取请求参数 String user = request.getParameter("username"); %> <html> <head> <title><%=user%>的个人信息</title> </head> <body> <% out.println("现在时间是: " + new java.util.Date() + "<br/>"); out.println("用户名: " + user); %> </body> </html>