转载一篇关于Servlet过滤器的详细讲解


个人小总结:

在Servlet规范2.3中定义了过滤器,它能够对Servlet容器的请求和响应对象进行检查和修改。

Servlet过滤器本身并不生成请求和响应对象,只是提供过滤功能。所以它不是Servlet

Servlet过滤器能够在Servlet被调用之前检查Request对象,并修改Request Header和Request内容;

Servlet被调用之后检查Response对象,修改Response Header和Response的内容。

Servlet过滤器可以过滤的Web组件包括Servlet,JSP和HTML等文件。

Filter类似于IO中的过滤流,实现也类似于Servlet。

Filter中的doFilter(ServletRequest request, ServletResponse response, FilterChain chain)中,ServletRequest和

ServletResponse一般要转成具体的Servlet实现这个对象,例如:转成HttpServletRequest和HttpServletResponse。



过滤器

Filter 技术是servlet 2.3 新增加的功能。servlet2.3是sun公司与2000年10月发布的,它的开发者包括许多个人和公司团体,充分体现了sun公司所倡导的代码开放性原则。由于众多的参与者的共同努力,servlet2.3比以往功能都强大了许多,而且性能也有了大幅提高。

Filter 技术使用户可以改变一个request和修改一个response。 Filter 不是一个servlet,它不能产生一个response,它能够在一个request到达servlet之前预处理request,也可以在离开servlet时处理response。换种说法,filter其实是一个”servlet chaining”(servlet 链)。

 

一个filter 包括:

  1。 在servlet被调用之前截获;

  2。 在servlet被调用之前检查servlet request;      如统一的编码转换

  3。 根据需要修改request头和request数据;         装饰、或者动态代理实现

  4。 根据需要修改response头和response数据;

5。 在servlet被调用之后截获。

  可以捕获servlet运行的结果,比如进行数据压缩等

以上特点可以通过简单案例演示,是学生对Filter有一个初步认识。

你能够配置一个filter 到一个或多个servlet;单个servlet或servlet组能够被多个filter 使用。几个实用的filter 包括:用户辨认filter,日志filter,审核filter,加密filter,符号filter,能改变xml内容的XSLT filter等。

一个filter必须实现javax。servlet。Filter接口定义的三个方法: doFilter、init和destroy。(在三个方法在后面后有详细的介绍)。

 

编写一个简单的过滤器对*.jsp文件过滤,演示

chain.doFilter(request, response)

Filter工作原理(执行流程)      

当客户端发出Web资源的请求时,Web服务器根据应用程序配置文件设置的过滤规则进行检查,若客户请求满足过滤规则,则对客户请求/响应进行拦截,对请求头和请求数据进行检查或改动,并依次通过过滤器链,最后把请求/响应交给请求的Web资源处理。请求信息在过滤器链中可以被修改,也可以根据条件让请求不发往资源处理器,并直接向客户机发回一个响应。当资源处理器完成了对资源的处理后,响应信息将逐级逆向返回。同样在这个过程中,用户可以修改响应信息,从而完成一定的任务。

    在这里,我要插几句——关于过滤链的问题:上面说了,当一个请求符合某个过滤器的过滤条件时该请求就会交给这个过滤器去处理。那么当两个过滤器同时过滤一个请求时谁先谁后呢?这就涉及到了过滤链FilterChain。

所有的奥秘都在Filter的FilterChain中。服务器会按照web.xml中过滤器定义的先后循序组装成一条链,然后一次执行其中的doFilter()方法。(注:这一点Filter和Servlet是不一样的,具体请参看我的另一篇文章:Servlet和Filter映射匹配原则之异同)执行的顺序就如下图所示,执行第一个过滤器的chain.doFilter()之前的代码,第二个过滤器的chain.doFilter()之前的代码,请求的资源,第二个过滤器的chain.doFilter()之后的代码,第一个过滤器的chain.doFilter()之后的代码,最后返回响应。

       
    这里还有一点想补充:大家有没有想过,上面说的“执行请求的资源”究竟是怎么执行的?对于“执行第一个过滤器的chain.doFilter()之前的代码,第二个过滤器的chain.doFilter()之前的代码”这些我可以理解,无非就是按顺序执行一句句的代码,但对于这个“执行请求的资源”我刚开始却是怎么也想不明白。直到我见到上面这张图片才恍然大悟。其实是这样的:

    通常我们所访问的资源是一个servlet或jsp页面,而jsp其实是一个被封装了的servlet,于是我们就可以统一地认为我们每次访问的都是一个Servlet,而每当我们访问一个servlet时,web容器都会调用该Servlet的service方法去处理请求。而在service方法又会根据请求方式的不同(Get/Post)去调用相应的doGet()或doPost()方法,实际处理请求的就是这个doGet或doPost方法。写过servlet的朋友都应该知道,我们在doGet(或doPost)方法中是通过response.getWriter()得到客户端的输出流对象,然后用此对象对客户进行响应。

