关于spring:彻底理解Spring-Interceptor和Servlet-Filter

37次阅读

共计 8154 个字符,预计需要花费 21 分钟才能阅读完成。

微服务时代,java 依附 SpringBoot 又再度晋升热度。本来认为 php, python 之类的会继续鲸吞 Java 的领地,熟知微服务又复原了 Java 来日的位置。SpringBoot 依赖 Spring 生态圈满满圈粉,热度更胜当年。

SpringBoot 的实质就是实现了主动拆卸,解决了 Spring 研发的配置地区问题。然而它的根底仍然是 Spring, 对于 web 研发的根底仍然是 SpringMVC。因而有必要深刻理解 Spring, SpringMVC。

对于 Spring 来说,拦截器和过滤器是十分外围和重要的两个概念。因而本文针对这两个概念进行深入分析,让咱们彻底了解拦截器和过滤器。

对于拦截器和过滤器是什么,有大量文章曾经做了阐明,咱们这里不具体论述。本文分为五个局部:

  1. 拦截器和过滤器的区别
  2. 拦截器应用
  3. 过滤器应用
  4. 拦截器和过滤器的利用场景
  5. 拦截器和过滤器的执行过程及流程

1. 拦截器和过滤器的区别

过滤器(Filter) 拦截器(Interceptor) 总结
Filter 接口定义在 javax.servlet 包中 HandlerInterceptor 接口定义在 org.springframework.web.servlet 包中
Filter 定义在 web.xml 中 HandlerInterceptor 是在应用程序上下文配置的。
Filter 只在 Servlet 前后起作用。Filter 通常将申请和响应当做黑盒子,Filter 通常不思考 Servlet 的实现。 拦截器能深刻到办法前后、异样抛出前后等,因而拦截器的应用具备更大的弹性。容许用户接入 (hook into) 申请的生命周期,在申请过程中获取信息,Interceptor 通常和申请更加耦合。 在 Spring 架构的程序中,要无限应用拦截器。简直所有 Filter 能做的事件,Interceptor 都可能轻松的实现。
Filter 是 Servlet 标准规定的。 拦截器既能够用于 Web 程序,也能够用于 Application、Swing 程序中。 应用范畴不同
Filter 是在 Servlet 标准中定义的,是 Servlet 容器反对的。 拦截器是在 Spring 容器内的,是 Spring 框架反对的。 标准不同
Filter 不可能应用 Spring 容器资源。 拦截器是一个 Spring 的组件,归 Spring 治理,配置在 Spring 文件中,因而能应用 Spring 里的任何资源、对象,例如 Service 对象、数据源、事务管理等,通过 IoC 注入到拦截器即可。 Spring 中应用拦截器更容易
Filter 是被 Servlet(就像 Tomcat)调用的。 拦截器 (Interceptor) 是被 Spring 调用的。 因而 Filter 总是优于 Interceptor 执行。

上面援用他人的一张图阐明一下,在 tomcat 容器下,拦截器、过滤器、Servlet 以及 Spring Controller 之间的关系。

在申请解决和响应过程中过滤器和拦截器等组件的执行流程及关系:

  1. 申请首先达到 Servlet 容器,将申请转给 web 容器。
  2. web 容器调用 Servlet 过滤器,而后持续将申请转交给 Spring 的 DispatcherServlet, 申请就转交给 Spring 上下文。
  3. DispatcherServlet 就能够调用 Spring 的拦截器对申请进行拦挡解决,最初交给资源控制器。
  4. 最终有资源控制器联合业务模型进行业务解决,而后沿着 4 到 1 一层层朝外对相应进行解决,最终返回给客户端。

2. 拦截器的应用

Interceptor 的执行程序大抵为:

  • 申请达到 DispatcherServlet
  • DispatcherServlet 发送至 Interceptor, 执行 preHandle
  • 申请达到 Controller
  • 申请完结后,postHandle 执行

Spring 中次要通过 HandlerInterceptor 接口来实现申请的拦挡,实现 HandlerInterceptor 接口须要实现上面三个办法:

  • preHandle(): 在 handle 执行之前,返回 boolean 值,true 示意继续执行,false 为进行执行并返回。
  • postHandle(): 在 handle 执行之后,能够在返回之前对返回的后果进行批改。
  • afterCompletion(): 在申请实现,视图生成后调用。

