利用 AOP 的思想对一些特定的功能进行统一的处理, 包括
通过一级路由调用多种方法, 需要保证这些方法的请求类型各不相同(GET, POST, PUT…)



使用 Spring AOP 可以实现统一拦截, 但 Spring AOP 的使用较为复杂, 包括
于是 Pivotal 公司针对上述情况开发出 Spring 拦截器
Spring 拦截器的使用🍂

(/*表示一级路由, /**表示所有的请求)
 
调用方法时, 发现 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( ) 🍂
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
    configurer.addPathPrefix("/bibubibu", c -> true);
}
 

在配置文件中添加🍂
server:
  servlet:
    context-path: /bibubibu
 

统一异常的处理, 利用 2 个注解
未设置异常处理🍂

NullPointerException

ArithmeticException

设置异常处理🍂
@ControllerAdvice
@ResponseBody
public class MyExceptionHandler {
    /**
    * 拦截所有空指针异常, 进行统一数据格式的返回
    * @author bibubibu
    * @date 2023/7/9
    */
    @ExceptionHandler(NullPointerException.class)
    public HashMap nullPointerException(NullPointerException e) {
        HashMap 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 arithmeticException(ArithmeticException e) {
        HashMap 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 exception(Exception e) {
        HashMap 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);
    }
}
 

设置统一数据格式的返回🍂
supports() 类似于一个开关
当返回值为 true 时, 开启 beforeBodyWrite() 中编写的相关功能
当返回值为 false 时, 关闭 beforeBodyWrite() 中编写的相关功能
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        HashMap 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
    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        HashMap 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;
    }
}
  


🌸🌸🌸完结撒花🌸🌸🌸
