在 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 方法,这样就会咱们前面定义的拦截器对应上了,如下图所示:



此时用户登录权限的验证方法就会执行,这就是拦截器的执行过程。

因此,可以得出结论,拦截器的实现主要是依赖 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拦截器和动态代理有什么区别?的更多相关文章

  1. Springboot拦截器实现IP黑名单

    Springboot拦截器实现IP黑名单 一·业务场景和需要实现的功能 以redis作为IP存储地址实现. 业务场景:针对秒杀活动或者常规电商业务场景等,防止恶意脚本不停的刷接口. 实现功能:写一个拦 ...

  2. Java结合SpringBoot拦截器实现简单的登录认证模块

    Java结合SpringBoot拦截器实现简单的登录认证模块 之前在做项目时需要实现一个简单的登录认证的功能,就寻思着使用Spring Boot的拦截器来实现,在此记录一下我的整个实现过程,源码见文章 ...

  3. SpringBoot拦截器中Bean无法注入(转)

    问题 这两天遇到SpringBoot拦截器中Bean无法注入问题.下面介绍我的思考过程和解决过程: 1.由于其他bean在service,controller层注入一点问题也没有,开始根本没意识到Be ...

  4. 【SpringBoot】SpringBoot拦截器实战和 Servlet3.0自定义Filter、Listener

    =================6.SpringBoot拦截器实战和 Servlet3.0自定义Filter.Listener ============ 1.深入SpringBoot2.x过滤器Fi ...

  5. springBoot拦截器的使用

    拦截器是动态拦截Action调用的对象.它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行.同时也是提供了一种可以提取action中可 ...

  6. SpringBoot拦截器中无法注入bean的解决方法

    SpringBoot拦截器中无法注入bean的解决方法 在使用springboot的拦截器时,有时候希望在拦截器中注入bean方便使用 但是如果直接注入会发现无法注入而报空指针异常 解决方法: 在注册 ...

  7. Springboot拦截器未起作用

    之前遇到要使用springboot拦截器却始终未生效的状况,查了网上的博客,大抵都是@Component,@Configuration注解未加,或是使用@ComponentScan增加包扫描,但是尝试 ...

  8. SpringBoot拦截器中service或者redis注入为空的问题

    原文:https://my.oschina.net/u/1790105/blog/1490098 这两天遇到SpringBoot拦截器中Bean无法注入问题.下面介绍我的思考过程和解决过程: 1.由于 ...

  9. springboot + 拦截器 + 注解 实现自定义权限验证

    springboot + 拦截器 + 注解 实现自定义权限验证最近用到一种前端模板技术:jtwig,在权限控制上没有用springSecurity.因此用拦截器和注解结合实现了权限控制. 1.1 定义 ...

  10. Springboot 拦截器配置(登录拦截)

    Springboot 拦截器配置(登录拦截) 注意这里环境为springboot为2.1版本 1.编写拦截器实现类,实现接口   HandlerInterceptor, 重写里面需要的三个比较常用的方 ...

随机推荐

  1. 华为云新一代分布式数据库GaussDB,给世界一个更优选择

    摘要:与伙伴一起,共建繁荣开放的GaussDB数据库新生态. 本文分享自华为云社区<华为云新一代分布式数据库GaussDB,给世界一个更优选择>,作者:华为云头条. 6月7日,在华为全球智 ...

  2. CKS 考试题整理 (18)-TLS 安全配置

    Task 通过 TLS 加强 kube-apiserver 安全配置,要求 kube-apiserver 除了 VersionTLS13 及以上的版本可以使用,其他版本都不允许使用. 密码套件(Cip ...

  3. LLE算法在自然语言处理中的应用:从文本到实体识别和关系抽取

    目录 文章介绍: 自然语言处理(Natural Language Processing,NLP)是人工智能领域的重要分支,它研究如何将人类语言转化为计算机可理解的格式.NLP的应用非常广泛,从语言翻译 ...

  4. 深度解析SpringBoot内嵌Web容器

    你好,我是刘牌! 前言 今天分享一个SpringBoot的内嵌Web容器,在SpringBoot还没有出现时,我们使用Java开发了Web项目,需要将其部署到Tomcat下面,需要配置很多xml文件, ...

  5. 手机号码吉利数PHP检测算法代码,超级实用

    手机号码吉利数理预测解读:将手机号码末尾的四个数字,先除以八十,再减去整数部分,只使用剩下的小数(小数点反面的数字)乘以八十,然后将所得结果,对表查阅,就知道吉凶.(换句话说就是余数)例如:手机尾号是 ...

  6. ThinkPHP6.0 链式SQL语句

    ThinkPHP6.0 链式SQL语句 查询单个数据 $user = Db::query('select * from `user`'); $user=Db::table('user')->wh ...

  7. C++面试八股文:技术勘误

    不知不觉,<C++面试八股文>已经更新30篇了,这是我第一次写技术博客,由于个人能力有限,出现了不少纰漏,在此向各位读者小伙伴们致歉. 为了不误导更多的小伙伴,以后会不定期的出勘误文章,请 ...

  8. 让IIS支持.NET Web Api PUT和DELETE请求

    前言 有很长一段时间没有使用过IIS来托管应用了,今天用IIS来托管一个比较老的.NET Fx4.6的项目.发布到线上后居然一直调用不同本地却一直是正常的,关键是POST和GET请求都是正常的,只有P ...

  9. 10/28-29_String类_SrtingBuffer类_Interger类_笔记

    API:应用程序编程接口 String功能 public String replace (char oldchar ,char newchar); //符串中某一字符被一新字符替换 public St ...

  10. .Net Core控制台程序读取Json配置文件

    1 添加依赖(可以直接通过nuget包安装 Microsoft.Extensions.Configuration.Json 这个会依赖其他两个会自动安装依赖的) Microsoft.Extension ...