【Spring Boot】拦截器使用和常用功能统一封装

x33g5p2x  于2022-08-17 转载在 Spring  
字(6.9k)|赞(0)|评价(0)|浏览(600)

1. 拦截器

1.1 拦截器的使用

Spring 中提供了拦截器 HandlerInteceptor,它的具体使用分为以下两个步骤:

  1. 创建自定义拦截器,实现 HandlerInteceptor 接口的 preHandle(执行具体方法之前的预处理)方法。
  2. 将自定义拦截器加入 WebMvcConfigurer 的 addInterceptors 方法中。

1.2 拦截器的原理

通过使用拦截器,业务的执行流程一般如下:

其中所有 controller 的执行都会通过一个调度器 DispatcherServlet 来实现,而所有的方法都会执行 DispatcherServlet 中的 doDispatch 调度方法,doDispatch 的源码如下:

  1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  2. HttpServletRequest processedRequest = request;
  3. HandlerExecutionChain mappedHandler = null;
  4. boolean multipartRequestParsed = false;
  5. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  6. try {
  7. try {
  8. ModelAndView mv = null;
  9. Object dispatchException = null;
  10. try {
  11. processedRequest = this.checkMultipart(request);
  12. multipartRequestParsed = processedRequest != request;
  13. mappedHandler = this.getHandler(processedRequest);
  14. if (mappedHandler == null) {
  15. this.noHandlerFound(processedRequest, response);
  16. return;
  17. }
  18. HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
  19. String method = request.getMethod();
  20. boolean isGet = "GET".equals(method);
  21. if (isGet || "HEAD".equals(method)) {
  22. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  23. if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
  24. return;
  25. }
  26. }
  27. // 调用预处理
  28. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  29. return;
  30. }
  31. // 执行 controller 中的业务
  32. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  33. if (asyncManager.isConcurrentHandlingStarted()) {
  34. return;
  35. }
  36. this.applyDefaultViewName(processedRequest, mv);
  37. mappedHandler.applyPostHandle(processedRequest, response, mv);
  38. } catch (Exception var20) {
  39. dispatchException = var20;
  40. } catch (Throwable var21) {
  41. dispatchException = new NestedServletException("Handler dispatch failed", var21);
  42. }
  43. this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
  44. } catch (Exception var22) {
  45. this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
  46. } catch (Throwable var23) {
  47. this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
  48. }
  49. } finally {
  50. if (asyncManager.isConcurrentHandlingStarted()) {
  51. if (mappedHandler != null) {
  52. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
  53. }
  54. } else if (multipartRequestParsed) {
  55. this.cleanupMultipart(processedRequest);
  56. }
  57. }
  58. }

在上述源码中标注了预处理和执行 controller 中业务的位置,而预处理即就是我们要探索的拦截器源码的位置,其中使用了 applyPreHandle 方法,applyPreHandle 方法的源码如下:

  1. boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
  2. HandlerInterceptor[] interceptors = this.getInterceptors();
  3. if (!ObjectUtils.isEmpty(interceptors)) {
  4. for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
  5. HandlerInterceptor interceptor = interceptors[i];
  6. if (!interceptor.preHandle(request, response, this.handler)) {
  7. this.triggerAfterCompletion(request, response, (Exception)null);
  8. return false;
  9. }
  10. }
  11. }
  12. return true;
  13. }

我们可以发现预处理中首先获取到了所有的拦截器,并进行遍历,如果当前拦截器的返回值为 true 则不进行拦截,如果返回值为 false 则提前结束,而在预处理中则会直接 return。

以上就是对拦截器源码的解析,在 applyRreHandle 中会获取所有拦截器 HandlerInterceptor,并执行拦截器的 preHandle 方法,这样就会与个人自定义的拦截器对应上。

而本质上 Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的,大体的调用流程如下:

2. 用户登录权限校验

  1. 定义用户登录权限校验的拦截器。
  1. public class LoginInterceptor implements HandlerInterceptor {
  2. @Override
  3. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  4. HttpSession session = request.getSession(false);
  5. if (session != null && session.getAttribute("user") != null){
  6. return true;
  7. }
  8. response.setStatus(401);
  9. return false;
  10. }
  11. }
  1. 将自定义拦截器加入到系统配置。
  1. @Configuration
  2. public class InterceptorConfig implements WebMvcConfigurer {
  3. /**
  4. * 添加拦截器和指定拦截规则
  5. * @param registry
  6. */
  7. @Override
  8. public void addInterceptors(InterceptorRegistry registry) {
  9. registry.addInterceptor(new LoginInterceptor())
  10. .addPathPatterns("/**") // 拦截所有请求
  11. .excludePathPatterns("/user/**"); // 放行 user 的请求
  12. }
  13. }

