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


    1. 拦截器

    1.1 拦截器的使用

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

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

    1.2 拦截器的原理

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jk93rtZE-1659533770813)(C:/Users/bbbbbge/Pictures/接单/1659418795924.png)]

    其中所有 controller 的执行都会通过一个调度器 DispatcherServlet 来实现,而所有的方法都会执行 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 = "GET".equals(method);
                    if (isGet || "HEAD".equals(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);
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

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

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

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

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

    而本质上 Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的,大体的调用流程如下:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5FHiBxLL-1659533770815)(C:/Users/bbbbbge/Pictures/接单/1659421359875.png)]

    2. 用户登录权限校验

    1. 定义用户登录权限校验的拦截器。

      public class LoginInterceptor implements HandlerInterceptor {
          @Override
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
              HttpSession session = request.getSession(false);
              if (session != null && session.getAttribute("user") != null){
                  return true;
              }
              response.setStatus(401);
              return false;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    2. 将自定义拦截器加入到系统配置。

      @Configuration
      public class InterceptorConfig implements WebMvcConfigurer {
          /**
           * 添加拦截器和指定拦截规则
           * @param registry
           */
          @Override
          public void addInterceptors(InterceptorRegistry registry) {
              registry.addInterceptor(new LoginInterceptor())
                      .addPathPatterns("/**") // 拦截所有请求
                      .excludePathPatterns("/user/**"); // 放行 user 的请求
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

    3. 统一异常处理

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

    1. 创建一个类,并在此类上添加 @ControllerAdvice@ResponseBody 注解。

      @ControllerAdvice // 表示控制器通知类
      @ResponseBody
      public class ExceptionAdvice {
          
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
    2. 编写异常返回数据的方法,在该方法上加上 @ExceptionHandler 注解。

      @ControllerAdvice // 表示控制器通知类
      @ResponseBody
      public class ExceptionAdvice {
      
          @ExceptionHandler(Exception.class)
          public Object Exeception(Exception e){
              HashMap<String, Object> map = new HashMap<>();
              map.put("code", 0); // 状态码(设置0为异常,1为正常)
              map.put("data", null); // 返回数据
              map.put("msg", e.getMessage()); // 异常信息
              return map;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    3. 当有多个异常通知时,匹配顺序为存在的当前类及其子类向上依次匹配。如统一异常代码如下:

      @ControllerAdvice // 表示控制器通知类
      @ResponseBody
      public class ExceptionAdvice {
      
          @ExceptionHandler(Exception.class)
          public Object Exeception(Exception e){
              HashMap<String, Object> map = new HashMap<>();
              map.put("code", 0); // 状态码(设置0为异常,1为正常)
              map.put("data", null); // 返回数据
              map.put("msg", "总的异常信息:" + e.getMessage()); // 异常信息
              return map;
          }
      
          @ExceptionHandler(ArithmeticException.class)
          public Object ArithmeticException(ArithmeticException e){
              HashMap<String, Object> map = new HashMap<>();
              map.put("code", 0);
              map.put("data", null);
              map.put("msg", "算数异常信息:" + e.getMessage());
              return map;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22

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

    4. 统一数据返回格式

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

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

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

    1. 创建一个类,给类上添加 @ControllerAdvice 注解,并实现 ResponseBodyAdvice 接口。

      @ControllerAdvice
      public class MyResponseBodyAdvice implements ResponseBodyAdvice {
      
      }
      
      • 1
      • 2
      • 3
      • 4
    2. 重写 ResponseBodyAdvice 接口的两个方法。

      • supports 方法表示是否在返回数据之前重写数据,返回值为 true 表示重写。
      • beforeBodyWrite 方法用来重写方法返回的数据,将数据封装后返回给前端。
      @ControllerAdvice
      public class MyResponseBodyAdvice implements ResponseBodyAdvice {
      
          /**
           * 将 supports 的返回值设置为 true 来表示在返回数据前重写数据
           */
          @Override
          public boolean supports(MethodParameter methodParameter, Class aClass) {
              return true;
          }
      
          /**
           * 封装要重写的数据
           */
          @Override
          public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
              HashMap<String, Object> map = new HashMap<>();
              map.put("code", 1);
              map.put("data", o);
              map.put("msg", null);
              return map;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
  • 相关阅读:
    JVM源码剖析之软、弱、虚引用的处理细节
    26.Xaml Menu控件---->菜单控件 (手动创建菜单,命令,Icon,快捷键)
    java计算机毕业设计二手车商城MyBatis+系统+LW文档+源码+调试部署
    Guava入门~CacheBuilderSpec
    如何避免朋友圈被隐藏折叠?三不要一要码住!
    数据分析实战 | 泊松回归——航班数据分析
    将Bean放入Spring容器中的方式有哪些?
    SpringBoot整合MQ
    AFUDOS命令
    MATLAB——一维离散小波的单层分解
  • 原文地址:https://blog.csdn.net/weixin_51367845/article/details/126149473