Java Web高级编程(三)
使用过滤器改进应用程序
一、过滤器的目的
过滤器是可以拦截访问资源的请求、资源的响应或者同时拦截两者的应用组件。过滤器可以检测和修改请求和响应,同时也可以拒绝、重定向或转发请求。javax.servlet.Filter接口实现了过滤器技术,使用HttpServletRequest和HttpServletResponse。过滤器可以在部署描述符中以以编程的方式声明,它们可以有初始化参数并且可以访问ServletContext。
日志过滤器
在应用程序开发中,需要记录所有应用程序的请求和每个请求的结果(状态码,长度等其它信息)。通常Web容器提供了请求日志的机制,但如果需要在请求日志中显示出一些特有的信息,可以使用过滤记录请求。
验证过滤器
如果需要确保只有授权用户才可以访问应用程序,通常可以检查每个请求的信息,保证用户已登录,过滤器可以通过将验证和授权操作集中到一个位置的方式使工作变得简单。
压缩和加密过滤器
存在着网络带宽有限而CPU资源充足的情况,通常在数据传输之前对数据进行压缩。过滤器可以在收到请求时,请求保持不变,但在响应返回给用户时,使用过滤器可以压缩相应对象。
错误处理过滤器
对于Web用用程序而言,出现错误,是一个HTTP响应代码500,一般还会伴随着一个普通的HTML页面,写着“Internal Server Error”以及一些诊断信息。对于在本地运行的应用程序对开发者是有用的,但是对于远程的应用程序来说是不必要的。需要通过过滤器给用户显示出更加友好的和通用的错误处理页面,并记录必要的错误信息。
二、创建、声明和映射过滤器
创建过滤器就是实现Filter接口一样,过滤器在初始化的时候将调用init方法,他可以访问过滤器的配置初始化参数和ServletContext。当请求进入到过滤器中,doFilter方法将会被调用,它提供了对ServletRequest、ServletResponse和FilterChain对象的访问。在doFilter中,可以拒绝请求或者调用FilterChain对象的doFilter方法,可以封装请求和响应对象。
过滤器链
尽管只有一个Servlet可以处理请求,但是可以使用许多的过滤拦截请求。在过滤器链中每一个过滤器接受进入的请求并将它传递到下一个过滤链中,直到所有匹配的过滤器都处理完成,最终再将它传入Servlet中。调用FilterChain.doFilter()将触发过滤器链的持续执行。如果当前的过滤器没有调用FilterChain.doFilter(),将把控制权返回值Servlet容器中。
映射到URL模式和Servlet名称
同Servlet一样,过滤器可以被映射到URL模式,这会决定哪个或哪些过滤器将拦截某个请求。任何匹配某个过滤器的URL模式的请求在被匹配的Servlet处理之前将首先进入该过滤器,通过使用URL模式,不仅可以拦截Servlet请求,还可以拦截其它资源。
但是当现在已经有多个URL已经映射到Servlet上,并且希望某些过滤器映射到这些URL上。与映射到URL上相反,可以将这些过滤器映射到一个或多个Servlet名称上。
1.在部署描述符中使用<filter>和<filter-mapping>元素:
<filter> <filter-name>filterA</filter-name> <filter-class>com.wrox.AnyRequestFilter</filter-class> </filter> <filter-mapping> <filter-name>filterA</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
在声明了过滤器之后,可以将它映射到任意数目的URL或Servlet名称。当然过滤器URL映射还可以包含通配符。
2.使用注解声明和映射过滤器
@WebFilter{ filterName = "myFilter", urlPatterns = {"/foo","/bar/*"}, serVletNames = {"myServlet"}, dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.ASYNC} }
但是不可以对对过滤器链上的过滤器进行排序
三、过滤器排序
过滤器的顺序决定了过滤器在过滤器链中出现的位置,这里将会使用部署描述符来进行配置,因为注解无法进行排序配置。
URL模式映射和Servlet名称映射,匹配请求的过滤器将按照它们出现在部署描述符或者编程式配置中的顺序添加到过滤器链中,但是需要注意URL映射的过滤器优先级比Servlet名称映射到的过滤器高,由URL模式匹配的过滤器总是出现在有Servlet名称匹配的过滤器之前。
<filter> <filter-name>filterA</filter-name> <filter-class>com.wrox.FilterA</filter-class> </filter> <filter-mapping> <filter-name>filterA</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>filterB</filter-name> <filter-class>com.wrox.FilterB</filter-class> </filter> <filter-mapping> <filter-name>filterB</filter-name> <url-pattern>/servletTwo</url-pattern> <url-pattern>/servletThree</url-pattern> </filter-mapping> <filter> <filter-name>filterC</filter-name> <filter-class>com.wrox.FilterC</filter-class> </filter> <filter-mapping> <filter-name>filterC</filter-name> <url-pattern>/servletTwo</url-pattern> </filter-mapping>
这是一个filter的实例:
这是处理中的第一个过滤器,它将记录处理请求的时间,并记录所有访问应用程序的请求信息——IP地址、时间戳、请求方法等信息,finally块中是日志的操作。
public class RequestLogFilter implements Filter
{
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException
{
Instant time = Instant.now();
StopWatch timer = new StopWatch();
try
{
timer.start();
chain.doFilter(request, response);
}
finally
{
timer.stop();
HttpServletRequest in = (HttpServletRequest)request;
HttpServletResponse out = (HttpServletResponse)response;
String length = out.getHeader("Content-Length");
if(length == null || length.length() == 0)
length = "-";
System.out.println(in.getRemoteAddr() + " - - [" + time + "]" +
" \"" + in.getMethod() + " " + in.getRequestURI() + " " +
in.getProtocol() + "\" " + out.getStatus() + " " + length +
" " + timer);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException { }
@Override
public void destroy() { }
}