3. 统一异常处理

统一异常处理使用的是 @ControllerAdvice 和 @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法,具体实现步骤如下:

  1. 创建一个类,并在此类上添加 @ControllerAdvice@ResponseBody 注解。
  1. @ControllerAdvice // 表示控制器通知类
  2. @ResponseBody
  3. public class ExceptionAdvice {
  4. }
  1. 编写异常返回数据的方法,在该方法上加上 @ExceptionHandler 注解。
  1. @ControllerAdvice // 表示控制器通知类
  2. @ResponseBody
  3. public class ExceptionAdvice {
  4. @ExceptionHandler(Exception.class)
  5. public Object Exeception(Exception e){
  6. HashMap<String, Object> map = new HashMap<>();
  7. map.put("code", 0); // 状态码(设置0为异常,1为正常)
  8. map.put("data", null); // 返回数据
  9. map.put("msg", e.getMessage()); // 异常信息
  10. return map;
  11. }
  12. }
  1. 当有多个异常通知时,匹配顺序为存在的当前类及其子类向上依次匹配。如统一异常代码如下:
  1. @ControllerAdvice // 表示控制器通知类
  2. @ResponseBody
  3. public class ExceptionAdvice {
  4. @ExceptionHandler(Exception.class)
  5. public Object Exeception(Exception e){
  6. HashMap<String, Object> map = new HashMap<>();
  7. map.put("code", 0); // 状态码(设置0为异常,1为正常)
  8. map.put("data", null); // 返回数据
  9. map.put("msg", "总的异常信息:" + e.getMessage()); // 异常信息
  10. return map;
  11. }
  12. @ExceptionHandler(ArithmeticException.class)
  13. public Object ArithmeticException(ArithmeticException e){
  14. HashMap<String, Object> map = new HashMap<>();
  15. map.put("code", 0);
  16. map.put("data", null);
  17. map.put("msg", "算数异常信息:" + e.getMessage());
  18. return map;
  19. }
  20. }

当出现算数异常时,返回的结果应该是 ArithmeticException 方法通知的异常。

4. 统一数据返回格式

对于统一数据的格式可以手动进行封装,代码如下:

  1. @Data
  2. public class Response implements Serializable {
  3. private int code; // 状态码(-1-失败 0-成功 1-空)
  4. private Object data; // 数据
  5. private String msg; // 异常信息
  6. public static String success(Object data){
  7. Response response = new Response();
  8. response.code = 0;
  9. response.data = data;
  10. response.msg = null;
  11. return JSON.toJSONString(response);
  12. }
  13. public static String success(){
  14. Response response = new Response();
  15. response.code = 0;
  16. response.data = null;
  17. response.msg = null;
  18. return JSON.toJSONString(response);
  19. }
  20. public static String fail(String msg){
  21. Response response = new Response();
  22. response.code = -1;
  23. response.data = null;
  24. response.msg = msg;
  25. return JSON.toJSONString(response);
  26. }
  27. public static String fail(){
  28. Response response = new Response();
  29. response.code = -1;
  30. response.data = null;
  31. response.msg = null;
  32. return JSON.toJSONString(response);
  33. }
  34. public static String Empty(){
  35. Response response = new Response();
  36. response.code = 1;
  37. response.data = null;
  38. response.msg = "没有找到数据!";
  39. return JSON.toJSONString(response);
  40. }
  41. }

也可以通过 @ControllerAdvice 注解和实现 ResponseBodyAdvice 接口来实现自动的封装。实现方式如下:

  1. 创建一个类,给类上添加 @ControllerAdvice 注解,并实现 ResponseBodyAdvice 接口。
  1. @ControllerAdvice
  2. public class MyResponseBodyAdvice implements ResponseBodyAdvice {
  3. }
  • 重写 ResponseBodyAdvice 接口的两个方法。

  • supports 方法表示是否在返回数据之前重写数据,返回值为 true 表示重写。

  • beforeBodyWrite 方法用来重写方法返回的数据,将数据封装后返回给前端。

  1. @ControllerAdvice
  2. public class MyResponseBodyAdvice implements ResponseBodyAdvice {
  3. /**
  4. * 将 supports 的返回值设置为 true 来表示在返回数据前重写数据
  5. */
  6. @Override
  7. public boolean supports(MethodParameter methodParameter, Class aClass) {
  8. return true;
  9. }
  10. /**
  11. * 封装要重写的数据
  12. */
  13. @Override
  14. public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
  15. HashMap<String, Object> map = new HashMap<>();
  16. map.put("code", 1);
  17. map.put("data", o);
  18. map.put("msg", null);
  19. return map;
  20. }
  21. }

相关文章