到这里我们就应该理解了过滤器的执行流程了:

执行第一个过滤器的chain.doFilter()之前的代码

——>第二个过滤器的chain.doFilter()之前的代码

——>……

——>第n个过滤器的chain.doFilter()之前的代码

——>所请求servlet的service()方法中的代码

——>所请求servlet的doGet()或doPost()方法中的代码

——>第n个过滤器的chain.doFilter()之后的代码

——>……

——>第二个过滤器的chain.doFilter()之后的代码

——>第一个过滤器的chain.doFilter()之后的代码。

 

 

简单演示多个过滤器共同作用的过程

FilterDemo1

System.out.println("11111111 before");

chain.doFilter(request, response);

System.out.println("1111111111 after");

 

FilterDemo2

System.out.println("22222222 before");

chain.doFilter(request, response);

System.out.println("22222222 after");

 

Index.jsp

<% System.out.println("jsp xxxxxxxxxxxxxxx............"); %>

输出结果

11111111 before

222222222222 before

jsp xxxxxxxxxxxxxxx............

222222222222 after

1111111111 after

 

交换web.xml中两个过滤器的描述代码位置,重新运行,结果为:

222222222222 before

11111111 before

jsp xxxxxxxxxxxxxxx............

1111111111 after

222222222222 after

 

过滤器的生命周期

过滤器的生命周期:(一定要实现javax.servlet包的Filter接口的三个方法init()、doFilter()、destroy(),空实现也行) 
(1)、启动服务器时加载过滤器的实例,并调用init()方法来初始化实例; 
(2)、每一次请求时都只调用方法doFilter()进行处理; 
(3)、停止服务器时调用destroy()方法,销毁实例。

 

案例:在三个方法中编写打印语句,然后启动、关闭服务器演示生命周期。

 

Servlet过滤器API

Servlet过滤器API包含了3个接口,它们都在javax.servlet包中,分别是Filter接口、FilterChain接口和FilterConfig接口。

1  public Interface Filter
    所有的过滤器都必须实现Filter接口。该接口定义了init,doFilter0,destory()三个方法:
    (1) public void init (FilterConfig filterConfig) throws   ServletException.
    当开始使用servlet过滤器服务时,Web容器调用此方法一次,为服务准备过滤器;然后在需要使用过滤器的时候调用doFilter(),传送给此方法的FilterConfig对象,包含servlet过滤器的初始化参数。
    (2)public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws java.io.IOException,ServletException.

每个过滤器都接受当前的请求和响应,且FilterChain过滤器链中的过滤器(应该都是符合条件的)都会被执行。doFilter方 法中,过滤器可以对请求和响应做它想做的一切,通过调用他们的方法收集数据,或者给对象添加新的行为。过滤器通过传送至此方法的FilterChain参数,调用chain.doFilterO将控制权传送给下一个过滤器。当这个调用返回后,过滤器可以在它的 Filter方法的最后对响应做些其他的工作。如果过滤器想要终止请求的处理或得到对响应的完全控制,则可以不调用下一个过滤器,而将其重定向至其它一些页面。当链中的最后一个过滤器调用chain.doFilterO方法时,将运行最初请求的Servlet。

(3)public void destroy()
     一旦doFilterO方法里的所有线程退出或已超时,容器调用此方法。服务器调用destoryO以指出过滤器已结束服务,用于释放过滤器占用的资源。

 

2 public interface FilterChain

public void doFilter(ServletRequest request,ServletResponse response)

thlows java.io.IOException,ServletException

此方法是由Servlet容器提供给开发者的,用于对资源请求过滤链的依次调用,通过FilterChain调用过滤链中的下一个过滤   器,如果是最后一个过滤器,则下一个就调用目标资源。

