Spring过滤器与拦截器

Spring 过滤器

什么是过滤器

过滤器 Filter,是在 Servlet 规范中定义的,是 Servlet 容器支持的,该接口定义在 javax.servlet包下,主要是在客户端请求(HttpServletRequest)进行预处理,以及对服务器响应(HttpServletResponse)进行后处理。接口代码以下:java

package javax.servlet;

import java.io.IOException;

public interface Filter {
    void init(FilterConfig var1) throws ServletException;

    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;

    void destroy();
}

复制代码

对上面三个接口方法进行分析:web

  • init(FilterConfig): 初始化接口,在用户自定义的 Filter 初始化时被调用,它与 Servlet 的 init 方法的做用是同样的。
  • doFilter(ServletRequest,ServletResponse,FilterChain): 在每一个用户的请求进来时这个方法都会被调用,并在 Servlet 的 service 方法以前调用(若是咱们是开发 Servlet 项目),而 FilterChain 就表明当前的整个请求链,经过调用 FilterChain.doFilter能够将请求继续传递下去,若是想拦截这个请求,能够不调用 FilterChain.doFilter,那么这个请求就直接返回了,因此 Filter 是一种责任链设计模式,在spring security就大量使用了过滤器,有一条过滤器链。
  • destroy: 当 Filter 对象被销毁时,这个方法被调用,注意,当 Web 容器调用这个方法以后,容器会再调用一次 doFilter 方法。

如何实现本身的过滤器

首先咱们须要建立一个类,让它实现 Filter 接口,而后重写接口中的方法:spring

package com.example.demojava.filter;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
@Order(1)   // 过滤顺序,值越小越先执行
@WebFilter(urlPatterns = "/demoFilter", filterName = "filterTest")
public class Filter1 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter初始化中...");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("doFilter()开始执行:发往 " + ((HttpServletRequest) servletRequest).getRequestURL().toString() + " 的请求已被拦截");

        System.out.println("检验接口是否被调用,尝试获取contentType以下: " + servletResponse.getContentType());

        // filter的链式调用;将请求转给下一条过滤链
        filterChain.doFilter(servletRequest, servletResponse);

        System.out.println("检验接口是否被调用,尝试获取contentType以下: " + servletResponse.getContentType());

        System.out.println("doFilter()执行结束。");

    }

    @Override
    public void destroy() {
        System.out.println("filter销毁中...");
    }
}

复制代码

当咱们配置了多个 filter,且一个请求可以被屡次拦截时,该请求将沿着 客户端 -> 过滤器1 -> 过滤器2 -> servlet -> 过滤器2 -> 过滤器1 -> 客户端 链式流转设计模式

@Component
@Order(2)   // 过滤顺序,值越小越先执行
@WebFilter(urlPatterns = "/demoFilter", filterName = "filterTest2")
public class Filter2 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter2初始化中...");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("doFilter2()开始执行:发往 " + ((HttpServletRequest) servletRequest).getRequestURL().toString() + " 的请求已被拦截");

        System.out.println("检验接口是否被调用,尝试获取contentType以下: " + servletResponse.getContentType());

        // filter的链式调用;将请求转给下一条过滤链
        filterChain.doFilter(servletRequest, servletResponse);

        System.out.println("检验接口是否被调用,尝试获取contentType以下: " + servletResponse.getContentType());

        System.out.println("doFilter2()执行结束。");

    }

    @Override
    public void destroy() {
        System.out.println("filter2销毁中...");
    }
}
复制代码

而后建立一个 Controller,对外提供两条请求路径:tomcat

@RestController
@RequestMapping("demoFilter")
public class FilterController {

    @GetMapping("hello")
    public String hello() {
        System.out.println("接口被调用:hello() ");
        return "hello filter";
    }

    @GetMapping("hi")
    public String hi() {
        System.out.println("接口被调用:hi()");
        return "hi filter";
    }
}
复制代码

启动项目,能够看到咱们的过滤器已经随着程序的启动被成功初始化了。bash

分别对这两个接口发送请求, 看到结果:服务器

doFilter()开始执行:发往 http://localhost:8080/demoFilter/hi 的请求已被拦截
检验接口是否被调用,尝试获取contentType以下: null

doFilter2()开始执行:发往 http://localhost:8080/demoFilter/hi 的请求已被拦截
检验接口是否被调用,尝试获取contentType以下: null

接口被调用:hi()

检验接口是否被调用,尝试获取contentType以下: text/plain;charset=UTF-8
doFilter2()执行结束。

检验接口是否被调用,尝试获取contentType以下: text/plain;charset=UTF-8
doFilter()执行结束。
复制代码

最后使项目中止运行,则过滤器随之销毁。cookie

