利用 AOP 的思想对一些特定的功能进行统一的处理, 包括
- 使用拦截器实现用户登录权限的统一校验
- 统一异常的处理
- 统一数据格式的返回
🔎小插曲(通过一级路由调用多种方法)
通过一级路由调用多种方法, 需要保证这些方法的请求类型各不相同(GET, POST, PUT…)
🔎使用拦截器实现用户登录权限的统一校验
使用 Spring AOP 可以实现统一拦截, 但 Spring AOP 的使用较为复杂, 包括
- 定义拦截的规则(切点表达式)较为复杂
- 在切面类中拿到 HttpSession 较为复杂
于是 Pivotal 公司针对上述情况开发出 Spring 拦截器
Spring 拦截器的使用🍂
- 自定义拦截器
- 实现 HandlerInterceptor 接口
- 重写 preHandler 方法, 在方法中编写业务代码
- 将自定义拦截器添加至配置文件中, 并设置拦截的规则
自定义拦截器
将自定义拦截器添加至配置文件中
- addPathPatterns, 表示需要拦截的 URL
(/*
表示一级路由,/**
表示所有的请求) - excludePathPatterns, 表示不需要拦截的 URL
拦截器的实现原理
调用方法时, 发现 DispatcherServlet
Dispatcher → 调度器
所有方法都会执行 DispatcherServlet 中的 doDispatch—调度方法
拦截器(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; } 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); } } }
当返回结果为 false 时, 拦截器将不会进行后续操作
applyPreHandle 的源码🌰
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) { 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, 并执行 HandlerInterceptor 中的 preHandle 方法
即自定义拦截器中重写的 preHandle 方法
统⼀访问前缀添加
统⼀访问前缀的添加有 2 种方式
- 重写 configurePathMatch( )
- 在配置文件中添加
重写 configurePathMatch( ) 🍂
@Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.addPathPrefix("/bibubibu", c -> true); }
/bibubibu
, 要添加的统一前缀c -> true
, 所有请求均添加统一前缀
在配置文件中添加🍂
server: servlet: context-path: /bibubibu