Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。Web开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
   通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截。简单说,就是可以实现web容器对某资源的访问前截获进行相关的处理,还可以在某资源向web容器返回响应前进行截获进行处理。如下所示:

  Servlet过滤器和SpringMVC的拦截器是有区别的,区别如下:

       ①拦截器是基于Java的反射机制的,而过滤器是基于函数回调。
  ②拦截器不依赖于servlet容器,过滤器依赖于servlet容器。
  ③拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
  ④拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
  ⑤在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
  ⑥拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。

  注:在网页编程中action是<form>表单标签的属性,指代的是当用户提交这张表单的时候由哪张页面来进行处理,一般使用方式是:<form action=”处理页面路径”method=”post或get”>。表明表单提交后转到action指明的动作(其实就是java文件),在这个动作中可以得到表单的数据,并对这些数据进行操作。在这个动作中可以得到表单的数据,并对这些数据进行操作。

   一个Filter必须实现javax.servlet.Filter接口的三个方法:
   void setFilterConfig(FilterConfig config) //设置Filter的配置对象;
   FilterConfig getFilterConfig() //返回Filter的配置对象;
   void doFilter(ServletRequest req,ServletResponse res,FilterChain chain) //执行Filter的工作

   Filter接口中有一个doFilter方法,当开发人员编写好Filter类实现doFilter方法,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前(服务器内部对资源的访问机制决定的),都会先调用一下Filter的doFilter方法。

过滤器应用举例:

public class EncodingFilter implements Filter {    

    private String encoding = null;    

    public void destroy() {    
        encoding = null;    
    }    

    public void doFilter(ServletRequest request, ServletResponse response,    
            FilterChain chain) throws IOException, ServletException {    
        String encoding = getEncoding();    
        if (encoding == null){    
            encoding = ”gb2312”;    
        }    
        request.setCharacterEncoding(encoding);//在请求里设置指定的编码    
        chain.doFilter(request, response);  
        //通过控制对chain.doFilter的方法的调用,来决定是否需要访问目标资源  
    }    

    public void init(FilterConfig FilterConfig) throws ServletException {    
        this.encoding = FilterConfig.getInitParameter(“encoding”);    
    }    

    private String getEncoding() {    
        return this.encoding;    
    }    

}    

XML配置代码:

<Filter>    
    <Filter-name>EncodingFilter</Filter-name>    
    <Filter-class>com.logcd.Filter.EncodingFilter</Filter-class>    
    <init-param>    
       <param-name>encoding</param-name>    
       <param-value>gb2312</param-value>    
    </init-param>    
</Filter>    

<Filter-mapping>    
   <Filter-name>EncodingFilter</Filter-name>    
   <url-pattern>/*</url-pattern>    
</Filter-mapping>    

  以上代码完成的功能为,无论进入哪个页面,都要先执行EncodingFilter类的doFilter方法设置字符集。其中,doFilter()方法类似于Servlet接口的service()方法。当客户端请求目标资源的时候,容器就会调用与这个目标资源相关联的过滤器的doFilter()方法。参数 request, response 为web 容器或 Filter 链的上一个 Filter 传递过来的请求和相应对象;参数 chain 代表当前 Filter 链的对象。 对于FilterChain接口,代表当前Filter链的对象。由容器实现,容器将其实例作为参数传入过滤器对象的doFilter()方法中。过滤器对象使用FilterChain对象调用过滤器链中的下一个过滤器,或者目标Servlet 程序去处理,也可以直接向客户端返回响应信息,或者利用RequestDispatcher的forward()和include()方法,以及HttpServletResponse的sendRedirect()方法将请求转向到其他资源。这个方法的请求和响应参数的类型是 ServletRequest和ServletResponse,也就是说,过滤器的使用并不依赖于具体的协议

   和Servlet一样,Filter的创建和销毁也是由Web容器负责。

  Filter的主要的作用就是用户在访问某个目标资源之前,对访问的请求和响应进行拦截,做一些处理,然后再调用目标程序,这样做的好处是可以对一些公共的操作进行抽象,就拿设置字符集来说,如果不使用这种方式,我们每个页面都要写设置字符集的语句。不但麻烦而且维护困难,但是如果使用Filter的话,只需要添加一个类,在xml中配置一下,如果不想使用了,将配置文件中的内容去除即可。Filter基于回调函数,我们需要实现的Filter接口中doFilter方法就是回调函数。

  当客户端发出Web资源的请求时,Web服务器根据应用程序配置文件设置的过滤规则进行检查,若客户请求满足过滤规则,则对客户请求/响应进行拦截,对请求头和请求数据进行检查或改动,并依次通过过滤器链,最后把请求/响应交给请求的Web资源处理。

  请求信息在过滤器链中可以被修改,也可以根据条件让请求不发往资源处理器,并直接向客户机发回一个响应。当资源处理器完成了对资源的处理后,响应信息将逐级逆向返回。同样在这个过程中,用户可以修改响应信息,从而完成一定的任务。

  过滤链的好处是,执行过程中任何时候都可以打断,只要不执行chain.doFilter()就不会再执行后面的过滤器和请求的内容。

  对于拦截器,写了点测试代码,顺便整理一下思路,搞清楚这几者之间的顺序:

  1、过滤器是JavaEE标准,采用函数回调的方式进行。是在请求进入容器之后,还未进入Servlet之前进行预处理,并且在请求结束返回给前端这之间进行后期处理

@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("before...");
        chain.doFilter(request, response);
        System.out.println("after...");
    }

  chain.doFilter(request, response);这个方法的调用作为分水岭。事实上调用Servlet的doService()方法是在chain.doFilter(request, response);这个方法中进行的。

  2、拦截器是被包裹在过滤器之中的。

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }

   preHandle()这个方法是在过滤器的chain.doFilter(request, response)方法的前一步执行,也就是在 System.out.println(“before…”)和chain.doFilter(request, response)之间执行的

  postHandle()方法在preHandle()方法之后,return ModelAndView之前执行,可以操控Controller的ModelAndView内容

  afterCompletion()方法是在过滤器返回给前端的前一步执行,也就是在chain.doFilter(request, response)和System.out.println(“after…”)之间执行

  3、SpringMVC的机制是由同一个Servlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的。所以过滤器、拦截器、service()方法,dispatcher()方法的执行顺序应该是这样的,大致画了个图:其实非常好测试,自己写一个过滤器,一个拦截器,然后在这些方法中都加个断点,一路F8下去就得出了结论。

  

 

  总结:拦截器功在对请求权限鉴定方面确实很有用处,在我所参与的这个项目之中,第三方的远程调用每个请求都需要参与鉴定,所以这样做非常方便,而且它是很独立的逻辑,这样做让业务逻辑代码很干净。和框架的其他功能一样,原理很简单,使用起来也很简单,大致看了下SpringMVC这一部分的源码,其实还是比较容易理解的。

  Spring框架提供了一个已经实现了拦截器接口的抽象配器类HandlerInterceptorAdapter,继承这个类然后重写一下需要用到的方法就行了,可以少几行代码,这种方式Java中很多地方都有体现。

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