拦截器和过滤器区别

1. 前言

Interceptor拦截器,是SpringMVC提供用来拦截发送给Controller层方法的请求的拦截器,类似于filter主要进行记录日志,判断用户是否登录,过滤权限等。拦截器和我们所学的过滤器是很相似的,只是范围不一样。

  • 过滤器filter:是JavaEE提供用来拦截所有的请求,进行过滤,它主要实现编码过滤,进行统一编码,防止乱码。
  • 拦截器Interceptor:主要用来拦截Controller控制器的方法,一般用于拦截Controller层,满足条件才放行,主要用于实现权限分配,不满足条件不能访问一些界面(比如登录才能进入)。

注意:

一般请求都是先通过过滤器过滤,才会被拦截器Interceptor处理,决定是否放行,两个过程有任何一个不放行,都不能访问到Controller层方法。

1.1 过滤器和拦截器的大概执行流程

过滤器和拦截器的大概执行流程

2. 拦截器

2.1 如何实现拦截器

拦截器它是链式调用,一个应用中可以同时存在多个拦截器Interceptor,一个请求也可以触发多个拦截器,而每个拦截器的调用会依据它的声明顺序依次执行。

首先编写一个简单的拦截器类,请求的拦截是通过HandlerInterceptor 来实现,看到HandlerInterceptor 接口中也定义了三个方法。

  • preHandle():这个方法将在请求处理之前调用(在访问controller方法之前执行),返回为true才会去执行Controller方法,注意:如果该方法的返回值为false ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。一般用来做权限控制。
  • postHandle():只有在preHandle()方法返回值为true时才会执行,会在Controller中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用,需要注意的是,postHandle() 方法被调用的顺序跟 preHandle() 是相反的,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。
  • afterCompletion():只有在 preHandle() 方法返回值为true 时才会执行。在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行。一般用于记录日志,资源释放

注意:如果preHadle()返回true,但是没有找到对应的Controller,是不会执行postHandle()方法的。

@Component
public class MyInterceptor  implements HandlerInterceptor {

    //在访问Controller方法之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //preHandle():访问Controller方法之前执行,返回为true,继续访问Controller
        System.out.println("MyInterceptor  preHandle方法在执行");
        return true;//返回false就不再继续执行Controller方法
    }

    //如果没有Controller就不执行postHandle()方法
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        /**
         *  posthandle:在执行controller方法之后, 执行jsp之前执行该方法,
         *  可以向作用域中放入数据,影响jsp展示效果,(可以执行jsp之前做渲染)
         */
        System.out.println("MyInterceptor  postHandle方法在执行");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //afterCompletion:在jsp渲染之后执行,用于记录日志,资源释放
        System.out.println("MyInterceptor afterCompletion方法在执行");
    }
}

SpringBoot中将自定义好的拦截器处理类进行注册,并通过addPathPatterns、excludePathPatterns等属性设置需要拦截或需要排除的 URL。

