资源描述
第1章 过滤器
在前面的章节分别学习了Servlet技术JSP技术,从本章开始我们将学习一些在Web应用程序中扮演特殊角色的技术、以及一些使用的功能。
在Web应用程序中经常需要处理或控制一些客户端请求以及响应。比如:请求信息编码处理、响应信息编码处理、权限验证、防盗链等。这些使用普通的JSP和Servlet实现很繁琐,因为需要在每个JSP和Servlet中编写一段相同的代码,不利于系统的维护。使用本章讲解的过滤器就可以解决这类问题。
1.1 过滤器介绍
Servlet过滤器是J2EE Web应用程序级的Java代码组件,是一种类似于Servlet,由容器管理的对象,它能够以声明的方式插人到HTTP请求响应的过程中。它们拦截请求和响应,以便查看、提取或以某种方式操作正在客户机和服务器之间交换的数据。它是通常封装了一些功能的类,实现了对Web应用程序中的预处理和后期处理逻辑。
过滤器提供一种面向对象的模块化机制,用以将公共任务封装到可插入的组件中。这些组件通过Web部署描述符(web.xml)配置文件来声明,可以方便地添加和删除过滤器,无需改动任何应用程序代码或JSP页面,并由Servlet容器进行动态调用。通过在请求/响应链中使用过滤器,可以对应用程序(而不是以任何方式替代)的Servlet或JSP页面提供的核心处理进行补充,而不会破坏Servlet或JSP页面的功能。由于是纯Java实现,所以Servlet过滤器是跨平台和跨容器可移植的,从而进一步支持了Servler过滤器的模块化和可重用性,使得它们很容易地被部署到任何相容的J2EE环境中。
图1-1 过滤器原理
如图1-1所示,当客户端发出Web资源的请求时,Web服务器根据应用程序配置文件设置的过滤规则进行检查,客户请求满足过滤规则,则对客户请求/响应进行拦截,对请求头和请求数据进行检查或改动,并依次通过过滤器链,最后把请求/响应交给请求的Web资源处理。请求信息在过滤器链中可以被修改,也可以根据条件让请求不发往资源处理器,并直接向客户机发回一个响应。
当资源处理器完成了对资源的处理后,响应信息将逐级逆向返回。同样在这个过程中,用户可以修改响应信息,从而完成一定的任务。如图1-1所示:在Servlet2.4规范中对2.3规范进行了扩展,使得Servlet过滤器可以应用在客户机和Servlet之间、Servlet和Servlet、Servlet和JSP页面之间、以及各个JSP页面之间。
1.2 过滤器API
Servlet过滤器API包含了3个接口,它们都在javax.servlet包中,分别是Filter接口、FilterChain接口和FilterConfig接口。
1、Filter接口
所有的过滤器都必须实现Filter接口。Filter接口定义了过滤器的相关规范,主要定义了init()、doFilter()、destory()三个方法,其源代码如下:
package javax.servlet;
import java.io.IOException;
public interface Filter{
public abstract void init(FilterConfig filterconfig) throws ServletException;
public abstract void doFilter(ServletRequest servletrequest, ServletResponse servletresponse, FilterChain filterchain) throws IOException, ServletException;
public abstract void destroy();
}
l init():当开始使用servlet过滤器服务时,Web容器调用此方法一次,为服务准备过滤器;然后在需要使用过滤器的时候调用doFilter(),传送给此方法的FilterConfig对象,包含servlet过滤器的初始化参数。
l doFilter():每个过滤器都接受当前的请求和响应,而FilterChain包含的过滤器则仍然必须被处理。doFilter()方法中,过滤器可以对请求和响应做它想做的一切,通过调用他们的方法收集数据,或者给对象添加新的行为。过滤器通过传送至此方法的FilterChain参数,调用chain.doFilter()将控制权传送给下一个过滤器。当这个调用返回后,过滤器可以在它的doFilter()方法的最后对响应做些其他的工作。如果过滤器想要终止请求的处理或得到对响应的完全控制,则可以不调用下一个过滤器,而将其重定向至其它一些页面。当链中的最后一个过滤器调用chain.doFilter()方法时,将运行最初请求的Servlet。
l destroy():一旦doFilter()方法里的所有线程退出或已超时,容器调用此方法。服务器调用destory()以指出过滤器已结束服务,用于释放过滤器占用的资源。
2、FilterChain接口
接口Filter的方法doFilter()是由Servlet容器提供给开发者的,用于对资源请求过滤链的依次调用,通过FilterChain调用过滤链中的下一个过滤器,如果是最后一个过滤器,则下一个就调用目标资源。
3、FilterConfig接口
检索过滤器名、初始化参数以及活动的Servlet上下文。该接口提供了以下四个方法:
l getFilterName()返回web.xml部署文件中定义的该过滤器的名称。
l getServletContext()返回调用者所处的servlet上下文。
l getInitParameter(String)返回过滤器初始化参数值的字符串形式,当参数不存在时,返回null.name初始化参数名。
l getInitParameterNames()以Enumeration形式返回过滤器所有初始化参数值,如果没有初始化参数,返回为空。
在编写Filter实现类是,并不用关心FilterChain、FilterConfig的实现类,因为在Filter实现类中,这两个接口的引用是由服务器创建,如Tomcat服务器。
1.3 过滤器的使用
过滤器的使用分为两步:一是创建过滤器类,实现功能的封装;二是在web.xml中配置过滤器的过滤范围,使之生效。如使用过滤器来处理如例1-1所示的Servlet信息。
例1-1:DisplayServlet
package com.create.javaWeb.ch10;
public class DisplayServlet extends HttpServlet {
/**
* get请求处理方法
*/
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("Java WEB编程!");
out.flush();
out.close();
}
}
由于没有进行中文处理,响应的中文内容将以乱码的形式显示在页面,如图1-2所示。
图1-2 未处理编码的servlet
1.3.1 创建过滤器
创建过滤器很简单,只要实现Filter接口、并实现相应的方法即可。如常用的过滤器之一就是请求、响应对象的编码处理,过滤器实现代码如例1-2所示。
例1-2:中文处理过滤器
package com.create.javaWeb.ch10;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class EncodingFilter implements Filter {
/**
* 初始化方法
*/
public void init(FilterConfig arg0) throws ServletException {
}
/**
* 过滤方法
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain ft) throws IOException, ServletException {
//设置请求对象、响应对象编码格式
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
//将请求发送给目标资源
ft.doFilter(request, response);
}
/**
* 销毁方法
*/
public void destroy() {
}
}
在过滤器中doFilter()方法是过滤的主方法,在该方法中如果想让请求通过过滤器访问目标,就必须调用FilterChain对象的foFilter()方法,否则请求将被阻止。
1.3.2 过滤器配置
创建的过滤器只是一个普通的实现了Filter接口的java类,想让该类实现过滤功能,就必须在web.xml中配置,让其发挥作用。在web.xml中使用filter、filter-mapping以及其子标签实现过滤器的配置,比如配置中文处理过滤器的代码如下:
<web-app>
<!-- ...... -->
<!-- ch10 -->
<filter>
<filter-name>encoding</filter-name>
<filter-class>com.create.javaWeb.ch1.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
在web.xml配置文件中:
l filter标签:用于配置一个过滤器。通过子标签filter-name为过滤器取名,子标签filter-class指定过滤器实现类的包路径即类名。
l filter-mapping标签:用于指定某个过滤的过滤范围。通过子标签filter-name指定配置的过滤器名,和filter标签中配置的名称相对应;子标签url-pattern指定过滤范围,当要过滤某个目录下所有的请求时使用“*”代替、或者单独配置某一个确切的路径,如:
<url-pattern>/servlet/DisplayServlet</url-pattern>
但是不能使用后缀名匹配的方式、如“/*.jsp”就是错误的。在上面的配置中“/*”代表该过滤器对当前web应用程序的所有请求都起作用。一个filter可以配置多个filter-mapping。
重新启动服务器,过滤器将过滤响应的请求。此时访问DisplayServlet将看到如图1-3所示的效果。
图1-3 过滤器处理中文效果
1.3.3 过滤器参数
过滤器想Servlet一样可以配置一些初始化参数,这些参数可以灵活控制过滤器的运行。过滤器的初始化参数可以通过web.xml中的init-param标签配置。如例1-3通过init-param在web.xml中配置编码过滤器的初始化参数。
例1-3:过滤器初始化参数
<web-app>
<!-- ...... -->
<!-- ch10 -->
<filter>
<filter-name>encoding</filter-name>
<filter-class>com.create.javaWeb.ch1.EncodingFilter</filter-class>
<init-param>
<param-name>encodingType</param-name>
<param-value>GBK</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
在过滤器实现类EncodingFilter中修改代码如下:
public class EncodingFilter implements Filter {
private String encoding = "UTF-8";
/**
* 初始化方法
*/
public void init(FilterConfig conf) throws ServletException {
//通过名称得到web.xml中配置的初始化参数
this.encoding = conf.getInitParameter("encodingType");
}
/**
* 过滤方法
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filter) throws IOException, ServletException {
//设置请求对象、响应对象编码格式
request.setCharacterEncoding(this.encoding);
response.setCharacterEncoding(this.encoding);
//将请求发送给目标资源
filter.doFilter(request, response);
}
/**
* 销毁方法
*/
public void destroy() {
}
}
在初始化方法中,通过FilterConfig得到初始化参数,名称必须和web.xml中的参数名相对应。重启服务器,访问DisplayServlet内容如图1-4所示。
图1-4 过滤器参数设置编码
1.4 过滤器生命周期
过滤器的生命周期和Servlet的生命周期基本一致,主要包含:类加载、实例化、初始化、过滤、销毁。其中类加载、实例化、初始化工作是在服务器启动时就执行、过滤是在每次访问时执行、销毁时在服务器停止时执行。如例1-4所示,在每个生命周期方法中打印相应的提示信息,用于观察过滤器的生命周期。
例1-4:过滤器生命周期
package com.create.javaWeb.ch10;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class LifeFilter implements Filter {
public LifeFilter(){
System.out.println("------实例化------");
}
public void destroy() {
System.out.println("------销 毁------");
}
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {
System.out.println("------过 滤------");
arg2.doFilter(arg0, arg1);
}
public void init(FilterConfig arg0) throws ServletException {
System.out.println("------初始化------");
}
}
在web.xml中配置过滤器的有效范如下:
<filter>
<filter-name>life</filter-name>
<filter-class>com.create.javaWeb.ch1.LifeFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>life</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
通过配置可以看出,LifeFilter过滤器对该web应用程序下的所有请求都起作用。当启动web应用程序是可以看到如图1-5所示的LifeFilter启动信息。
图1-5 启动服务
当访问服务器上的每一个资源时都将看到如图1-6所示的LifeFilter过滤信息。
图1-6 访问web资源
当服务器停止时将执行销毁方法。
1.5 过滤器链
当多个过滤器对同一资源路径有效时就形成了过滤器链,也就是说在同一个Web应用程序中有多个过滤器。过滤器链的原理就像现实生活中一道一道的门,比如在卧室有你所想要的东西,你必须点通过房屋的大门进入客厅,然后再经过卧室的门进入卧室,取得你所需要的东西,然后走出卧室、再走出房屋。这里的房屋就相当于web应用程序、卧室存放的物品就是web应用程序中的资源、门就相当于过滤器(大门是第一个过滤器、卧室的门是第二个过滤器)。从这个例子我们可以看出在多个过滤器作用于同一个web资源是,过滤器的过滤顺序是先进后出的原则。其原理如图1-7所示。
图1-7 过滤器链
例1-5:过滤器链
本例通过在每个过滤器中打印相应的提示信息来观察过滤器链的执行过程。创建过滤器OneFilter、TwoFilter、test.jsp即配置内容非别如下:
OneFilter
package com.create.javaWeb.ch10;
//导包
public class OneFilter implements Filter {
public void destroy() {}
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {
System.out.println("------OneFilter begin------");
arg2.doFilter(arg0, arg1);
System.out.println("------OneFilter end------");
}
public void init(FilterConfig arg0) throws ServletException {}
}
TwoFilter
package com.create.javaWeb.ch10;
//导包
public class TwoFilter implements Filter {
public void destroy() {}
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {
System.out.println("------TwoFilter begin------");
arg2.doFilter(arg0, arg1);
System.out.println("------TwoFilter end------");
}
public void init(FilterConfig arg0) throws ServletException {}
}
web.xml配置
<filter>
<filter-name>one</filter-name>
<filter-class>com.create.javaWeb.ch1.OneFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>one</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>two</filter-name>
<filter-class>com.create.javaWeb.ch1.TwoFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>two</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
test.jsp
<%@ page language="java" pageEncoding="UTF-8"%>
<html>
<head>
<title>test</title>
</head>
<%
System.out.println("------test.jsp 资源------");
%>
<body>
This is my JSP page. <br>
</body>
</html>
当访问web应用程序资源test.jsp时将看到如图1-8所示的过滤器链执行流程。
图1-8 过滤器链
由图1-8可以看出过滤器链中的过滤器执行是先进后出的原则,通过修改web.xml中过滤器的配置顺序可以改变过滤器链中每个过滤器的先后顺序,web.xml中配置在前的过滤器将先执行、配置在后的过滤器将后执行。
- 12 -
展开阅读全文