zoukankan      html  css  js  c++  java
  • JSP Filters(过滤器)

      Filter是拦截Request请求的对象:在用户的请求访 问资源前处理ServletRequest以及ServletResponse,它可 用于日志记录、加解密、Session检查、图像文件保护 等。通过Filter可以拦截处理某个资源或者某些资源。 Filter的配置可以通过Annotation或者部署描述来完成。 当一个资源或者某些资源需要被多个Filter所使用到, 且它的触发顺序很重要时,只能通过部署描述来配置。

    一.Filter API

    1. Filter 相关接口,包含Filter, FilterConfig, FilterChain

      Filter的实现必须继承javax.servlet.Filter接口。这个 接口包含了Filter的3个生命周期:init、doFilter、 destroy。

      Servlet容器初始化Filter时,会触发Filter的init方 法,一般来说是在应用开始时。也就是说,init方法并 不是在该Filter相关的资源使用到时才初始化的,而且 这个方法只调用一次,用于初始化Filter。init方法的定 义如下:

    oid init(FilterConfig filterConfig)

    注意: FilterConfig实例是由Servlet容器传入init方法中的.

      当Servlet容器每次处理Filter相关的资源时,都会调 用该Filter实例的doFilter方法。Filter的doFilter方法包含 ServletRequest、ServletResponse、FilterChain这3个参 数。

      doFilter的定义如下:

    void doFilter(ServletRequest request, ServletResponse response,FilterChain filterChain)

      接下来,说明一下doFilter的实现中访问 ServletRequet、ServletResponse。这也就意味着允许给 ServletRequest增加属性或者增加Header。当然也可以修 饰ServletRequest或者ServletRespone来改变它们的行 为。

      在Filter的doFilter的实现中,最后一行需要调用 FilterChain中的doFilter方法。注意Filter的doFilter方法 里的第3个参数,就是filterChain的实例:

    filterChain.doFilter(request, response)

      一个资源可能需要被多个Filter关联到(更专业一 点来说,这应该叫作Filter链条),这时Filter.doFilter() 的方法将触发Filter链条中下一个Filter。只有在Filter链 条中最后一个Filter里调用的FilterChain.doFilter(),才会 触发处理资源的方法。

      如果在Filter.doFilter()的实现中,没有在结尾处调 用FilterChain.doFilter()的方法,那么该Request请求中 止,后面的处理就会中断。

    注意: FilterChain接口中,唯一的方法就是doFilter。该方法与Filter中的 doFilter的定义是不一致的:在FilterChaing中,doFilter方法只有两个参 数,但在Filter中,doFilter方法有三个参数。

      Filter接口中,最后一个方法是destroy,它的定义 如下:

    Void destroy()

      该方法在Servlet容器要销毁Filter时触发,一般在应 用停止的时候进行调用。

      除非Filter在部署描述中被多次定义到,否则Servlet 窗口只会为每个Filter创建单一实例。由于Serlvet/JSP的 应用通常要处理用户并发请求,此时Filter实例需要同 时被多个线程所关联到,因此需要非常小心地处理多线 程问题。

    三. Filter配置

      当完成Filter的实现后,就可以开始配置Filter了。 Filter的配置需要如下步骤:

    • 确认哪些资源需要使用这个Filter拦截处理。
    • 配置Filter的初始化参数值,这些参数可以在Filter的 init方法中读取到;
    • 给Filter取一个名称。一般来说,这个名称没有什么 特别的含义,但在一些特殊的情况下,这个名字十 分有用。例如,要记录Filter的初始化时间,但这个 应用中有许多的Filter,这时它就可以用来识别Filter 了。

      FilterConfig接口允许通过它的getServletContext的 方法来访问ServletContext:

    ServletContext getServletContext(

      如果配置了Filter的名字,在FilterConfig的 getFilterName中就可以获取Filter的名字。getFilterName 的定义如下:

    java.lang.String getFilterName()

      当然,最重要的还是要获取到开发者或者运维给 Filter配置的初始化参数。为了获取这些初始化参数, 需要用到FilterConfig中的两个方法,第一个方法是 getParameterNames:

    java.util.Enumeration<java.lang.String> getInitParameterNames()

      这个方法返回Filter参数名字的Enumeration对象。 如果没有给这个Filter配置任何参数,该方法返回的是 空的Enumeration对象。

      第二个方法是getParameter:

    java.lang.String getInitParameter(java.lang.String parameterName)

      有两种方法可以配置Filter:一种是通过WebFilter 的Annotation来配置Filter,另一种是通过部署描述来注 册。使用@WebFilter的方法,只需要在Filter的实现类 中增加一个注解即可,不需要重复地配置部署描述。当 然,此时要修改配置参数,就需要重新构建Filter实现 类了。换句话说,使用部署描述意味着修改Filter配置 只要修改一下文本文件就可以了。

      使用@WebFilter,你需要熟悉下表中所列出来的 参数,这些参数是在WebFilter的Annotation里定义的。 所有参数都是可选的。

    WebFilter的属性
    属性 描述
    asyncSupported Filter是否支持异步操作
    description Filter的描述
    dispatcerTypes Filter所生效范围
    displayName Filter的显示名
    filterName Filter的名称
    initParams Filter的初始化参数
    largeIcon Filter的大图名称
    servletName Filter所生效的Servlet名称
    smallIcon Filter的小图名称
    urlPatterns Filter所生效的URL路径
    value Filter所生效的URL路径

     

    三. 示例1: 日志 Filter

      作为第1个例子,将做一个简单的Filter:在app09a 的应用中把Request请求的URL记录到日志文本文件 中。日志文本文件名通过Filter的初始化参数来配置。 此外,日志的每条记录都会有一个前缀,该前缀也由 Filter初始化参数来定义。通过日志文件,可以获得许 多有用的信息,例如在应用中哪些资源访问最频繁; Web站点在一天中的哪个时间段访问量最多。

       这个Filter的类名叫LoggingFilter。 一般情况下,Filter的类名都以*Filter结尾。

    package filter;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Date;
    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.annotation.WebFilter;
    import javax.servlet.annotation.WebInitParam;
    import javax.servlet.http.HttpServletRequest;
    
    @WebFilter(filterName = "LoggingFilter", urlPatterns = { "/ *" }, initParams = {
            @WebInitParam(name = "logFileName", value = "log.txt"), @WebInitParam(name = "prefix", value = "URI: ") })
    public class LoggingFilter implements Filter {
        private PrintWriter logger;
        private String prefix;
        
        
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            prefix = filterConfig.getInitParameter("prefix"); //得到URI
            String logFileName = filterConfig.getInitParameter("logFileName");
            String appPath = filterConfig.getServletContext().getRealPath("/"); //得到项目路径
    // without path info in logFileName, the log file will be
    // created in $TOMCAT_HOME/bin
            System.out.println("logFileName:" + logFileName);
            try {
                logger = new PrintWriter(new File(appPath, logFileName)); //打开文件
                logger.println("I have the output");
                logger.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                throw new ServletException(e.getMessage());
            }
        }
    
        @Override
        public void destroy() {
            System.out.println("destroying filter");
            if (logger != null) {
                logger.close();
            }
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
                throws IOException, ServletException {
            System.out.println("LoggingFilter.doFilter");
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            logger.println(new Date() + " " + prefix + httpServletRequest.getRequestURI()); //写入数据
            logger.flush(); //刷新缓冲
            filterChain.doFilter(request, response); //如果没有doFilter方法,后面发Filter处理就会中断
        }
        
    }

      下面来仔细分析一下Filter类。 首先,该Filter的类实现了Filter的接口并声明两个 变量:PrintWriter类型的logger和String类型的prefix。

      其中PrintWriter用于记录日志到文本文件,prefix的 字符串用于每条日志的前缀。 Filter的类使用了@WebFilter的Annotation,将两个 参数(logFilteName、prefix)传入到该Filter中.

      在Filter的init方法中,通过FilterConfig里传入的 getInitParameter方法来获取prefix和getFileName的初始 化参数。其中把prefix参数中赋给了类变量prefix, logFileName则用于创建一个PrintWriter

      如果Servlet/JSP应用是通过Servlet/JSP容器启动 的,那么当前应用的工作目录是当前JDK所在的目录。 如果是在Tomcat中,该目录是Tomcat的安装目录。在 应用中创建日志文件,可以通过 ServletContext.getRealPath来获取工作目录,结合应用 工作目录以及初始化参数中的logFilterNmae,就可以得 到日志文件的绝对路径.

      当Filter的init方法被执行时,日志文件就会创建出 来。如果在应用的工作目录中该文件已经存在,那么该 日志文件的内容将会被覆盖。 当应用关闭时,PrintWriter需要被关闭。因此在 Filter的destroy方法中,需要:关闭日志

      Filter的doFilter实现中记录着所有从ServletRequest 到HttpServletRequest的Request,并调用了它的 getRequestURI方法,该方法的返回值将记录通过 PrintWriter的pringln记录下来

      每条记录都有一个时间戳以及前缀,这样可以很方 便地标识每条记录。接下来 Filter的doFilter实现调用 PrintWriter的flush方法以及FilterChain.doFilter,以唤起 资源的调用:

       如果使用Tomcat,Filter的初始化并不会等到第一 个Request请求时才触发进行。这点可以在控制台中打 印出来的logFileName参数值中可以看到。在app09a应 用中通过URL调用test.jsp页面,就可以测试该Filter了

      通过检查日志文件的内容,就可以验证这个Filter 是否运行正常。

    四.  示例2:图像文件保护Filter

      本例中的图像文件保护Filter用于在浏览器中输入 图像文件的URL路径时,防止下载图像文件。应用中的 图像文件只有当图像链接在页面中被点击的时候才会显 示。该Filter的实现原理是检查HTTP Header的referer 值。如果该值为null,就意味着当前的请求中没有 referer值,即当前的请求是直接通过输入URL来访问该 资源的。如果资源的Header值为非空,将返回Request 语法的原始页面作为referer值。注意Header的referer的 属性名中,在第2个e以及第3个e中仅有一个r。

       ImageProtectorFilter的Filter实现类,如清单所 示。从WebFilter的Annotation中,可以看到该Filter应用 于所有的.png、.jpg、.gif文件后缀。

    ImageProtectorFilter实现类

    package filter;
    
    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;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    
    
    //过滤所有以.png , .jpg  .gif 为后缀的访问
    @WebFilter(filterName = "ImageProtetorFilter", urlPatterns = { "*.png", "*.jpg", "*.gif" })
    public class ImageProtectorFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }
    
        @Override
        public void destroy() {
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
                throws IOException, ServletException {
            //直接输入图片地址访问http://localhost:8080/app09/图片.jpg会被拦截
            System.out.println("ImageProtectorFilter");
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            String referrer = httpServletRequest.getHeader("referer");
            System.out.println("referrer:" + referrer);
            if (referrer != null) {
             filterChain.doFilter(request, response); }
    else { throw new ServletException("Image not available"); } } }

       这里并没有init和destroy方法。其中doFilter方法读 取到Header中的referer值,要确认是要继续处理这个资 源还是给个异常.

      测试该Filter,可以在浏览器中输入如下ULR路 径,尝试访问logo.png图像:

    http://localhost:8080/app09/图片.jpg

      接下来,通过image.jsp的页面来访问该图像:

    http://localhost:8080/app09/test.jsp

    五.示例3:下载计数Filter

      本例子中,下载计数Filter将会示范如何在Filter中 计算资源下载的次数。这个示例特别有用,它将会得到 文档、音频文件的受欢迎程度。作为简单的示例,这里 将数值保存在属性文件中,而不保存在数据库中。其中 资源的ULR路径将作为属性名保存在属性文件中。

      因为我们把值保存在属性文件中,并且Filter可以 被多线程访问,因此涉及线程安全问题。用户访问一个 资源时,Filter需要读取相应的属性值加1,然后保存该 值。如果第二个用户在第一个线程完成前同时访问该资 源,将会发生什么呢?计算值出错。在本例中,读写的 同步锁并不是一个好的解决这个问题的方法,因为它会 导致扩展性问题。

       本示例中,解决这个线程安全问题是通过Queue以 及Executor。

      简而言之,进来的Request请求将会保存在单线程 Executor的队列中。替换这个任务十分方便,因为这是 一个异步的方法,因此你不需要等待该任务结束。 Executor一次从队列中获取一个对象,然后做相应属性 值的增加。由于Executor只在一个线程中使用,因此可 以消除多个线程同时访问一个属性文件的影响。

    DownloadCounterFilter实现类

    package filter;
    
    import java.io.File;
    import java.io.FileReader;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.util.Properties;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    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.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    
    // 拦截所有URL
    //书上的是 urlPatterns={"/"},亲测无效
    @WebFilter(filterName = "DownloadCounterFilter", urlPatterns = { "*.JPG" })
    public class DownloadCounterFilter implements Filter {
        // 获取单个线程池的Executor 由于Executor只在一个线程中使用,因此可 以消除多个线程同时访问一个属性文件的影响。
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        // propeties 属性集
        Properties downloadLog;
        File logFile;
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("DownloadCounterFilter");
            //获取路径
            String appPath = filterConfig.getServletContext().getRealPath("/");
            // 获取downLoadLog.txt文件
            logFile = new File(appPath, "downloadLog.txt");
            if (!logFile.exists()) {
                try {
                    logFile.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
            downloadLog = new Properties();
            try {
                downloadLog.load(new FileReader(logFile));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void destroy() {
            executorService.shutdown();
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
                throws IOException, ServletException {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            final String uri = httpServletRequest.getRequestURI();
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    String property = downloadLog.getProperty(uri);
                    if (property == null) {
                        downloadLog.setProperty(uri, "1");
                    } else {
                        int count = 0;
                        try {
                            count = Integer.parseInt(property);
                        } catch (NumberFormatException e) {
    // silent
                        }
                        count++;
                        downloadLog.setProperty(uri, Integer.toString(count));
                    }
                    try {
                        downloadLog.store(new FileWriter(logFile), "");
                    } catch (IOException e) {
                    }
                }
            });
            filterChain.doFilter(request, response);
        }
    }

      如果在当前应用的工作目录中不存在 downloadLog.txt文件,这个Filter的init方法就会创建 它

      接着创建Properties对象,并读取该文件.

      注意,Filter的实现类中引用到了 ExecutorService(Executor的子类).

      且当Filter销毁时,会调用ExecutorService的 shutdown方法.

      Filter的doFilter实现中大量地使用到这个Job。每次 URL请求都会调用到ExecutorService的execute方法,然 后才调用FilterChaing.doFilter()。该任务的execute实现 非常好理解:它将URL作为一个属性名,从Properties 实例中获取该属性的值,然后加1,并调用flush方法写 回到指定的日志文件中

      这个Filter可在许多资源上生效,但也可以非常简 单地配置,限定为PDF或者AVI文件资源。

    properties文件

    六. Filter顺序

      如果多个Filter应用于同一个资源,Filter的触发顺 序将变得非常重要,这时就需要使用部署描述来管理 Filter:指定哪个Filter先被触发。例如:Filter 1需要在 Filter 2前被触发,那么在部署描述中,Filter 1需要配置 在Filter 2之前:

    <filter>
    <filter-name>Filter1</filter-name>
    <filter-class>
    the fully-qualified name of the filter class
    </filter-class>
    </filter>
    <filter>
    <filter-name>Filter2</filter-name>
    <filter-class>
    the fully-qualified name of the filter class
    </filter-class>
    </filter>

    通过部署描述之外的配置来指定Filter触发的顺序 是不可能的

  • 相关阅读:
    Live Writer配置
    protobufnet 学习手记
    好的Sql语句也能提高效率(二)
    关于CodeSmith的输出问题
    [Scrum]12.29
    [scrum] 1.4
    分享 关于c#注释的规范
    [Scrum] 1.3
    分享:将XML(VS提取注释时生成)转换为Chm的一个方法
    【Scrum】2010.12.27
  • 原文地址:https://www.cnblogs.com/jiangfeilong/p/10710273.html
Copyright © 2011-2022 走看看