能够看出,当请求同时知足多个过滤器的过滤条件时,filterChain.doFilter() 会将其按必定顺序(能够经过 @Order 指定)依次传递到下一个 filter,直到进入 servlet 进行接口的实际调用。调用完成后,响应结果将沿着原路返回,并在再一次通过各个 filter 后,最终抵达客户端。app

拦截器

什么是拦截器

拦截器是 AOP 的一种实现策略,用于在某个方法或字段被访问前对它进行拦截,而后在其以前或以后加上某些操做。同 filter 同样,interceptor 也是链式调用。每一个 interceptor 的调用会依据它的声明顺序依次执行。通常来讲拦截器能够用于如下方面 :框架

  • 日志记录 :概率请求信息的日志,以便进行信息监控、信息统计等等

  • 权限检查 :对用户的访问权限,认证,或受权等进行检查

  • 性能监控 :经过拦截器在进入处理器先后分别记录开始时间和结束时间,从而获得请求的处理时间

  • 通用行为 :读取 cookie 获得用户信息并将用户对象放入请求头中,从而方便后续流程使用

拦截器的接口方法

在 SpringMVC 中,DispatcherServlet 捕获每一个请求,在到达对应的 Controller 以前,请求能够被拦截器处理,在拦截器中进行前置处理后,请求最终才到达 Controller。

拦截器的接口是 org.springframework.web.servlet.HandlerInterceptor接口,接口代码以下:

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

复制代码

接口方法解读:

  • preHandle 方法:对客户端发过来的请求进行前置处理,若是方法返回 true,继续执行后续操做,若是返回 false,执行中断请求处理,请求不会发送到 Controller
  • postHandler 方法:在请求进行处理后执行,也就是在 Controller 方法调用以后处理,固然前提是以前的 preHandle方法返回 true。具体来讲,postHandler方法会在 DispatcherServlet 进行视图返回渲染前被调用,也就是说咱们能够在这个方法中对 Controller 处理以后的ModelAndView对象进行操做
  • afterCompletion 方法: 该方法在整个请求结束以后执行,固然前提依然是 preHandle方法的返回值为 true 才行。该方法通常用于资源清理工做

如何实现一个本身的拦截器

一样,首先建立一个类,让它实现 HandlerInterceptor 接口,而后重写接口中的方法 :

package com.demo.demofilter.demofilter.interceptor;

import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class DemoInterceptor implements HandlerInterceptor {

    @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, @Nullable ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

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

紧接着须要对拦截器进行注册,指明使用哪一个拦截器,及该拦截器对应拦截的 URL :

package com.demo.demofilter.demofilter.interceptor;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 若是有多个拦截器,继续registry.add往下添加就能够啦
        registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/demoInterceptor/**");
    }

}
复制代码
复制代码

最后是 Controller

package com.demo.demofilter.demofilter.interceptor;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("demoInterceptor")
public class InterceptorController {

    @GetMapping("hello")
    public String hello() {
        System.out.println("接口被调用:hello() ");
        return "hello interceptor";
    }

    @GetMapping("hi")
    public String hi() {
        System.out.println("接口被调用:hi()");
        return "hi interceptor";
    }
}
复制代码
复制代码

运行结果以下 :

preHandler
接口被调用: hello()
postHandler
afterHandler
复制代码

其余注意事项

在 Http 的请求执行过程当中,要通过如下几个步骤  :

  1. 由 DispatcherServlet 捕获请求
  2. DispatcherServlet 将接收到的 URL 和对应的 Controller 进行映射
  3. 在请求到达相应的 Controller 以前,由拦截器对请求进行处理
  4. 处理完成以后,进行视图的解析
  5. 返回视图

因此,只有通过 DispatcherServlet 的请求才会被拦截器捕获,而咱们自定义的 Servlet 请求则不会被拦截的。

过滤器与拦截器二者对比总结

  1. 过滤器是基于函数的回调,而拦截器是基于 Java 反射机制的
  2. 过滤器 Filter 依赖于 Servlet 容器。拦截器 Interceptor 依赖于框架容器,能够调用 IOC 容器中的各类依赖
  3. 拦截器能够 preHandle方法内返回 false 进行中断。过滤器就比较复杂,须要处理请求和响应对象来引起中断,须要额外的动做,好比将用户重定向到错误页面
  4. 过滤器只能在请求的先后使用,而拦截器能够详细到每一个方法

tomcat 容器中执行顺序: Filter -> Servlet -> Interceptor -> Controller

最后

本文到此结束,感谢阅读。若是您以为不错,请关注公众号【当我赶上你】,您的支持是我写做的最大动力。

相关文章
相关标签/搜索