首页 > 其他分享 >SpringBoot拦截器和动态代理有什么区别?

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

时间:2023-09-15 11:59:23浏览次数:42  
标签:拦截器 SpringBoot Spring request 代理 response processedRequest

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

1.拦截器

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

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

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

1.1 拦截器实现

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

  1. 创建一个普通的拦截器,实现 HandlerInterceptor 接口,并重写接口中的相关方法。
  2. 将上一步创建的拦截器加入到 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"); // 放行接口
    }
}

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

1.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 方法,这样就会咱们前面定义的拦截器对应上了,如下图所示:
image.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 中,拦截器和动态代理都是用来实现功能增强的,但二者没有任何关联关系,它的区别主要体现在使用范围、实现原理、加入时机和使用的难易程度都是不同的。

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

标签:拦截器,SpringBoot,Spring,request,代理,response,processedRequest
From: https://www.cnblogs.com/vipstone/p/17704643.html

相关文章

  • 如何在移动端猎豹浏览器中设置代理IP
    手机浏览器作为一款功能强大且广受欢迎的移动浏览器,提供了丰富的功能和个性化选项,其中包括设置动态ip地址的功能。通过设置动态ip地址,您可以改变您的网络访问路径,保护个人隐私,或者访问被地理限制的内容。接下来,我将为您介绍在手机浏览器中如何设置动态ip地址的步骤!步骤1:打开浏览器......
  • SpringBoot 自定义starter汇总
    1、SpringBootstarter机制SpringBoot中的starter是一种非常重要的机制,能够抛弃以前繁杂的配置,将其统一集成进starter,应用者只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。starter让我们摆脱了各种依赖库的处理,需要配置各种信息......
  • SpringBoot打成jar运行后无法读取resources里的文件
    开发一个word替换功能时,因替换其中的内容功能需要word模版,就把word_replace_tpl.docx模版文件放到resources下在开发环境中通过下面方法能读取word_replace_tpl.docx文件,但是打成jar包在linux下运行后无法找到文件了Filefile=ResourceUtils.getFile(ResourceUtils.CL......
  • springboot整合rocketMQ——消费者
    依赖<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/......
  • 基于springboot学院宿舍管理系统-计算机毕业设计源码+LW文档
    摘要随着信息技术的发展,管理系统越来越成熟,各种企事业单位使用各种类型的管理系统来提高工作效率,从而降低手工劳动的弊端。我国政府一直以来都非常重视教育事业的发展,随着学生人数增加,学校对宿舍学生信息管理也变的困难。因此,高校提出通过开发宿舍管理系统来优化管理方案,对宿舍信......
  • 基于springboot与mybatis的健身器材批发管理系统的设计与实现
    研究的背景意义随着互联网技术的发展,各种类型的管理系统深入到人们的工作学习中,不再是最初的资料查询、在线沟通等简单形式的应用。各种统计分析、物联网、人工智能、AI等技术越来越多,特别典型的就是网上商城。电子商城也分为多种类型,包括B2B、B2C、C2C等。还有我们常见的批发商城......
  • Socks5代理与网络安全:保护您的隐私与数据
    在今天数字化的世界中,隐私和网络安全已经成为至关重要的话题。Socks5代理作为一种强大的工具,不仅为用户提供了隐私保护,还在网络安全和爬虫领域发挥着关键作用。本文将深入探讨Socks5代理的工作原理、其在网络安全中的应用,以及如何在爬虫开发中充分利用它。1.Socks5代理简介Socks5......
  • 【Python爬虫】python打印本地代理
    在进行网络爬虫时,使用代理是非常重要的。因为爬虫经常会被网站封IP,而代理可以隐藏你的真实IP地址,让你可以更不受限制地爬取数据。本文将介绍如何在Python中打印代理,包括代理IP的使用以及代码案例。代理IP的使用在使用代理IP时,需要注意一些事项,包括:获取代理IP你可以在一......
  • 代理IP和Socks5代理在跨界电商中的关键作用
    随着跨界电商的兴起,代理IP和Socks5代理成为了技术领域的关键工具。本文将深入探讨它们在跨界电商、爬虫技术和出海战略中的关键作用,以及如何最大程度地利用它们来支持企业的全球扩张。引言简要介绍跨界电商的崛起和全球化趋势。提出代理IP和Socks5代理在这一背景下的重要性。代理IP......
  • SpringBoot 3.0最低版本要求的JDK 17,这几个新特性不能不知道
    最近,有很多人在传说SpringBoot要出3.0的版本了,并且宣布不再支持Java8,最低要求是Java17了。其实,早在2021年9月份,关于SpringFramework6.0的消息出来的时候,Spring官方就已经明确了不会向下兼容,最低的JDK版本是JDK17。2022年,SpringFramework6.0和SpringBoot3.0都......