2.1 利用拦截器进行申请耗时统计

申请耗时统计能够通过在申请解决之前记录一个工夫,申请解决实现后再记录一个工夫,打印出两个工夫的差就是申请耗时。

2.1.1 定义一个拦截器

public class ExecuteTimeInterceptor extends HandlerInterceptorAdapter {// private static final Logger log = LoggerFactory.getLogger(ExecuteTimeInterceptor.class);

    /**
     * 在 handler 执行之前执行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);
        return super.preHandle(request, response, handler);
    }

    /**
     * 在 handler 执行之后执行
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {long startTime = (long) request.getAttribute("startTime");
        long endTime = System.currentTimeMillis();
        long executeTime = endTime - startTime;

        System.out.println("[" + handler + "] executeTime :" + executeTime + "ms");
        super.postHandle(request, response, handler, modelAndView);
    }
}

2.1.2 配置拦截器

后面介绍咱们晓得拦截器是 Spring 上下文的货色,因而咱们须要再 Spring 的上下文进行配置,应用 springmvc-servlet.xml 配置的形式如下:

<mvc:interceptors>
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="com.xxx.interceptor.ExecuteTimeInterceptor" />
  </mvc:interceptor>
</mvc:interceptors>

2.1.3 运行测试

运行起来,而后拜访一个接口,就会看到控制台有输入如下:

2.2 应用拦截器实现利用安全检查

通常咱们的利用分为前端利用和治理后盾利用,它们的安全检查策略是不同的。咱们就能够通过拦截器实现不同安全策略的查看。具体的拦截器逻辑实现咱们就不列举了,仅简略形容一下:

  1. 对于所有前端利用的申请都进行个别的安全检查,咱们暂且让 FrontSecurityInterceptor 拦截器来实现具体的业务。
  2. 对于所有后端利用的申请都进行较严格的安全检查,因为后盾数据很重要。咱们权且用 BackendSecurityInterceptor 拦截器来实现这个艰巨的工作。
  3. 另外,咱们前端利用和治理端利用都部署在同一个域名 www.example.com 下,前台利用间接拜访这个链接即可,后盾治理咱们通过增加前缀 /admin 来拜访。

那么咱们就能够配置拦截器如下所示:

<mvc:interceptors>
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <mvc:exclude-mapping path="/admin/**" />
    <bean class="com.yang.interceptor.FrontSecurityInterceptor" />
  </mvc:interceptor>
  <mvc:interceptor>
    <mvc:mapping path="/admin/**"/>
    <bean class="com.yang.interceptor.BackendSecurityInterceptor" />
  </mvc:interceptor>
</mvc:interceptors>

2.3 Spring 拦截器总结

咱们能够利用 mvc:interceptors 标签申明一系列的拦截器,而后它们就能够造成一个拦截器链,拦截器的执行程序是按申明的先后顺序执行的,先申明的拦截器中的 preHandle 办法会先执行,然而它的 postHandle 办法和 afterCompletion 办法却会后执行。

在 mvc:interceptors 标签下申明 interceptor 次要有两种形式:

  1. 间接定义一个 Interceptor 实现类的 bean 对象。应用这种形式申明的 Interceptor 拦截器将会对所有的申请进行拦挡。
  2. 应用 mvc:interceptor 标签进行申明。应用这种形式进行申明的 Interceptor 能够通过 mvc:mapping 子标签来定义须要进行拦挡的申请门路。

3. Filter 的应用

Servlet 的 Filter 接口须要实现如下办法:

  • void init(FilterConfig paramFilterConfig) – 当容器初始化 Filter 时调用,该办法在 Filter 的生命周期只会被调用一次,个别在该办法中初始化一些资源,FilterConfig 是容器提供给 Filter 的初始化参数,在该办法中能够抛出 ServletException。init 办法必须执行胜利,否则 Filter 可能不起作用,呈现以下两种状况时,web 容器中 Filter 可能有效:

    • 抛出 ServletException
    • 超过 web 容器定义的执行工夫。
  • doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain paramFilterChain) – Web 容器每一次申请都会调用该办法。该办法将容器的申请和响应作为参数传递进来,FilterChain 用来调用下一个 Filter。
  • void destroy() – 当容器销毁 Filter 实例时调用该办法,能够在办法中销毁资源,该办法在 Filter 的生命周期只会被调用一次。

Filter 和 Interceptor 的一些用处

以下是 Filter 接口源代码中列举出的过滤器的一些用处

  • Authentication Filters
  • Logging and Auditing Filters
  • Image conversion Filters
  • Data compression Filters
  • Encryption Filters
  • Tokenizing Filters
  • Filters that trigger resource access events
  • XSL/T filters
  • Mime-type chain Filter

4.1 申请过滤器利用场景

  • 执行安全检查(perform security checks)
  • 格式化申请 header 和 body(reformat request headers or bodies)
  • 审查或记录日志(audit or log requests)
  • 依据申请内容受权或者限度用户拜访(Authentication-Blocking requests based on user identity)
  • 依据申请频率限度用户拜访

4.2 响应过滤器利用场景

  • 压缩响应内容,比方让下载的内容更小(Compress the response stream)
  • 追加或者批改响应(append or alter the response stream)
  • 创立或者整体批改响应(create a different response altogether)
  • 依据中央不同批改响应内容(Localization-Targeting the request and response to a particular locale)

5. 验证过滤器和拦截器执行过程

5.1 定义两个过滤器和拦截器

上面咱们验证一下过滤器和拦截器的执行流程和程序:

  1. 定义两个 filter, 在 web.xml 中进行配置: 别离是 FirstFilter 和 SecondFilter, 对所有申请进行过滤。
  2. 定义两个拦截器,在 spring 上下文中进行配置,这里在 springmvc-servlet.xml 中进行配置: 别离是 FirstInterceptor 和 SecondInterceptor。

两个 Filter 实现根本相似:

// Filter
public class FirstFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {System.out.println(this.getClass().getName() + ": init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println(this.getClass().getName() + ": doFilter");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {System.out.println(this.getClass().getName() + ": destroy");
    }
}

两个 Interceptor 的实现也根本类似:

public class FirstInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println(this.getClass().getName() + ": preHandle");
        return super.preHandle(request, response, handler);
    }

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

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

5.2 配置过滤器和拦截器

在 web.xml 配置过滤器:

<!-- 定义两个 filter -->
<filter>
  <filter-name>firstFilter</filter-name>
  <filter-class>com.yang.filter.FirstFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>firstFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
  <filter-name>secondFilter</filter-name>
  <filter-class>com.yang.filter.SecondFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>secondFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

在 springmvc-servlet.xml 中配置拦截器:

<mvc:interceptors>
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="com.yang.interceptor.ExecuteTimeInterceptor" />
  </mvc:interceptor>

  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="com.yang.interceptor.FirstInterceptor" />
  </mvc:interceptor>

  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="com.yang.interceptor.SecondInterceptor" />
  </mvc:interceptor>
</mvc:interceptors>

5.3 验证后果

而后运行 Tomcat, 能够看到 FirstFilter, SecondFilter 的 init()办法在启动的时候就被执行了。

咱们启动 tomcat 后主动关上一个 jsp 页面: 咱们看到两个 filter 的 doFilter 也执行了:

而后咱们拜访一个 API 接口: GET /demo/hello, 咱们看到控制台输入:

5.4 总结

  1. 过滤器是被容器调用的,容器启动会调用过滤器的 init()办法。
  2. 过滤器是针对所有申请的,包含一般的 jsp 页面,比方首页。
  3. 多个过滤器执行程序是依照在 web.xml 中配置的程序执行的。
  4. 拦截器是 Spring 上下文执行的,是在容器启动之后,申请被转给 Spring 的时候。
  5. 多个拦截器 preHandle 执行程序依照在容器中配置的程序进行执行。
  6. 多个拦截器 afterHandle 执行程序依照在容器中配置的倒序进行执行。
  7. 多个拦截器 afterCompletion 执行程序依照在容器中配置的倒序进行执行,并且在所有 afterHandle 执行实现之后执行。

码字不易,如果对大家有用,请关注“老丐说码”。

老丐说码: 专一 Java 研发、SpringBoot、Spring Cloud,前面继续退出一系列相干文章。

另外举荐大家一个收费好用的本地 markdown 编辑器: Typora

参考文章:

  • Servlet Filter and Handler Interceptor- Spring boot Implementation
  • Introduction To Servlet Filter and Handler Interceptor
  • Spring Interceptor vs Filter 拦截器和过滤器区别

正文完
 0