过滤器和拦截器的区别

Spring开发中我们会遇到Filter过滤器与Interceptor拦截器的使用,他们都能对一些请求做一下预处理,但他们之间还是有很大的不同的:

  1. 拦截器是基于Java的反射机制的,而过滤器是基于函数回调。
  2. Filter是在Servlet规范中定义的,是Servlet容器支持的。而拦截器是在Spring容器内的,是Spring框架支持的。
  3. Filter在只在Servlet前后起作用。而拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。所以在Spring构架的程序中,要优先使用拦截器)
  4. 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
  5. 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。

应用场景

  • 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
  • 权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;
  • 拦截器能深入方法的前后执行过程,可以进行统计方法执行时间来判断性能问题

过滤器

过滤器接口

public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {
    }
}

Filter是javax.servlet中的接口,是有生命周期的,存在初始化,执行以及销毁的过程。

过滤器的实现

下面来看一下我写的一个权限检验的demo

public class AuthCheckFilter implements Filter {


    //@Resource //不生效
    private UserService userService;
    //Filter的创建和销毁由WEB服务器负责。 web 应用程序启动时,
    //web 服务器将创建Filter的实例对象,并调用其init方法进行初始化,
    //容器只有在实例化过滤器时才会调用该方法一次。
    //容器为这个方法传递一个FilterConfig对象,其中包含与Filter相关的配置信息
    public void init(FilterConfig filterConfig) throws ServletException {
       //获取容器上下文,可以进行对Bean的操作
        ServletContext context = filterConfig.getServletContext();
        ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context);
        userService = (UserServiceImpl)ctx.getBean(UserServiceImpl.class);


    }
    //每次filter进行拦截都会执行。需要注意的是过滤器的一个实例可以同时服务于多个请求,
    // 特别需要注意线程同步问题,尽量不用或少用实例变量。
    // 在过滤器的doFilter()方法实现中,任何出现在FilterChain的doFilter方法之前地方,
    //request是可用的;在doFilter()方法之后response是可用的。
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        String user_token = request.getHeader("user_token");
        String methodString=request.getMethod();
        //这个判断只是简单的说明,通过init方法我们可以获取bean对象来进行验证操作
        if(!userService.findOne(user_token)){
            response.setContentType("application/json; charset=utf-8");
            String jsonString="{\"status\":\"false\",\"info\":\"用户未登陆或超时\"}";
            OutputStream os = response.getOutputStream();
            os.write(jsonString.getBytes());
            os.close();
        }
        //这里是个简单的demo ,具体逻辑需要时再处理
        if(StringUtils.isEmpty(user_token)){
            response.setContentType("application/json; charset=utf-8");
            String jsonString="{\"status\":\"false\",\"info\":\"用户未登陆或超时\"}";
            OutputStream os = response.getOutputStream();
            os.write(jsonString.getBytes());
            os.close();
        }

        filterChain.doFilter(servletRequest, servletResponse);

    }
    //在Web容器卸载 Filter 对象之前被调用
    //如果过滤器使用了其他资源,需要在这个方法中释放这些资源。
    public void destroy() {

    }
}

在上面代码的初始化方法init()中,我添加了获取Spring上下文以及bean对象的方法,这样我们就可以有更大的灵活性来做处理。

Filter的配置是在web.xml中添加的

<filter>
    <filter-name>authFilter</filter-name>
    <filter-class>app.filter.AuthCheckFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>authFilter</filter-name>
    <url-pattern>/user/*</url-pattern>
</filter-mapping>
<!--<filter-mapping>标记是有先后顺序的,它的声明顺序说明容器是如何形成过滤器链的-->
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

过滤器的demo
https://github.com/lili1990/learning/tree/master/spring-learning/spring-filter

拦截器

拦截器接口

public interface HandlerInterceptor {

    //预处理回调方法,实现处理器的预处理(如登录检查)
    //第三个参数为响应的处理器,拦截的controller对象
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception;

    //后处理回调方法,实现处理器的后处理,但在渲染视图之前
    void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception;


    //整个请求处理完毕回调方法,即在视图渲染完毕时回调
    void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception;

}

拦截适配器

public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {

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

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

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

    @Override
    public void afterConcurrentHandlingStarted(
            HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
    }

}

这是HandlerInterceptor的适配器,简单的实现了HandlerInterceptor(此处所以三个回调方法都是空实现,preHandle返回true。),我们这样就不必要实现HandlerInterceptor的全部方法,我们通过HandlerInterceptorAdapter来实现自己需要的方法。

运行流程:

spring gateway过滤器 spring 过滤器 拦截器_拦截器

注意:异常流程中,比如是HandlerInterceptor2中断的流程(preHandle返回false),此处仅调用它之前拦截器的preHandle返回true的afterCompletion方法。

拦截器配置

<mvc:annotation-driven />

    <context:component-scan base-package="app.controller"/>

    <context:component-scan base-package="app.service"/>
 <!--拦截器配置-->
    <mvc:interceptors>
        <!-- 日志拦截器,全局拦截 -->
        <bean class="app.interceptor.LogInterceptor"/>
        <!--配置拦截器, 多个拦截器,顺序执行 -->
        <!--权限校验,排除部分请求-->
        <mvc:interceptor>
            <!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller -->
            <mvc:mapping path="/**"/>
            <!--排除部分请求,不被拦截(下面拦截的请求都是swagger的请求)-->
            <mvc:exclude-mapping path="/swagger-ui.html*"/>
            <mvc:exclude-mapping path="/webjars/**"/>
            <mvc:exclude-mapping path="/configuration/ui/**"/>
            <mvc:exclude-mapping path="/swagger-resources/**"/>
            <mvc:exclude-mapping path="/v2/**"/>
            <bean class="app.interceptor.AuthCheckInterceptor" />
        </mvc:interceptor>
        <!-- 当设置多个拦截器时,先按顺序调用preHandle方法,然后逆序调用每个拦截器的postHandle和afterCompletion方法 -->
    </mvc:interceptors>

拦截器的demo
https://github.com/lili1990/learning/tree/master/spring-learning/spring-interceptor