/**
 * 1、编写一个拦截器实现HandlerInterceptor接口
 * 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
 * 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
 */
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/**")//所有请求都被拦截包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
                
 		registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
    }
}

使用xml配置多个拦截器

<!-- /** 是拦截所有的请求   path="/interceptor"只拦截interceptor路径-->
     <mvc:interceptors>
         <mvc:interceptor>
         	<!-- /**拦截所有请求,配置全局拦截器 -->
             <mvc:mapping path="/**"/>
             <bean class="com.example.config.interceptor.MyInterceptor"/>
         </mvc:interceptor>
         <mvc:interceptor>
         	<!-- /interceptor 之拦截interceptor该路径 -->
             <mvc:mapping path="/interceptor"/>
             <bean class="com.example.config.interceptor.MyInterceptor1"/>
         </mvc:interceptor>
     </mvc:interceptors>

2.2 多个拦截器的执行顺序

如果所有拦截器都通过(都不拦截)执行顺序是这样的:

  • 都执行的话,preHandle顺序执行,postHandler逆序执行,最后再afterCompletion逆序执行。
    在这里插入图片描述
  • 如果拦截器1拦截(也就是preHandle1返回false),那么后面的拦截器也不执行,直接原路打回。
    在这里插入图片描述
  • 如果拦截器3拦截,那么也不执行controller方法,大概是这样的。
    在这里插入图片描述

3. 过滤器

3.1 如何实现过滤器

过滤器实现比较简单,直接实现Filter接口即可,也可以通过@WebFilter注解实现对特定URL拦截,Filter 接口中定义了三个方法。

  • init():该方法在容器启动初始化过滤器时被调用,它在Filter的整个生命周期只会被调用一次,注意:这个方法必须执行成功,否则过滤器会不起作用。
  • doFilter():容器中的每一次请求都会调用该方法,FilterChain 用来调用下一个过滤器 Filter
  • destroy():当容器销毁过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次
@WebFilter
public class MyFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("MyFilter init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter doFilter");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("MyFilter destroy");

    }
}

注册到到容器中,配置映射路径。

@Configuration
public class MyRegistConfig {

    @Bean
    public FilterRegistrationBean myFilter(){
        MyFilter myFilter=new MyFilter();
        FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","/css/*"));
        return filterRegistrationBean;
    }
}

3.2 过滤器先后顺序问题

  • 注解配置:按照类名的字符串比较规则比较,值小的先执行如: MyFilter和 MyFilter2,MyFilter就先执行了。
  • web.xml配置: 谁定义在上边,谁先执行。
  • JavaConfig配置:谁定义在上边,谁先执行。

4. 区别

过滤器 和 拦截器 均体现了AOP的编程思想,都可以实现诸如日志记录、登录鉴权等功能,但二者的不同点也是比较多的,接下来一一说明。

4.1 实现原理不同

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

4.1.1 过滤器

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

/**
 * FilterChain是servlet容器提供给开发人员的一个对象,它向开发人员提供了对资源的过滤请求的调用链的视图. 
 * 过滤器使用过滤器链调用链中的下一个过滤器,或者如果调用过滤器是链中的最后一个过滤器,则调用链末端的资源.
 */
public interface FilterChain {
	/**
     * 调用链中的下一个筛选器,或者如果调用筛选器是链中的最后一个筛选器,则会导致调用链末尾的资源.
     * @param request ServletRequest对象.
     * @param response ServletResponse对象.
     * @throws IOException .
     * @throws ServletException .
     */
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

在这里插入图片描述
ApplicationFilterChain里面能拿到我们自定义的xxxFilter类,在其内部回调方法doFIlter()里调用各个自定义的xxxFilter过滤器,并执行 doFilter() 方法。

public final class ApplicationFilterChain implements FilterChain {
	 /**
     * Filters. 执行目标Servlet.service()方法前需要经历的过滤器Filter,初始化为0个元素的数组对象
     */
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

 	//用于记录过滤器链中当前所执行的过滤器的位置,是当前过滤器在filters数组的下标,初始化为0
    private int pos = 0;

/**
     * The int which gives the current number of filters in the chain.
     * 过滤器链中过滤器的个数(注意:并不是数组filters的长度),初始化为0,和filters数组的初始长度一致
     */
    private int n = 0;

 	/**
     * The servlet instance to be executed by this chain.
     * 该过滤器链执行完过滤器后最终要执行的目标Servlet
     */
    private Servlet servlet = null;

	  /**
     * Does the associated servlet instance support async processing?
     * 所关联的Servlet实例是否支持异步处理 ? 缺省为 false,表示缺省情况下不支持异步处理。
     */
    private boolean servletSupportsAsync = false;


