SpringBoot拦截器和动态代理有什么区别?
在 Spring Boot 中,拦截器和动态代理都是用来实现功能增强的,所以在很多时候,有人会认为拦截器的底层是通过动态代理实现的,所以本文就来盘点一下他们两的区别,以及拦截器的底层实现。
1.拦截器
拦截器(Interceptor)准确来说在 Spring MVC 中的一个很重要的组件,用于拦截 Controller 的请求。
它的主要作用有以下几个:
- 权限验证:验证用户是否登录、是否有权限访问某个接口。
- 日志记录:记录请求信息的日志,如请求参数,响应信息等。
- 性能监控:监控系统的运行性能,如慢查询接口等。
- 通用行为:插入一些通用的行为,比如开发环境忽略某些请求。
典型的使用场景是身份认证、授权检查、请求日志记录等。
1.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"); // 放行接口
}
}
这样我们的拦截器就实现完了。
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 中的,动态代理的实现手段有以下两种:
- 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 中,拦截器和动态代理都是用来实现功能增强的,但二者没有任何关联关系,它的区别主要体现在使用范围、实现原理、加入时机和使用的难易程度都是不同的。
本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。
SpringBoot拦截器和动态代理有什么区别?的更多相关文章
- Springboot拦截器实现IP黑名单
Springboot拦截器实现IP黑名单 一·业务场景和需要实现的功能 以redis作为IP存储地址实现. 业务场景:针对秒杀活动或者常规电商业务场景等,防止恶意脚本不停的刷接口. 实现功能:写一个拦 ...
- Java结合SpringBoot拦截器实现简单的登录认证模块
Java结合SpringBoot拦截器实现简单的登录认证模块 之前在做项目时需要实现一个简单的登录认证的功能,就寻思着使用Spring Boot的拦截器来实现,在此记录一下我的整个实现过程,源码见文章 ...
- SpringBoot拦截器中Bean无法注入(转)
问题 这两天遇到SpringBoot拦截器中Bean无法注入问题.下面介绍我的思考过程和解决过程: 1.由于其他bean在service,controller层注入一点问题也没有,开始根本没意识到Be ...
- 【SpringBoot】SpringBoot拦截器实战和 Servlet3.0自定义Filter、Listener
=================6.SpringBoot拦截器实战和 Servlet3.0自定义Filter.Listener ============ 1.深入SpringBoot2.x过滤器Fi ...
- springBoot拦截器的使用
拦截器是动态拦截Action调用的对象.它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行.同时也是提供了一种可以提取action中可 ...
- SpringBoot拦截器中无法注入bean的解决方法
SpringBoot拦截器中无法注入bean的解决方法 在使用springboot的拦截器时,有时候希望在拦截器中注入bean方便使用 但是如果直接注入会发现无法注入而报空指针异常 解决方法: 在注册 ...
- Springboot拦截器未起作用
之前遇到要使用springboot拦截器却始终未生效的状况,查了网上的博客,大抵都是@Component,@Configuration注解未加,或是使用@ComponentScan增加包扫描,但是尝试 ...
- SpringBoot拦截器中service或者redis注入为空的问题
原文:https://my.oschina.net/u/1790105/blog/1490098 这两天遇到SpringBoot拦截器中Bean无法注入问题.下面介绍我的思考过程和解决过程: 1.由于 ...
- springboot + 拦截器 + 注解 实现自定义权限验证
springboot + 拦截器 + 注解 实现自定义权限验证最近用到一种前端模板技术:jtwig,在权限控制上没有用springSecurity.因此用拦截器和注解结合实现了权限控制. 1.1 定义 ...
- Springboot 拦截器配置(登录拦截)
Springboot 拦截器配置(登录拦截) 注意这里环境为springboot为2.1版本 1.编写拦截器实现类,实现接口 HandlerInterceptor, 重写里面需要的三个比较常用的方 ...
随机推荐
- 生物信息学 Python 入门之源码安装
编程,作为生物信息学的一个基础性技能,是任何一个生信工程师都无法绕开话题.也许有些人还在纠结 Perl 和 Python 到底应该学习哪一个,但作为目前最火最流行的编程语言 Python 还是非常值得 ...
- 关于Pod中进程在节点中的研究
最近研究OpenShift virtulization, 各种Pod对KVM进程的封装,引发了Pod中进程到底在Node中是什么表现形势的好奇,因为对基础知识的不扎实,还是希望找个环境能仔细看看,建立 ...
- Handler机制实现原理总结
Handler一般用于线程间通信,如常用的子线程使用handler让主线程更新UI.那么这是怎么实现的呢? 我们先把这个大问题分解成多个小问题: post();postDelayed();sendMe ...
- [QML]事无巨细开始实践QML开发(一)什么是QML,为什么学习QML,先写一个简单的页面
[QML]从零开始QML开发(一)什么是QML,为什么学习QML,先写一个简单的页面 QML开发和QWidget开发的区别 QML(Qt Meta-Object Language)是Qt提供的一种声明 ...
- TortoiseGit使用Cherry Pick遇到的问题及解决方案
TortoiseGit的Cherry Pick 比如从master pick某一个commit 记录到其它分支(release) pick的操作方法:切到分支,点击 show log,然后在log d ...
- 从零开始整SpringBoot-搭建一个企业级项目
创建Springboot项目 在企业里的项目多数是分Modules的,更多是通过物理方式隔离层之间的职责. 首先按照原有新建SpringBoot项目创建,填好相关信息后,选择Java 8其他不用选. ...
- C++ 核心指南之资源管理(下)—— 智能指针最佳实践
C++ 核心指南(C++ Core Guidelines)是由 Bjarne Stroustrup.Herb Sutter 等顶尖 C+ 专家创建的一份 C++ 指南.规则及最佳实践.旨在帮助大家正确 ...
- LaTeX 的学习笔记
摘自我的洛谷博客 该文章被打开的次数(包括洛谷平台): \(\LaTeX\) 中所有命令都以\开头,后面可以跟一个花括号,代表参数. \documentclass{} 指定了文章类型,有 articl ...
- 即构发布 LCEP 产品「RoomKit」 ,实现房间内0代码接入
2021年2月5日,即构科技正式发布全新形态「低代码互动平台」(Low-code Engagement Platform,简称LCEP)产品「RoomKit」. RoomKit定位为低代码互动平台(L ...
- CocosCreator基础
跳转到底部 目录 CocosCreator项目结构: 资源文件夹(assets) 资源库(library) 本地设置(local) 项目设置(settings) project.json 构建目标(b ...