什么是拦截器?拦截器如何配置?

1. 什么是拦截器?

拦截器(Interceptor)是 Spring MVC 中的一个很重要的组件,主要用于拦截用户的请求,可以在指定方法前后, 根据业务需要执行预先设定的代码。它的作用主要有:

  1. 权限验证:验证用户是否登录、是否有权限访问某个接口。
  2. 日志记录:记录请求信息的日志,如请求参数,响应信息等。
  3. 性能监控:监控系统的运行性能,如慢查询接口等。
  4. 通用行为:插入一些通用的行为,比如开发环境忽略某些请求。

也就是说, 允许开发⼈员提前预定义⼀些逻辑, 在用户的请求响应前后执行。也可以在用户请求前阻止其执行。在拦截器当中,开发⼈员可以在应⽤程序中做⼀些通用性的操作, 比如通过拦截器来拦截前端发来的请求, 判断Session中是否有登录用户的信息。如果有就可以放行, 如果没有就进行拦截。
image.png

2. 拦截器的实现

2.1 定义拦截器

自定义一个拦截器非常简单,只需要实现HandlerInterceptor这个接口即可,并重写该接口的方法。该接口有三个可以实现的方法:

  • preHandle():该方法会在目标方法执行前执行,当其返回值为true时,表示继续向下执行;当其返回值为false时,会中断后续的所有操作(包括调用下一个拦截器和控制器类Controller中的方法执行等 )。
  • postHandle(): 该方法会在目标方法调用之后,且解析视图之前执行。可以通过此方法对请求域中的模型和视图作出进一步的修改。
  • afterCompletion():该方法会在整个请求完成,即视图渲染结束之后执行。可以通过此方法实现一些资源清理、记录日志信息等工作。
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("LoginInterceptor 目标方法执行前执行...");
        // true-放行  false-拦截
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("LoginInterceptor 目标方法执行后执行...");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("LoginInterceptor 视图渲染完毕之后执行,最后执行....");
    }
}

2.2 注册配置拦截器

想要在Spring Boot生效,只需要定义一个配置类,实现WebMvcConfigurer这个接口,并且实现其中的addInterceptiors()方法即可。

public class WebConfig implements WebMvcConfigurer {
    // 注入自定义的拦截器对象
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册自定义的拦截器对象
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")// 设置拦截器拦截的请求路径(/** 表示拦截所有请求)
                .excludePathPatterns("/user/login");//设置拦截器 排除拦截的路径
    }
}

我们试试启动服务,访问任意一个请求,观察后端日志:
image.png
可以看到preHandle 方法执行之后就放行了, 开始执⾏目标方法, 目标⽅法执⾏完成之后执⾏postHandle和afterCompletion⽅法。
我们把拦截器中preHandle⽅法的返回值改为false, 再观察运⾏结果:
image.png
可以看到, 拦截器拦截了请求, 没有进⾏响应。

3. 拦截器的工作原理

3.1 执行流程

没有拦截器时,程序的调用顺序:
image.png
有了拦截器之后,程序会在调用Controller 之前进行相应的业务处理:
image.png
注意:

  1. 添加拦截器后, 程序在执⾏Controller的⽅法之前, 请求会先被拦截器拦截住。执⾏ preHandle() ⽅法,这个⽅法需要返回⼀个布尔类型的值。 如果返回true, 就表示放⾏本次操作, 继续访问Controller中的⽅法;如果返回false,则不会放⾏(Controller中的⽅法也不会执⾏)。
  2. Controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 这个⽅法以及afterCompletion() ⽅法,执⾏完毕之后,最终给浏览器响应数据。

3.2 源码分析

Spring Boot 拦截器是基于 Java 的 Servlet 规范实现的,通过实现 HandlerInterceptor 接口来实现拦截器功能。
当Tomcat启动之后, 有⼀个核⼼的类DispatcherServlet,它来控制程序的执行顺序。所有请求都会先进到DispatcherServlet,执⾏doDispatch 调度⽅法。如果有拦截器, 会先执⾏拦截器preHandle() ⽅法的代码, 如果 preHandle() 返回true, 继续访问controller中的⽅法。Controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 和 afterCompletion() ,返回给DispatcherServlet,最终给浏览器响应数据。
image.png
DispatcherServlet 接收到请求后, 执⾏doDispatch 调度⽅法, 再将请求转给Controller。
doDispatch 源码实现如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    //1. 获取执⾏链
                    //遍历所有的 HandlerMapping 找到与请求对应的Handler
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }

                    //2. 获取适配器
                    //遍历所有的 HandlerAdapter,找到可以处理该 Handler 的HandlerAdapter
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = HttpMethod.GET.matches(method);
                    if (isGet || HttpMethod.HEAD.matches(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }

                    //3. 执⾏拦截器preHandle⽅法
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }

                    //4. 执⾏⽬标⽅法
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(processedRequest, mv);
                    //5. 执⾏拦截器postHandle⽅法
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }

                //6. 处理视图, 处理之后执⾏拦截器afterCompletion⽅法
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                //7. 执⾏拦截器afterCompletion⽅法
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }

从上述源码可以看出在开始执⾏ Controller 之前,会先调⽤ 预处理⽅法 applyPreHandle,⽽applyPreHandle ⽅法的实现源码如下:

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
            // 获取项⽬中使⽤的拦截器 HandlerInterceptor
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
            if (!interceptor.preHandle(request, response, this.handler)) {
                this.triggerAfterCompletion(request, response, (Exception)null);
                return false;
            }
        }

        return true;
    }

在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor , 并执⾏拦截器中的preHandle ⽅法,这样就会咱们前⾯定义的拦截器对应上了,如下图所示:
image.png
如果拦截器返回true, 整个发放就返回true, 继续执⾏后续逻辑处理;如果拦截器返回fasle, 则中断后续操作。
因此,可以得出结论,拦截器的实现主要是依赖 Servlet 或 Spring 执行流程来进行拦截和功能增强的。

  • 12
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值