您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

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

时间:2023-09-15 13:10:31  来源:微信公众号  作者:Java中文社群

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

1、拦截器

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

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

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

(1)拦截器实现

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

  • 创建一个普通的拦截器,实现 HandlerInterceptor 接口,并重写接口中的相关方法。
  • 将上一步创建的拦截器加入到 Spring Boot 的配置文件中,并配置拦截规则。

具体实现如下。

实现自定义拦截器

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

@Component
public class TestInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截器:执行 preHandle 方法。");
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("拦截器:执行 postHandle 方法。");
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("拦截器:执行 afterCompletion 方法。");
    }
}

其中:

  • 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 渲染了对应的视图之后再执行。

配置拦截规则

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

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.WebMvcConfigurer;

@Configuration
public class AppConfig implements WebMvcConfigurer {

    // 注入拦截器
    @Autowired
    private TestInterceptor testInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(testInterceptor) // 添加拦截器
                .addPathPatterns("/**"); // 拦截所有地址
          .excludePathPatterns("/login"); // 放行接口
    }
}

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

(2)拦截器实现原理

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

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

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

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;
            Object dispatchException = null;

            try {
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }

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

                // 调用预处理【重点】
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 执行 Controller 中的业务
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                this.applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var20) {
                dispatchException = var20;
            } catch (Throwable var21) {
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }

            this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
        } catch (Exception var22) {
            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 方法,这样就会咱们前面定义的拦截器对应上了,如下图所示:

此时用户登录权限的验证方法就会执行,这就是拦截器的执行过程。因此,可以得出结论,拦截器的实现主要是依赖 Servlet 或 Spring 执行流程来进行拦截和功能增强的。

2、动态代理

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

  • JDK 动态代理:通过反射机制生成代理对象,目标对象必须实现接口。
  • CGLIB 动态代理:通过生成目标类的子类来实现代理,不要求目标对象实现接口。

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

  • 扩展目标对象的功能:如添加日志、验证参数等。
  • 控制目标对象的访问:如进行权限控制。
  • 延迟加载目标对象:在需要时才实例化目标对象。
  • 远程代理:将请求转发到远程的目标对象上。

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

3、拦截器 VS 动态代理

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

  • 使用范围不同:拦截器通常用于 Spring MVC 中,主要用于拦截 Controller 请求。动态代理可以使用在 Bean 中,主要用于提供 bean 的代理对象,实现对 bean 方法的拦截。
  • 实现原理不同:拦截器是通过 HandlerInterceptor 接口来实现的,主要是通过 afterCompletion、postHandle、preHandle 这三个方法在请求前后进行拦截处理。动态代理主要有 JDK 动态代理和 CGLIB 动态代理,JDK 通过反射生成代理类;CGLIB 通过生成被代理类的子类来实现代理。
  • 加入时机不同:拦截器是在运行阶段动态加入的;动态代理是在编译期或运行期生成的代理类。
  • 使用难易程度不同:拦截器相对简单,通过实现接口即可使用。动态代理稍微复杂,需要了解动态代理的实现原理,然后通过相应的 api 实现。

小结

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



Tags:SpringBoot   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除。
▌相关推荐
在 Spring Boot 中,拦截器和动态代理都是用来实现功能增强的,所以在很多时候,有人会认为拦截器的底层是通过动态代理实现的,所以本文就来盘点一下他们两的区别,以及拦截器的底层...【详细内容】
2023-09-15  Tags: SpringBoot  点击:(0)  评论:(0)  加入收藏
RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。我之前的HTTP开发是用apache的Htt...【详细内容】
2023-09-14  Tags: SpringBoot  点击:(4)  评论:(0)  加入收藏
开发环境:JDK1.8+SpringBoot2.4.12+Oracle这里我们假设要使用两个数据源分别为:master和slave。 pom.xml 依赖包<dependencies> <dependency> <groupId>org.springframe...【详细内容】
2023-09-07  Tags: SpringBoot  点击:(11)  评论:(0)  加入收藏
一、前言当我们在企业开发时,数据库连接池是一个至关重要的组成部分。一个优秀的数据库连接池可以显著提高应用程序的性能和可伸缩性。在Java生态系统中,有很多连接池:Druid、H...【详细内容】
2023-09-01  Tags: SpringBoot  点击:(28)  评论:(0)  加入收藏
当我们的应用程序需要频繁地读取和写入数据时,为了提高应用程序的性能,我们通常会使用缓存技术。Spring Boot 提供了一种简单而强大的缓存框架,它可以轻松地将数据缓存到 Redis...【详细内容】
2023-08-31  Tags: SpringBoot  点击:(27)  评论:(0)  加入收藏
一、前言常见的设计模式有23种,我们不得不提到模板方法设计模式,这是一种在软件开发中广泛使用的行为型设计模式之一。 它为我们提供了一种优雅的方式来定义算法的结构,并将算...【详细内容】
2023-08-29  Tags: SpringBoot  点击:(35)  评论:(0)  加入收藏
我们将分布式锁基于缓存扩展了一版,也就是说本starter即有分布式缓存功能,又有分布式锁功能。而注解版的分布式锁能够解决大多数场景的并核问题,小粒度的Lock锁方式补全其他场...【详细内容】
2023-08-28  Tags: SpringBoot  点击:(40)  评论:(0)  加入收藏
环境:springboot2.5.12经常会遇到在项目中调用第三方接口的情景,你是如何调用的呢?同步?异步?场景:假设下单业务流程如下步骤:1、查询用户信息。2、查询库存信息。3、查询活动信息...【详细内容】
2023-08-23  Tags: SpringBoot  点击:(26)  评论:(0)  加入收藏
Spring Boot与微服务微服务架构是当下构建互联网应用的主流架构。在Spring家族中,专门有着用于构建微服务架构的Spring Cloud框架。而Spring Cloud框架本身则是构建在Spring...【详细内容】
2023-08-23  Tags: SpringBoot  点击:(20)  评论:(0)  加入收藏
目前,网上一搜SpringBoot环境下载文件。有多种实现方式,大概率出来的会是基础版的,基础版的有几个坑,我这里分别将基础版以及基础版会出现的问题,从而引申各种解决方法。基础版话...【详细内容】
2023-08-22  Tags: SpringBoot  点击:(21)  评论:(0)  加入收藏
▌简易百科推荐
在 Spring Boot 中,拦截器和动态代理都是用来实现功能增强的,所以在很多时候,有人会认为拦截器的底层是通过动态代理实现的,所以本文就来盘点一下他们两的区别,以及拦截器的底层...【详细内容】
2023-09-15  Java中文社群  微信公众号  Tags:SpringBoot   点击:(0)  评论:(0)  加入收藏
1 判断类型注入的属性判断其类型: Optional ObjectFactory ObjectProvider javax.inject.Providerpublic class DefaultListableBeanFactory extends AbstractAutowireCapabl...【详细内容】
2023-09-14  Spring全家桶实战案例源码    Tags:Spring   点击:(1)  评论:(0)  加入收藏
RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。我之前的HTTP开发是用apache的Htt...【详细内容】
2023-09-14  PlayInJava  今日头条  Tags:Springboot   点击:(4)  评论:(0)  加入收藏
本文目录- 说在前面- 喜马拉雅自研亿级API网关技术实践- 1、第1版:Tomcat NIO+Async Servlet- 2、第2版:Netty+全异步 - 2.1 接入层 - 2.2 业务逻辑层 - 2.3 服务调用层...【详细内容】
2023-09-14  技术老男孩  微信公众号  Tags:架构设计   点击:(0)  评论:(0)  加入收藏
1、含义不同微服务架构是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中。分布式系统是若干独立计算机的集合,这些计算机对用户来说就像单个相...【详细内容】
2023-09-13  AI改变你我  微信公众号  Tags:架构   点击:(4)  评论:(0)  加入收藏
大家好,我是不才陈某~在排查线上异常的过程中,查询日志总是必不可缺的一部分。现今大多采用的微服务架构,日志被分散在不同的机器上,使得日志的查询变得异常困难。工欲善其事,必...【详细内容】
2023-09-13  码猿技术专栏  微信公众号  Tags:Spring Boot   点击:(5)  评论:(0)  加入收藏
分布式锁是一种用于保证分布式系统中多个进程或线程同步访问共享资源的技术。同时它又是面试中的常见问题,所以我们本文就重点来看分布式锁的具体实现(含实现代码)。在分布式系...【详细内容】
2023-09-13    Java中文社群  Tags:分布式锁   点击:(3)  评论:(0)  加入收藏
作者 | David Linthicum策划 | 言征 从数据可用性、安全性到模型选择和监控,生成式AI的加入便意味着要重新审视云架构。 所以,如果在构建一个云架构同时也在设计生成式AI驱动...【详细内容】
2023-09-13  David Linthicum    Tags:云架构   点击:(3)  评论:(0)  加入收藏
本文主要介绍BSTS模型原理以及CausalImpact对模型的代码实现,旨在面对一些具有特定周期性特点的数据时,更精准科学地进行因果效应值的估计。作者简介Yiwen,携程数据分析师,专注...【详细内容】
2023-09-12  携程技术    Tags:结构模型   点击:(7)  评论:(0)  加入收藏
人工智能(AI)和机器学习(Machine Learning)的崛起正在深刻地改变着各行各业。随着数据量的不断增大和计算能力的提升,利用AI和机器学习来做出智能决策已经成为企业和组织的关键战...【详细内容】
2023-09-11  高级互联网架构    Tags:架构   点击:(8)  评论:(0)  加入收藏
站内最新
站内热门
站内头条