Spring学习笔记(8)--拦截器Interceptor和过滤器Filter的区别详解

2022-04-14 16:07:29 浏览数 (1)

一、实现原理区别

过滤器和拦截器 底层实现方式大不相同:过滤器是基于函数回调的,拦截器则是基于Java的反射机制(动态代理)实现的。

过滤器(Filter):它依赖于servlet容器。在实现上是基于函数回调。《Java过滤器Filter详解》

在我们自定义的过滤器中都会实现一个 doFilter()方法,这个方法有一个FilterChain 参数,而实际上它是一个回调接口。ApplicationFilterChain是它的实现类, 这个实现类内部也有一个 doFilter() 方法就是回调方法。

每个过滤器Filter 会先执行自身的 doFilter() 过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse),也就是回调ApplicationFilterChain的doFilter() 方法,以此循环执行实现函数回调。

拦截器(Interceptor):在实现上,基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在service或者一个方法前,调用一个方法,或者在方法后,调用一个方法,比如动态代理就是拦截器的简单实现。

二、使用范围限制

过滤器Filter:过滤器实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。

拦截器(Interceptor): 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。在SpringMVC中就是依赖于SpringMVC框架

看看代码实例

我们通过代码来查看触发时机、拦截请求返回,执行顺序等区别

1)第一个过滤器:@Order:表示过滤器执行顺序。通过@Order控制过滤器的级别,值越小级别越高越先执行。

Filter使用@WebFilter注解,但注解@WebFilter是Servlet3.0的规范,并不是Spring boot提供的.因此Filter 依赖于Servlet。

代码语言:javascript复制
package com.demo.springboot2.web.service;

import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.annotation.WebFilter;
import org.springframework.core.annotation.Order;
import javax.servlet.FilterConfig;
import org.springframework.stereotype.Component;

@Order(1)
@WebFilter(filterName = "DemoFilter1", urlPatterns = "/*")
public class DemoFilter1 implements Filter{

    @Override
    public void destroy() {
        System.out.println("DemoFilter1  is destoried");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        //在DispatcherServlet之前执行
        System.out.println("############DemoFilter1 doFilte前############");
        filterChain.doFilter(request, response);
        //在视图页面返回给客户端之前执行,但是执行顺序在Interceptor之后
        System.out.println("############DemoFilter1 doFilter后r############");
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
        System.out.println("DemoFilter1  is inited");
    }

}

第二个过滤器:@Order(2):表示过滤器第2个顺序

代码语言:javascript复制
@Order(2)
@WebFilter(filterName = "DemoFilter2", urlPatterns = "/*")
public class DemoFilter2  implements Filter{
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        //在DispatcherServlet之前执行
        System.out.println("############DemoFilter2 doFilte前############");
        filterChain.doFilter(request, response);
        //在视图页面返回给客户端之前执行,但是执行顺序在Interceptor之后
        System.out.println("############DemoFilter2 doFilter后############");
    }
}

第一个拦截器:

代码语言:javascript复制
package com.demo.springboot2.web.service;

import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;

@Component
public class DemoInterceptor1 implements HandlerInterceptor{
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler)throws Exception {
        System.out.println("************DemoInterceptor1 preHandle**********");
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response, Object handler, ModelAndView arg3) throws Exception {
        System.out.println("************DemoInterceptor1 postHandle **********");

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

第二个拦截器:

代码语言:javascript复制
@Component
public class DemoInterceptor2 implements HandlerInterceptor{
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler)throws Exception {
        System.out.println("************DemoInterceptor2 preHandle**********");
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response, Object handler, ModelAndView arg3) throws Exception {
        System.out.println("************DemoInterceptor2 postHandle **********");

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

注册拦截器:

代码语言:javascript复制
package com.demo.springboot2.web.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfigurer implements WebMvcConfigurer {

    @Autowired
    private DemoInterceptor1 demoInterceptor1;

    @Autowired
    private DemoInterceptor2 demoInterceptor2;

    // 这个方法是用来配置静态资源的,比如html,js,css,等等
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    }

    // 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns("/**") 表示拦截所有的请求,
        // excludePathPatterns("/login", "/register") 表示除了登陆与注册之外,因为登陆注册不需要登陆也可以访问
        registry.addInterceptor(demoInterceptor1).addPathPatterns("/**").excludePathPatterns("/login", "/register");
        registry.addInterceptor(demoInterceptor2).addPathPatterns("/**").excludePathPatterns("/login", "/register");
        System.out.println("************addInterceptors**********");
    }
}

controller代码:

代码语言:javascript复制
package com.demo.springboot2.web.controller;

import org.springframework.web.bind.annotation.*;

@RestController
public class TestController {
    @RequestMapping("/")
    @ResponseBody
    public String index() {
        return "Hello world";
    }
    @RequestMapping("/test")
    @ResponseBody
    public String test() {
        System.out.println("------------TestController-----------");
        return "Hello test";
    }
}

请求:http://localhost:9091/test

三、触发时机

滤器Filter是在请求进入web容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。 拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。

执行顺序 :Filter 处理 -> Interceptor前置 -> controller -> Interceptor处理中 -> Interceptor 处理后 -Filter 处理后

四、拦截请求的范围不同

求资源:http://localhost:9091/favicon.ico

只有两个过滤器Filter执行:

这是因为过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用。

五、执行顺序

http://localhost:9091/test

1、多个过滤器的执行顺序跟定义的先后关系有关。 通过@Order控制过滤器的级别,值越小级别越高越先执行。 2、多个拦截器执行顺序跟注册先后顺序有关。 registry.addInterceptor(demoInterceptor1).addPathPatterns("/**").excludePathPatterns("/login", "/register"); registry.addInterceptor(demoInterceptor2).addPathPatterns("/**").excludePathPatterns("/login", "/register"); 拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行。

3、先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。postHandle() 方法被调用的顺序跟 preHandle() 居然是相反的。那为什么会这样?

我们要知道controller 中所有的请求都要经过核心组件DispatcherServlet路由,都会执行它的 doDispatch() 方法,而拦截器postHandle()preHandle()方法便是在其中调用的。

代码语言:javascript复制
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
        try {
         ...........
            try {
           
                // 获取可以执行当前Handler的适配器
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for ["   getRequestUri(request)   "] is: "   lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                // 注意: 执行Interceptor中PreHandle()方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);

                // 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
        }
        ...........
    }

看看两个方法applyPreHandle()applyPostHandle()具体是如何被调用的,就明白为什么postHandle()preHandle() 执行顺序是相反的了。

代码语言:javascript复制
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = 0; i < interceptors.length; this.interceptorIndex = i  ) {
                HandlerInterceptor interceptor = interceptors[i];
                if(!interceptor.preHandle(request, response, this.handler)) {
                    this.triggerAfterCompletion(request, response, (Exception)null);
                    return false;
                }
            }
        }

        return true;
    }
代码语言:javascript复制
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = interceptors.length - 1; i >= 0; --i) {
                HandlerInterceptor interceptor = interceptors[i];
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }

发现两个方法中在调用拦截器数组 HandlerInterceptor[] 时,循环的顺序竟然是相反的。。。,导致postHandle()preHandle() 方法执行的顺序相反。

0 人点赞