• SpringBoot拦截器和动态代理有什么区别?


    3a027c25fe2223a4b689b6a8c5f2d9b7.png

    作者 | 磊哥

    来源 | Java中文社群(ID:javacn666)

    转载请联系授权(微信ID:GG_Stone)

    在 Spring Boot 中,拦截器和动态代理都是用来实现功能增强的,所以在很多时候,有人会认为拦截器的底层是通过动态代理实现的,所以本文就来盘点一下他们两的区别,以及拦截器的底层实现。

    1.拦截器

    拦截器(Interceptor)准确来说在 Spring MVC 中的一个很重要的组件,用于拦截 Controller 的请求。它的主要作用有以下几个:

    1. 权限验证:验证用户是否登录、是否有权限访问某个接口。

    2. 日志记录:记录请求信息的日志,如请求参数,响应信息等。

    3. 性能监控:监控系统的运行性能,如慢查询接口等。

    4. 通用行为:插入一些通用的行为,比如开发环境忽略某些请求。

    典型的使用场景是身份认证、授权检查、请求日志记录等。

    1.1 拦截器实现

    在 Spring Boot 中拦截器的实现分为两步:

    1. 创建一个普通的拦截器,实现 HandlerInterceptor 接口,并重写接口中的相关方法。

    2. 将上一步创建的拦截器加入到 Spring Boot 的配置文件中,并配置拦截规则。

    具体实现如下。

    ① 实现自定义拦截器
    1. import org.springframework.stereotype.Component;
    2. import org.springframework.web.servlet.HandlerInterceptor;
    3. import org.springframework.web.servlet.ModelAndView;
    4. import javax.servlet.http.HttpServletRequest;
    5. import javax.servlet.http.HttpServletResponse;
    6. @Component
    7. public class TestInterceptor implements HandlerInterceptor {
    8.     @Override
    9.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    10.         System.out.println("拦截器:执行 preHandle 方法。");
    11.         return true;
    12.     }
    13.     @Override
    14.     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    15.         System.out.println("拦截器:执行 postHandle 方法。");
    16.     }
    17.     @Override
    18.     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    19.         System.out.println("拦截器:执行 afterCompletion 方法。");
    20.     }
    21. }

    其中:

    • boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handle):在请求方法执行前被调用,也就是调用目标方法之前被调用。比如我们在操作数据之前先要验证用户的登录信息,就可以在此方法中实现,如果验证成功则返回 true,继续执行数据操作业务;否则就返回 false,后续操作数据的业务就不会被执行了。

    • void postHandle(HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView):调用请求方法之后执行,但它会在 DispatcherServlet 进行渲染视图之前被执行。

    • void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex):会在整个请求结束之后再执行,也就是在 DispatcherServlet 渲染了对应的视图之后再执行。

    ② 配置拦截规则

    然后,我们再将上面的拦截器注入到项目配置文件中,并设置相应拦截规则,具体实现代码如下:

    1. import org.springframework.beans.factory.annotation.Autowired;
    2. import org.springframework.context.annotation.Configuration;
    3. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    4. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    5. @Configuration
    6. public class AppConfig implements WebMvcConfigurer {
    7.     // 注入拦截器
    8.     @Autowired
    9.     private TestInterceptor testInterceptor;
    10.     @Override
    11.     public void addInterceptors(InterceptorRegistry registry) {
    12.         registry.addInterceptor(testInterceptor) // 添加拦截器
    13.                 .addPathPatterns("/**"); // 拦截所有地址
    14.           .excludePathPatterns("/login"); // 放行接口
    15.     }
    16. }

    这样我们的拦截器就实现完了。

    1.2 拦截器实现原理

    Spring Boot 拦截器是基于 Java 的 Servlet 规范实现的,通过实现 HandlerInterceptor 接口来实现拦截器功能。

    在 Spring Boot 框架的执行流程中,拦截器被注册在 DispatcherServlet 的 doDispatch() 方法中,该方法是 Spring Boot 框架的核心方法,用于处理请求和响应。

    程序每次执行时都会调用 doDispatch() 方法时,并验证拦截器(链),之后再根据拦截器返回的结果,进行下一步的处理。如果返回的是 true,那么继续调用目标方法,反之则会直接返回验证失败给前端。

    doDispatch  源码实现如下:

    1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    2.     HttpServletRequest processedRequest = request;
    3.     HandlerExecutionChain mappedHandler = null;
    4.     boolean multipartRequestParsed = false;
    5.     WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    6.     try {
    7.         try {
    8.             ModelAndView mv = null;
    9.             Object dispatchException = null;
    10.             try {
    11.                 processedRequest = this.checkMultipart(request);
    12.                 multipartRequestParsed = processedRequest != request;
    13.                 mappedHandler = this.getHandler(processedRequest);
    14.                 if (mappedHandler == null) {
    15.                     this.noHandlerFound(processedRequest, response);
    16.                     return;
    17.                 }
    18.                 HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
    19.                 String method = request.getMethod();
    20.                 boolean isGet = HttpMethod.GET.matches(method);
    21.                 if (isGet || HttpMethod.HEAD.matches(method)) {
    22.                     long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    23.                     if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
    24.                         return;
    25.                     }
    26.                 }
    27.                 // 调用预处理【重点】
    28.                 if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    29.                     return;
    30.                 }
    31.                 // 执行 Controller 中的业务
    32.                 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    33.                 if (asyncManager.isConcurrentHandlingStarted()) {
    34.                     return;
    35.                 }
    36.                 this.applyDefaultViewName(processedRequest, mv);
    37.                 mappedHandler.applyPostHandle(processedRequest, response, mv);
    38.             } catch (Exception var20) {
    39.                 dispatchException = var20;
    40.             } catch (Throwable var21) {
    41.                 dispatchException = new NestedServletException("Handler dispatch failed", var21);
    42.             }
    43.             this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
    44.         } catch (Exception var22) {
    45.             this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
    46.         } catch (Throwable var23) {
    47.             this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
    48.         }
    49.     } finally {
    50.         if (asyncManager.isConcurrentHandlingStarted()) {
    51.             if (mappedHandler != null) {
    52.                 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    53.             }
    54.         } else if (multipartRequestParsed) {
    55.             this.cleanupMultipart(processedRequest);
    56.         }
    57.     }
    58. }

    从上述源码可以看出在开始执行 Controller 之前,会先调用 预处理方法 applyPreHandle,而 applyPreHandle 方法的实现源码如下:

    1. boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    2.     for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
    3.         // 获取项目中使用的拦截器 HandlerInterceptor
    4.         HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
    5.         if (!interceptor.preHandle(request, response, this.handler)) {
    6.             this.triggerAfterCompletion(request, response, (Exception)null);
    7.             return false;
    8.         }
    9.     }
    10.     return true;
    11. }

    从上述源码可以看出,在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执行拦截器中的 preHandle 方法,这样就会咱们前面定义的拦截器对应上了,如下图所示:883fe50788313f47b16af67eb3652a2f.png此时用户登录权限的验证方法就会执行,这就是拦截器的执行过程。因此,可以得出结论,拦截器的实现主要是依赖 Servlet 或 Spring 执行流程来进行拦截和功能增强的。

    2.动态代理

    动态代理是一种设计模式,它是指在运行时提供代理对象,来扩展目标对象的功能。在 Spring 中的,动态代理的实现手段有以下两种:

    1. JDK 动态代理:通过反射机制生成代理对象,目标对象必须实现接口。

    2. CGLIB 动态代理:通过生成目标类的子类来实现代理,不要求目标对象实现接口。

    动态代理的主要作用包括:

    1. 扩展目标对象的功能:如添加日志、验证参数等。

    2. 控制目标对象的访问:如进行权限控制。

    3. 延迟加载目标对象:在需要时才实例化目标对象。

    4. 远程代理:将请求转发到远程的目标对象上。

    JDK 动态代理和 CGLIB 的区别详见:www.javacn.site/interview/spring/jdk_cglib.html

    3.拦截器 VS 动态代理

    因此,我们可以得出结论,拦截器和动态代理虽然都是用来实现功能增强的,但二者完全不同,他们的主要区别体现在以下几点:

    1. 使用范围不同:拦截器通常用于 Spring MVC 中,主要用于拦截 Controller 请求。动态代理可以使用在 Bean 中,主要用于提供 bean 的代理对象,实现对 bean 方法的拦截。

    2. 实现原理不同:拦截器是通过 HandlerInterceptor 接口来实现的,主要是通过 afterCompletion、postHandle、preHandle 这三个方法在请求前后进行拦截处理。动态代理主要有 JDK 动态代理和 CGLIB 动态代理,JDK 通过反射生成代理类;CGLIB 通过生成被代理类的子类来实现代理。

    3. 加入时机不同:拦截器是在运行阶段动态加入的;动态代理是在编译期或运行期生成的代理类。

    4. 使用难易程度不同:拦截器相对简单,通过实现接口即可使用。动态代理稍微复杂,需要了解动态代理的实现原理,然后通过相应的 api 实现。

    小结

    在 Spring Boot 中,拦截器和动态代理都是用来实现功能增强的,但二者没有任何关联关系,它的区别主要体现在使用范围、实现原理、加入时机和使用的难易程度都是不同的。


    说件大事

    磊哥一直在做的事,为了让大家找到更好的工作,所以有着13 年工作经验的我,开发了一门《Java 面试突击课》。

    整个课程以包含 15 万字的图文课件 + 3000 多分钟的视频课(支持永久观看),带领大家一起高效、系统的复习 Java 面试知识。整个课程从 Java 基础到微服务 Spring Cloud 应用尽有,包含以下 16 个大的模块:

    0a85566a930a91ebda0a8dff7eb1b595.png

    收费只要 1200 元,带你高效的搞定面试问题,学完之后可以搞定目前市面上的绝大部分面试,锁定高薪 Offer。

    报名只需加我微信:GG_Stone【备注:面试课】

    e0222d22adf401552c308bce06705239.png

  • 相关阅读:
    基于 Keras 的图像分类器
    【 OpenGauss源码学习 —— 列存储(update)】
    hive零基础入门
    重学Elasticsearch第5章 : 过滤查询、聚合查询
    Python数据透视表
    ubunbtu下基于c++实现MQTT客户端通信
    TCP Window Full & TCP Zero Window
    算法 LeetCode 题解 | 两个数组的交集
    virtualbox 安装win7坑点汇总
    GBASE 8A v953报错集锦48--远程 rmt 导出 dual 表数据没有落到本地而是落到了集群节点上
  • 原文地址:https://blog.csdn.net/sufu1065/article/details/132913704