微服务时代,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实现根本相似:

// Filterpublic 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 拦截器和过滤器区别