SpringBoot—统一功能处理
- 🔎小插曲(通过一级路由调用多种方法)
- 🔎使用拦截器实现用户登录权限的统一校验
- 自定义拦截器
- 将自定义拦截器添加至配置文件中
- 拦截器的实现原理
- 统⼀访问前缀添加
- 🔎统一异常的处理
- 🔎统一数据格式的返回
- 统一数据格式返回的优点
- 统一数据格式返回的实现
- 🔎总结
利用 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
🔎统一异常的处理
统一异常的处理, 利用 2 个注解
- @ControllerAdvice → 感知异常
- @ExceptionHandler → 处理异常
未设置异常处理🍂
NullPointerException
ArithmeticException
设置异常处理🍂
@ControllerAdvice
@ResponseBody
public class MyExceptionHandler {/*** 拦截所有空指针异常, 进行统一数据格式的返回* @author bibubibu* @date 2023/7/9*/@ExceptionHandler(NullPointerException.class)public HashMap<String, Object> nullPointerException(NullPointerException e) {HashMap<String, Object> map = new HashMap<>();map.put("status", -1);map.put("data", null);map.put("msg", "NullPointerException" + e.getMessage()); // 错误码的描述信息return map;}/*** 拦截所有算数异常, 进行统一数据格式的返回* @author bibubibu* @date 2023/7/9*/@ExceptionHandler(ArithmeticException.class)public HashMap<String, Object> arithmeticException(ArithmeticException e) {HashMap<String, Object> map = new HashMap<>();map.put("status", -1);map.put("data", null);map.put("msg", "ArithmeticException" + e.getMessage()); // 错误码的描述信息return map;}/*** 拦截所有异常, 进行统一数据格式的返回* @author bibubibu* @date 2023/7/9*/@ExceptionHandler(Exception.class)public HashMap<String, Object> exception(Exception e) {HashMap<String, Object> map = new HashMap<>();map.put("status", -1);map.put("data", null);map.put("msg", "Exception" + e.getMessage());return map;}
}
NullPointerException
ArithmeticException
🔎统一数据格式的返回
统一数据格式返回的优点
- 方便前端程序员更好的接收和解析后端数据接口返回的数据
- 降低前端程序员和后端程序员的沟通成本, 按照某个格式实现即可, 因为所有接口都是这样返回的
- 有利于项目统一数据的维护和修改
- 有利于后端技术部门统一规范的标准制定, 不会出现奇怪的返回内容
统一数据格式返回的实现
统一数据格式的返回, 利用注解 @ControllerAdvice + ResponseBodyAdvice(接口) 实现
未设置统一数据格式的返回🍂
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("get-num")public Integer getNumber() {return (int) (Math.random() * 10 + 1);}}
设置统一数据格式的返回🍂
- 自定义类(ResponseAdvice), 添加 @ControllerAdvice 注解
- 实现 ResponseBodyAdvice 接口, 重写 supports() 与 beforeBodyWrite()
supports() 类似于一个开关
当返回值为 true 时, 开启 beforeBodyWrite() 中编写的相关功能
当返回值为 false 时, 关闭 beforeBodyWrite() 中编写的相关功能
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {HashMap<String, Object> map = new HashMap<>();map.put("status", 200);map.put("data", body); // 此处的 Body 是 String 类型会出错map.put("msg", "");return map;}
}
当方法的返回值类型为 String 时
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/get-user")public String getUser() {System.out.println("执行 getUser()");return "getUser~~";}}
当调用方法的返回值类型为 String 时, 设置统一数据格式的返回🍂
类型转换异常
解决方法
当调用方法的返回值类型为 String 时, 利用 jackson 完成类型转换
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {// 利用 jackson 转换 String@Autowiredprivate ObjectMapper objectMapper;@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {HashMap<String, Object> map = new HashMap<>();map.put("status", 200);map.put("data", body); // 此处的 Body 是 String 类型会出错map.put("msg", "");// 判断 Body 是否为 String 类型if(body instanceof String) {// 是 String 类型, 将 map 转换为 Json 格式try {return objectMapper.writeValueAsString(map);} catch (JsonProcessingException e) {e.printStackTrace();}}return map;}
}
🔎总结
- 用户登录权限的统一校验 → 实现 HandlerInterceptor 接口 + 重写 preHandler 方法 + 将自定义拦截器添加至配置文件中(实现 WebMvcConfigurer 接口)
- 统一访问前缀的添加 → 重写 configurePathMatch( ) / 在配置文件中添加
- 统一异常的处理 → 利用注解 @ControllerAdvice + @ExceptionHandler
- 统一数据格式的返回 → 利用注解 @ControllerAdvice + 实现接口 ResponseBodyAdvice
🌸🌸🌸完结撒花🌸🌸🌸