3 public interface FilterConfig

     FilterConfig接口检索过滤器名、初始化参数以及活动的Servlet上下文。该接口提供了以下4个方法:

     (1)public java.1ang.String getFilterName0
           返回web.xml部署文件中定义的该过滤器的名称。
     (2)public ServletContext getServletContextO
          返回调用者所处的servlet上下文。
     (3)public java.1ang.String getlnitParameter(java.1ang.String name)
          返回过滤器初始化参数值的字符串形式,当参数不存在时,返回nul1.name是初始化参数名。
     (4)public java.util.Enumeration getlnitParameterNames()
          以Enumeration形式返回过滤器所有初始化参数值,如果没有初始化参数,返回为空。

 

过滤器相关接口工作流程

从编程的角度看,过滤器类将实现Filter接口,然后使用这个过滤器类中的FilterChain和FilterConfig接口。该过滤器类的

— 个引用将传递给FilterChain对象,以允许过滤器把控制权传递给链中的下一个资源。FilterConfig对象将由容器提供给过滤
器,以允许访问该过滤器的初始化数据。详细流程如下图所示:

 

过滤器配置

   过滤器通过Web应用程序中的配置描述符web.xml文件中的明,包括部分:

过滤器定义,由<filter>元素表示,主要包括<filter-name>和<filter-class>两个必须的子元素和<icon>、<init-param>,<display-name>,<description>这4个可选的子元素。<filter-name>子元素定义了—个过滤器的名字,<filter-class>指定了由容器载入的实际类,<init-param>子元素为过滤器提供初始化参数。

<filter-mapping> 主要由<filter-name>,<servlet-name>和<url-pattem>子元素组成。<servlet-name>将过滤器映射到一个或多个Servlet上,<url-pattem>将过滤器映射到—个或多个任意特征的URL的JSP页面。


过滤器使用案例

解决全站乱码问题

CharseterEncodingFilter1.java

private FilterConfig config = null;

private String defaultCharset = "utf-8";

 

public void doFilter(ServletRequest request, ServletResponse response,

           FilterChain chain) throws IOException, ServletException {

      

       HttpServletRequest req = (HttpServletRequest) request;

       HttpServletResponse resp = (HttpServletResponse) response;

      

       String charset = config.getInitParameter("charset");

       if(charset==null){

           charset = defaultCharset;

       }

       request.setCharacterEncoding(charset);

 

        //有时候response不用设置编码,因为servlet一般不做输出,

//输出交由jsp来做,所以只要jsp页面设定编码即可

       resp.setCharacterEncoding(charset);

       resp.setContentType("text/html;charset="+charset);

      

       chain.doFilter(req, resp);

 

}

 

public void init(FilterConfig filterConfig) throws ServletException {

       this.config = filterConfig;

}

