zoukankan      html  css  js  c++  java
  • 理解Servlet过滤器 (javax.servlet.Filter)

    过滤器(Filter)的概念

    • 过滤器位于客户端和web应用程序之间,用于检查和修改两者之间流过的请求和响应。
    • 在请求到达Servlet/JSP之前,过滤器截获请求。
    • 在响应送给客户端之前,过滤器截获响应。
    • 多个过滤器形成一个过滤器链,过滤器链中不同过滤器的先后顺序由部署文件web.xml中过滤器映射<filter-mapping>的顺序决定。
    • 最先截获客户端请求的过滤器将最后截获Servlet/JSP的响应信息。

    过滤器的链式结构

        可以为一个Web应用组件部署多个过滤器,这些过滤器组成一个过滤器链,每个过滤器只执行某个特定的操作或者检查。这样请求在到达被访问的目标之前,需要经过这个过滤器链。

    过滤器链式结构

    实现过滤器

    在Web应用中使用过滤器需要实现javax.servlet.Filter接口,实现Filter接口中所定义的方法,并在web.xml中部署过滤器。

    public class MyFilter implements Filter {

        public void init(FilterConfig fc) {
            //过滤器初始化代码
        }

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
            //在这里可以对客户端请求进行检查
            //沿过滤器链将请求传递到下一个过滤器。
            chain.doFilter(request, response);
            //在这里可以对响应进行处理

        }

        public void destroy( ) {
            //过滤器被销毁时执行的代码
        }

    }

    Filter接口

    public void init(FilterConfig config)

    web容器调用本方法,说明过滤器正被加载到web容器中去。容器只有在实例化过滤器时才会调用该方法一次。容器为这个方法传递一个FilterConfig对象,其中包含与Filter相关的配

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

    每当请求和响应经过过滤器链时,容器都要调用一次该方法。需要注意的是过滤器的一个实例可以同时服务于多个请求,特别需要注意线程同步问题,尽量不用或少用实例变量。 在过滤器的doFilter()方法实现中,任何出现在FilterChain的doFilter方法之前地方,request是可用的;在doFilter()方法之后response是可用的。

    public void destroy()

    容器调用destroy()方法指出将从服务中删除该过滤器。如果过滤器使用了其他资源,需要在这个方法中释放这些资源。

    部署过滤器

    在Web应用的WEB-INF目录下,找到web.xml文件,在其中添加如下代码来声明Filter。

    <filter>
    <filter-name>TlwModifyResponseFilter</filter-name>
    <filter-class>
    com.Common.action.TlwModifyResponseFilter
    </filter-class>
    </filter>

    <filter-mapping>
    <filter-name>TlwModifyResponseFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    以上是我的项目工程中的action路径

    在2.4版本的servlet规范在部属描述符中新增加了一个<dispatcher>元素,这个元素有四个可能的值:REQUEST,FORWARD,INCLUDE和ERROR
    可以在一个<filter-mapping>元素中加入任意数目的<dispatcher>,使得filter将会作用于直接从客户端过来的request,通过forward过来的request,通过include过来的request和通过<error-page>过来的request。如果没有指定任何<dispatcher>元素,默认值是REQUEST。
    可以通过下面几个例子来辅助理解。   
    例1:  
    1 <filter-mapping>   
    2   <filter-name>Logging   Filter</filter-name>   
    3   <url-pattern>/products/*</url-pattern>   
    4 </filter-mapping> 

     这种情况下,过滤器将会作用于直接从客户端发过来的以/products/…开始的请求。因为这里没有制定任何的<dispatcher>元素,默认值是REQUEST。   

    例2:  

      <filter-mapping>   
          <filter-name>Logging   Filter</filter-name>   
          <servlet-name>ProductServlet</servlet-name>   
          <dispatcher>INCLUDE</dispatcher>   
      </filter-mapping> 

     这种情况下,如果请求是通过request   dispatcher的include方法传递过来的对ProductServlet的请求,则要经过这个过滤器的过滤。其它的诸如从客户端直接过来的对ProductServlet的请求等都不需要经过这个过滤器。   
         指定filter的匹配方式有两种方法:直接指定url-pattern和指定servlet,后者相当于把指定的servlet对应的url-pattern作为filter的匹配模式,filter的路径匹配和servlet是一样的,都遵循servlet规范中《SRV.11.2   Specification   of   Mappings》一节的说明  。

    例3: 

      <filter-mapping>   
             <filter-name>Logging   Filter</filter-name>   
             <url-pattern>/products/*</url-pattern>   
             <dispatcher>FORWARD</dispatcher>   
             <dispatcher>REQUEST</dispatcher>   
      </filter-mapping>  

     在这种情况下,如果请求是以/products/…开头的,并且是通过request   dispatcher的forward方法传递过来或者直接从客户端传递过来的,则必须经过这个过滤器。

    1.请求过滤器

    web.xml中配置如下

    <filter>
            <filter-name>MyFilter</filter-name>
            <filter-class>cn.telling.Filter.MyFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>MyFilter</filter-name>
            <url-pattern>/*</url-pattern>
    
        </filter-mapping>
    public class MyFilter implements Filter{
         FilterConfig config;  
    
        /**
         * 
         * @Description: TODO
         * @param filterConfig
         * @throws ServletException
         * @author xingle
         * @data 2015-10-26 下午4:32:44
         */
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
              System.out.println("begin do the log filter!"); 
              this.config = filterConfig;
        }
    
        /**
         * 
         * @Description: TODO
         * @param request
         * @param response
         * @param chain
         * @throws IOException
         * @throws ServletException
         * @author xingle
         * @data 2015-10-26 下午4:32:44
         */
        @Override
        public void doFilter(ServletRequest request, ServletResponse response,
                FilterChain chain) throws IOException, ServletException {
            ServletContext context = this.config.getServletContext();  
            System.out.println("before the log filter!");  
            HttpServletRequest hreq = (HttpServletRequest) request;  
            System.out.println("Log Filter已经截获到用户的请求的地址:"+hreq.getServletPath() );  
            // Filter 只是链式处理,请求依然转发到目的地址。  
            chain.doFilter(request, response);  
        }
    
        /**
         * 
         * @Description: TODO
         * @author xingle
         * @data 2015-10-26 下午4:32:44
         */
        @Override
        public void destroy() {
            this.config = null;  
        }
    
    }

    2.响应过滤器

    比如要实现输出压缩:

    这样不行!输出会从servlet直接返回给客户。但是我们的目标是压缩输出。

    先来想想这样一个问题…… servlet 实际上是从响应对象得到输出流或书写器。那么,如果不把实际的相应对象传给servlet,而是由过滤器换入一个定制的相应对象,而且这个定制响应对象有你能控制的一个输出流,这样可以吗?需要建立我们自己的HttpServletResponse 接口定制实现,并把它通过chain.doFilter() 调用传递到servlet。而且这个定制实现还必须包含一个定制输出流,因为这正是我们的目标,在servlet写输出之后并且在输出返回给客户之前,过滤器就能拿到这个输出。

    servlet中使用HttpServletResponseWrapper截获返回的页面内容

     要截获页面返回的内容,整体的思路是先把原始返回的页面内容写入到一个字符Writer,然后再组装成字符串并进行分析,最后再返回给客户端。代码如下:

    package cn.telling.Filter;
    
    import java.io.CharArrayWriter;
    import java.io.PrintWriter;
    
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpServletResponseWrapper;
    
    /**
     * 自定义一个响应结果包装器,将在这里提供一个基于内存的输出器来存储所有
     * 返回给客户端的原始HTML代码。
     * @ClassName: ResponseWrapper TODO
     * @author xingle
     * @date 2015-10-27 上午9:22:14
     */
    public class ResponseWrapper extends HttpServletResponseWrapper {
        private PrintWriter cachedWriter;
        private CharArrayWriter bufferedWriter;
    
        /**
         * @param response
         */
        public ResponseWrapper(HttpServletResponse response) {
            super(response);
            // 这个是我们保存返回结果的地方
            bufferedWriter = new CharArrayWriter();
            // 这个是包装PrintWriter的,让所有结果通过这个PrintWriter写入到bufferedWriter中
            cachedWriter = new PrintWriter(bufferedWriter);
        }
        
        public PrintWriter getWriter(){
            return cachedWriter;
        }
        
        /**
         * 获取原始的HTML页面内容。
         * @return
         */
        public String getResult() {
            return bufferedWriter.toString();
        }
    
    }

    然后再写一个过滤器来截获内容并处理:

    package cn.telling.Filter;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * 
     * @ClassName: MyServletFilter TODO
     * @author xingle
     * @date 2015-10-27 上午9:24:34
     */
    public class MyServletFilter implements Filter {
    
        /**
         * 
         * @Description: TODO
         * @param filterConfig
         * @throws ServletException
         * @author xingle
         * @data 2015-10-27 上午9:24:47
         */
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            // TODO Auto-generated method stub
    
        }
    
        /**
         * 
         * @Description: TODO
         * @param request
         * @param response
         * @param chain
         * @throws IOException
         * @throws ServletException
         * @author xingle
         * @data 2015-10-27 上午9:24:47
         */
        @Override
        public void doFilter(ServletRequest request, ServletResponse response,
                FilterChain chain) throws IOException, ServletException {
            // 使用我们自定义的响应包装器来包装原始的ServletResponse
            ResponseWrapper wrapper = new ResponseWrapper((HttpServletResponse) response);
            // 这句话非常重要,注意看到第二个参数是我们的包装器而不是response
            chain.doFilter(request, wrapper);
            // 处理截获的结果并进行处理,比如替换所有的“名称”为“铁木箱子”
            String result = wrapper.getResult();
            result = result.replace("名称", "替换后的");
            // 输出最终的结果
            PrintWriter out = response.getWriter();
            out.write(result);
            out.flush();
            out.close();
        }
    
        /**
         * 
         * @Description: TODO
         * @author xingle
         * @data 2015-10-27 上午9:24:47
         */
        @Override
        public void destroy() {
            // TODO Auto-generated method stub
    
        }
    
    }

    然后将该servlet配置在web.xml文件中,如下:

        <filter>
            <filter-name>MyFilter</filter-name>
            <filter-class>cn.telling.Filter.MyServletFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>MyFilter</filter-name>
            <url-pattern>/*</url-pattern>
    
        </filter-mapping>

    然后我们在web应用根目录下建立一个jsp文件echo.jsp,内容如下:

    <%@page language="java" contentType="text/html; charset=UTF-8"
      pageEncoding="UTF-8"%>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <head>
        <title>页面返回结果过滤测试</title></head>
    </head>
    <body>
    你好,我叫“名称”。
    </body>
    
    </html>

    配置完后,部署到tomcat,然后访问应用下的echo.jsp文件,就可以发现返回的内容变成了:

    从而也就达到了我们想要的效果了。在文章开头我也提到了说有一个问题,那就是有可能在运行的过程中页面只输出一部分,尤其是在使用多个框架后(比如sitemesh)出现的可能性非常大,在探究了好久之后终于发现原来是响应的ContentLength惹的祸。因为在经过多个过滤器或是框架处理后,很有可能在其他框架中设置了响应的输出内容的长度,导致浏览器只根据得到的长度头来显示部分内容。知道了原因,处理起来就比较方便了,我们在处理结果输出前重置一下ContentLength即可,如下:

    // 重置响应输出的内容长度
    response.setContentLength(-1);
    // 输出最终的结果
    PrintWriter out = response.getWriter();
    out.write(result);
    out.flush();
    out.close();

    这样处理后就不会再出现只出现部分页面的问题了!

  • 相关阅读:
    DIV高度设置全屏
    Yii2使用PHPExcel读取excel
    关于linux centos7 vmware 和windows7 文件共享笔记
    mysql rpm包安装
    linux crontab 计划任务脚本
    linux php5.6 安装
    linux上安装php phpredis扩展
    让微信小程序每次请求的时候不改变session_id的方法
    mysql主从配置
    mysql存储过程之游标遍历数据表
  • 原文地址:https://www.cnblogs.com/xingele0917/p/3673877.html
Copyright © 2011-2022 走看看