	/**
	*执行过滤器链中的下一个过滤器Filter。如果链中所有过滤器都执行过,
	* 则调用servlet的service()方法。
	*/
	public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
		// 下面的if-else分支主要是根据Globals.IS_SECURITY_ENABLED是true还是false决定
		// 如何调用目标逻辑,但两种情况下,目标逻辑最终都是 internalDoFilter(req,res)
        if (Globals.IS_SECURITY_ENABLED) {
            ServletRequest req = request;
            ServletResponse res = response;

            try {
                AccessController.doPrivileged(() -> {
                    this.internalDoFilter(req, res);
                    return null;
                });
            } catch (PrivilegedActionException var7) {
                Exception e = var7.getException();
                if (e instanceof ServletException) {
                    throw (ServletException)e;
                }

                if (e instanceof IOException) {
                    throw (IOException)e;
                }

                if (e instanceof RuntimeException) {
                    throw (RuntimeException)e;
                }

                throw new ServletException(e.getMessage(), e);
            }
        } else {
            this.internalDoFilter(request, response);
        }
    }
	//实际的Filter方法
	private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
		 //如果filter没有执行完毕,且filter中一直保持了filterChain的链式调用
        if (this.pos < this.n) {
        	//根据pos定位找到ApplicationFilterConfig 
            ApplicationFilterConfig filterConfig = this.filters[this.pos++];

            try {
            	//拆包取得Filter对象
                Filter filter = filterConfig.getFilter();
                //异步判断
                if (request.isAsyncSupported() && "false".equalsIgnoreCase(filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
                }
				/**
				* 执行目标Filter对象的doFilter()方法
				* 注意,这里当前的ApplicationFilterChain对象
				* 被递到了目标Filter对象的doFilter方法
				* 而目标Filter对象的doFilter()方法在执行完
				* 自己被指定的逻辑之后会反过来调用
				* 这个ApplicationFilterChain对象的doFilter方法
				* 只是pos向前推进了一个过滤器。
				* 这个ApplicationFilterChain和Filter之间
				* 反复调用彼此doFilter方法的过程一直持续直到当前链发现所有的
                * Filter都已经被执行
				*/
                if (Globals.IS_SECURITY_ENABLED) {
                    Principal principal = ((HttpServletRequest)request).getUserPrincipal();
                    Object[] args = new Object[]{request, response, this};
                    SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
                } else {
                	//调用用户编写的Filter中的方法进行过滤
                    filter.doFilter(request, response, this);
                }

            } catch (ServletException | RuntimeException | IOException var15) {
                throw var15;
            } catch (Throwable var16) {
                Throwable e = ExceptionUtils.unwrapInvocationTargetException(var16);
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(sm.getString("filterChain.filter"), e);
            }
        } else {
        // 这里是过滤器链中所有的过滤器都已经被执行的情况,现在需要调用servlet实例本身了。
        // !!! 注意 : 虽然这里开始调用servlet实例了,但是从当前方法执行堆栈可以看出,过滤器链
        // 和链中过滤器的doFilter方法的执行帧还在堆栈中并未退出,他们会在servlet实例的逻辑
        // 执行完后,分别执行完自己剩余的的逻辑才会逐一结束。
            try {
                if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                	//保存Servlet执行前的request与response
                    lastServicedRequest.set(request);
                    lastServicedResponse.set(response);
                }

                if (request.isAsyncSupported() && !this.servletSupportsAsync) {
                    request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
                }
				 //安全判断、特殊处理
                if (request instanceof HttpServletRequest && response instanceof HttpServletResponse && Globals.IS_SECURITY_ENABLED) {
                    Principal principal = ((HttpServletRequest)request).getUserPrincipal();
                    Object[] args = new Object[]{request, response};
                    SecurityUtil.doAsPrivilege("service", this.servlet, classTypeUsedInService, args, principal);
                } else {
                	//Servlet调用
                    this.servlet.service(request, response);
                }
            } catch (ServletException | RuntimeException | IOException var17) {
                throw var17;
            } catch (Throwable var18) {
                Throwable e = ExceptionUtils.unwrapInvocationTargetException(var18);
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(sm.getString("filterChain.servlet"), e);
            } finally {
                if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                    lastServicedRequest.set((Object)null);
                    lastServicedResponse.set((Object)null);
                }

            }

        }
    }
	
	 /**
     * 往当前要执行的过滤器链的过滤器集合filters中增加一个过滤器
     */
    void addFilter(ApplicationFilterConfig filterConfig) {

        // Prevent the same filter being added multiple times
        // 去重处理,如果已经添加进来则避免二次添加
        for(ApplicationFilterConfig filter:filters)
            if(filter==filterConfig)
                return;

        if (n == filters.length) {
	        // !!! 请注意:每次需要扩容时并不是增加一个元素空间,而是增加INCREMENT个,
	        // 这个行为的结果是filters数组的长度和数组中过滤器的个数n并不相等
            ApplicationFilterConfig[] newFilters =
                new ApplicationFilterConfig[n + INCREMENT];
            System.arraycopy(filters, 0, newFilters, 0, n);
            filters = newFilters;
        }
        filters[n++] = filterConfig;

    }
}

