
作者 | 磊哥
来源 | Java中文社群(ID:javacn666)
转载请联系授权(微信ID:GG_Stone)
在 Spring Boot 中,拦截器和动态代理都是用来实现功能增强的,所以在很多时候,有人会认为拦截器的底层是通过动态代理实现的,所以本文就来盘点一下他们两的区别,以及拦截器的底层实现。
拦截器(Interceptor)准确来说在 Spring MVC 中的一个很重要的组件,用于拦截 Controller 的请求。它的主要作用有以下几个:
权限验证:验证用户是否登录、是否有权限访问某个接口。
日志记录:记录请求信息的日志,如请求参数,响应信息等。
性能监控:监控系统的运行性能,如慢查询接口等。
通用行为:插入一些通用的行为,比如开发环境忽略某些请求。
典型的使用场景是身份认证、授权检查、请求日志记录等。
在 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"); // 放行接口
- }
- }
这样我们的拦截器就实现完了。
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 执行流程来进行拦截和功能增强的。
动态代理是一种设计模式,它是指在运行时提供代理对象,来扩展目标对象的功能。在 Spring 中的,动态代理的实现手段有以下两种:
JDK 动态代理:通过反射机制生成代理对象,目标对象必须实现接口。
CGLIB 动态代理:通过生成目标类的子类来实现代理,不要求目标对象实现接口。
动态代理的主要作用包括:
扩展目标对象的功能:如添加日志、验证参数等。
控制目标对象的访问:如进行权限控制。
延迟加载目标对象:在需要时才实例化目标对象。
远程代理:将请求转发到远程的目标对象上。
“JDK 动态代理和 CGLIB 的区别详见:www.javacn.site/interview/spring/jdk_cglib.html
”
因此,我们可以得出结论,拦截器和动态代理虽然都是用来实现功能增强的,但二者完全不同,他们的主要区别体现在以下几点:
使用范围不同:拦截器通常用于 Spring MVC 中,主要用于拦截 Controller 请求。动态代理可以使用在 Bean 中,主要用于提供 bean 的代理对象,实现对 bean 方法的拦截。
实现原理不同:拦截器是通过 HandlerInterceptor 接口来实现的,主要是通过 afterCompletion、postHandle、preHandle 这三个方法在请求前后进行拦截处理。动态代理主要有 JDK 动态代理和 CGLIB 动态代理,JDK 通过反射生成代理类;CGLIB 通过生成被代理类的子类来实现代理。
加入时机不同:拦截器是在运行阶段动态加入的;动态代理是在编译期或运行期生成的代理类。
使用难易程度不同:拦截器相对简单,通过实现接口即可使用。动态代理稍微复杂,需要了解动态代理的实现原理,然后通过相应的 api 实现。
在 Spring Boot 中,拦截器和动态代理都是用来实现功能增强的,但二者没有任何关联关系,它的区别主要体现在使用范围、实现原理、加入时机和使用的难易程度都是不同的。
磊哥一直在做的事,为了让大家找到更好的工作,所以有着13 年工作经验的我,开发了一门《Java 面试突击课》。
整个课程以包含 15 万字的图文课件 + 3000 多分钟的视频课(支持永久观看),带领大家一起高效、系统的复习 Java 面试知识。整个课程从 Java 基础到微服务 Spring Cloud 应用尽有,包含以下 16 个大的模块:

收费只要 1200 元,带你高效的搞定面试问题,学完之后可以搞定目前市面上的绝大部分面试,锁定高薪 Offer。
报名只需加我微信:GG_Stone【备注:面试课】