Web.xml

    <filter>

       <filter-name>charseterEncodingFilter1</filter-name>

        <filter-class>cn.class3g.web.filter.CharseterEncodingFilter1</filter-class>

    </filter>

    <filter-mapping>

       <filter-name>charseterEncodingFilter1</filter-name>

       <url-pattern>/*</url-pattern>

    </filter-mapping>

 

Index.jsp

   <form action="/Filter_Test/servlet/TestCharsetServlet" method="post" >

   城市:<input type="text" name="city" value="保定"/> <br/>

   <input type="submit" value="提交" />

   </form>

TestCharsetServlet.java

public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {

    String city = request.getParameter("city");

    response.getWriter().write(city);

}

public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {

    String city = request.getParameter("city");

    response.getWriter().write(city);

}

 

l         运行测试结果,页面能够正常显示

l         将过滤器去掉,再次测试,乱码

l         将表单的method改为get再次测试,乱码。因为此种办法不能解决get方式的乱码问题

 

禁止缓存所有动态页面的过滤器

a)       有 3 个 HTTP 响应头字段都可以禁止浏览器缓存当前页面,它们在 Servlet 中的示例代码如下:

l         response.setDateHeader("Expires",-1);

l         response.setHeader("Cache-Control","no-cache"); 

l         response.setHeader("Pragma","no-cache"); 

b)       并不是所有的浏览器都能完全支持上面的三个响应头,因此最好是同时使用上面的三个响应头。

c)       Expires数据头:值为GMT时间值,为-1指浏览器不要缓存页面

d)       Cache-Control响应头有两个常用值:

l         no-cache指浏览器不要缓存当前页面。

l         max-age:xxx指浏览器缓存页面xxx秒

NoCacheFilter

    public void doFilter(ServletRequest arg0, ServletResponse arg1,

           FilterChain arg2) throws IOException, ServletException {

       HttpServletRequest request = (HttpServletRequest) arg0;

       HttpServletResponse response = (HttpServletResponse) arg1;

      

       response.setDateHeader("expires",-1);

       response.setHeader("Cache-Control","no-cache");

       response.setHeader("Pragma","no-cache");

      

       arg2.doFilter(request, response); 

    }

Web.xml

    <filter>

       <filter-name>NoCacheFilter</filter-name>

        <filter-class>cn.class3g.web.filter.NoCacheFilter</filter-class>

    </filter>

    <filter-mapping>

       <filter-name>NoCacheFilter</filter-name>

       <url-pattern>*.jsp</url-pattern>

    </filter-mapping>

    <filter-mapping>

       <filter-name>NoCacheFilter</filter-name>

       <url-pattern>/servlet/*</url-pattern>

    </filter-mapping>

可以设置多个filter-mapping来映射不同的文件类型

测试:

提交表单数据,返回,查看表单中的数据是否仍然存在

 

控制浏览器缓存页面中的静态资源的过滤器:

场景:有些动态页面中引用了一些图片或css文件以修饰页面效果,这些图片和css文件经常是不变化的,所以为减轻服务器的压力,可以使用filter控制浏览器缓存这些文件,以提升服务器的性能。

ExpiresFilter

private FilterConfig config;

public void doFilter(ServletRequest req, ServletResponse resp,

           FilterChain chain) throws IOException, ServletException {

      

       HttpServletRequest request = (HttpServletRequest) req;

       HttpServletResponse response = (HttpServletResponse) resp;

      

       //1.得到请求的是什么资源  css js  jpg

       String uri = request.getRequestURI();

       if(uri.endsWith(".css")){

           long expriesTime = Integer.parseInt(this.config.getInitParameter("css"))*1000;

           response.setDateHeader("expires", System.currentTimeMillis()+expriesTime);

       }else if(uri.endsWith(".js")){

           long expriesTime = Integer.parseInt(this.config.getInitParameter("js"))*1000;

           response.setDateHeader("expires", System.currentTimeMillis()+expriesTime);

       }else if(uri.endsWith(".jpg")){

           long expriesTime = Integer.parseInt(this.config.getInitParameter("jpg"))*1000;

           response.setDateHeader("expires", System.currentTimeMillis()+expriesTime);

       }

       chain.doFilter(request, response);

}

 

public void init(FilterConfig filterConfig) throws ServletException {

       this.config = filterConfig;

}

Web.xml

    <filter>

       <filter-name>ExpiresFilter</filter-name>

        <filter-class>cn.class3g.web.filter.ExpiresFilter</filter-class>

       <init-param>

           <param-name>css</param-name>

           <param-value>120</param-value>

       </init-param>

       <init-param>

           <param-name>js</param-name>

           <param-value>120</param-value>

       </init-param>

       <init-param>

           <param-name>jpg</param-name>

           <param-value>120</param-value>

       </init-param>

    </filter>

    <filter-mapping>

       <filter-name>ExpiresFilter</filter-name>

       <url-pattern>*.css</url-pattern>

    </filter-mapping>

    <filter-mapping>

       <filter-name>ExpiresFilter</filter-name>

       <url-pattern>*.js</url-pattern>

    </filter-mapping>

    <filter-mapping>

       <filter-name>ExpiresFilter</filter-name>

       <url-pattern>*.jpg</url-pattern>

    </filter-mapping>

测试流程

复制一张图片tss.jpg放到webroot下

Index.jsp添加代码如下

<img src="tss.jpg" />

 

打开ie浏览器临时文件夹,清空临时文件夹。一般为:

C:\Documents and Settings\Administrator\Local Settings\Temporary Internet Files

打开浏览器,运行httpwatch,访问index.jsp,

结果:如果过滤器正常工作,在缓存的文件没有超时的时间内,尽管多次访问tss.jpg也只会向服务器发送一次请求。

同时观察临时文件的产生情况和httpwatch中对于tss.jpg的请求次数

注意,如果用刷新则一定会发生重新请求,不管文件是否已被缓存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值