从源码中我们可以看到,首先从零开始遍历符合的filter ,每个xxxFilter 会先执行自身的 doFilter() 过滤逻辑。最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse),也就是回调ApplicationFilterChain的doFilter() 方法,以此循环执行实现函数回调。当所有的过滤器执行完毕后再执行service()方法。

4.1.1 拦截器

4.2 使用范围不同

我们看到过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。
在这里插入图片描述
而拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于ApplicationSwing等程序中。
在这里插入图片描述

4.3 触发时机不同

过滤器 和 拦截器的触发时机也不同,我们看下边这张图。
在这里插入图片描述

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

4.4 拦截的请求范围不同

在上边我们已经同时配置了过滤器和拦截器,再建一个Controller接收请求测试一下。


@RestController
public class HelloController {
    @Autowired
    AccountMapper accountMapper;


    @GetMapping("/hello")
    public  String hello(){
        List<Account> accountList = accountMapper.findAccountList();
        System.out.println("查询多条数据"+accountList);
        return accountList.toString();
    }
}

项目启动过程中发现,过滤器的init()方法,随着容器的启动进行了初始化。
在这里插入图片描述
此时浏览器发送请求
在这里插入图片描述
看到控制台的打印日志如下:
在这里插入图片描述

4.5 注入Bean情况不同

在实际的业务场景中,应用到过滤器或拦截器,为处理业务逻辑难免会引入一些service服务。
下边我们分别在过滤器和拦截器中都注入service,看看有什么不同?

@Component
public class TestServiceimpl implements TestService {
    @Override
    public void test() {
        System.out.println("我是test方法");
    }
}

过滤器中注入service,发起请求测试一下 ,日志正常打印出“我是test方法”。

    @Autowired
    TestService testService;


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter doFilter");
        testService.test();
        filterChain.doFilter(servletRequest, servletResponse);
    }

结果发现报空指针问题,过滤器不能访问容器里面的Bean

MyFilter doFilter
2022-04-07 21:40:51.713 ERROR 653008 --- [nio-9080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception

java.lang.NullPointerException: null
	at com.example.config.MyFIlter.MyFilter.doFilter(MyFilter.java:30) ~[classes/:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.15.jar:5.3.15]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.15.jar:5.3.15]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.15.jar:5.3.15]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) [tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540) [tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) [tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) [tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) [tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382) [tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895) [tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1732) [tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) [tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) [tomcat-embed-core-9.0.56.jar:9.0.56]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.56.jar:9.0.56]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_144]

MyInterceptor  preHandle方法在执行
MyInterceptor  postHandle方法在执行
MyInterceptor afterCompletion方法在执行

在拦截器中注入service,发起请求测试一下 ,竟然TM的也报错了,debug跟一下发现注入的service怎么是Null啊?
在这里插入图片描述

  • 这是因为加载顺序导致的问题,拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理。
  • 解决方案也很简单,我们在注册拦截器之前,先将Interceptor 手动进行注入。注意:在registry.addInterceptor()注册的是getMyInterceptor() 实例。
 @Bean
    public MyInterceptor getMyInterceptor(){
        System.out.println("注入了MyInterceptor");
        return new MyInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getMyInterceptor())
                .addPathPatterns("/**")//所有请求都被拦截包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
    }

在这里插入图片描述

4.6 控制执行顺序不同

实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。

4.6.1 过滤器执行顺序

过滤器用@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行。

@Order(Ordered.HIGHEST_PRECEDENCE)
@WebFilter
public class MyFilter2 implements Filter {

在这里插入图片描述

但是在我测试之后,发现并没有成功,过滤器依旧是按照名称的顺序先后执行,那么我们就只能通过编写MyRegistConfig 文件来决定执行顺序

@Configuration
public class MyRegistConfig {

    //根据定义Filter的bean顺序执行
    @Bean
    public FilterRegistrationBean myFilter2(){
        MyFilter2 myFilter2=new MyFilter2();
        FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean(myFilter2);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","/css/*"));
        return filterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean myFilter(){
        MyFilter myFilter=new MyFilter();
        FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","/css/*"));
        return filterRegistrationBean;
    }
}

在这里插入图片描述

4.6.2 拦截器执行顺序

拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行。

 @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/**").order(2);
        registry.addInterceptor(new MyInterceptor2())
                .addPathPatterns("/**").order(1);
                
    }
  • 看到输出结果发现,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。
  • postHandle() 方法被调用的顺序跟 preHandle() 居然是相反的!如果实际开发中严格要求执行顺序,那就需要特别注意这一点。
    在这里插入图片描述
    那为什么会这样呢? 得到答案就只能看源码了,我们要知道controller 中所有的请求都要经过核心组件DispatcherServlet路由,都会执行它的 doDispatch() 方法,而拦截器postHandle()preHandle()方法便是在其中调用的。
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() 执行顺序是相反的了。

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;
    }

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() 方法执行的顺序相反。

5. 区别

  • 过滤器是基于函数的回调,而拦截器是基于Java的反射机制。
  • 过滤器依赖于servlet容器,而拦截器不依赖于servlet容器。
  • 过滤器则可以对所有的请求起作用,而拦截器只对action请求起作用。
  • 拦截器可以获取IOC容器中的各个bean,而过滤器不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
  • 拦截器可以访问action上下文、值、栈里面的对象,而过滤器不可以。
  • 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
拦截器过滤器是在开发中常用的两种设计模式,用于在请求处理的不同阶段对请求进行处理和拦截。它们的区别主要体现在使用场景和功能上。 拦截器(Interceptor)是一种在请求处理的不同阶段进行拦截并插入自定义逻辑的机制。它通常用于对请求进行预处理或后处理,例如记录日志、验证权限、设置上下文环境等。拦截器可以在请求的开始、结束或异常抛出等时机进行拦截,并且可以被链式调用,即一个拦截器可以调用多个其他拦截器拦截器通常与框架或中间件紧密结合,在请求的处理过程中按照一定的顺序依次执行。 过滤器(Filter)是一种在请求到达目标处理程序之前或之后对请求进行过滤和处理的机制。它通常用于对请求进行预处理、过滤或修改,例如字符编码转换、参数校验、防止跨站点脚本攻击等。过滤器可以在请求被处理前进行处理(前置过滤器),也可以在请求被处理后进行处理(后置过滤器)。过滤器通常与Web容器(如Servlet容器)紧密结合,在请求的处理过程中按照一定的顺序依次执行。 总结来说,拦截器过滤器的主要区别在于使用场景和功能。拦截器更适合进行请求的预处理和后处理,而过滤器更适合对请求进行过滤和修改。拦截器一般与框架紧密结合,可以链式调用,而过滤器一般与Web容器紧密结合,按照一定的顺序依次执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值