springboot拦截器使用和常用功能统一封装(代码片段)

吞吞吐吐大魔王 吞吞吐吐大魔王     2022-12-30     787

关键词:

文章目录

1. 拦截器

1.1 拦截器的使用

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

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

1.2 拦截器的原理

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

其中所有 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);
        

    

在上述源码中标注了预处理和执行 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;

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

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

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

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;
        
    
    
  2. 将自定义拦截器加入到系统配置。

    @Configuration
    public class InterceptorConfig implements WebMvcConfigurer 
        /**
         * 添加拦截器和指定拦截规则
         * @param registry
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) 
            registry.addInterceptor(new LoginInterceptor())
                    .addPathPatterns("/**") // 拦截所有请求
                    .excludePathPatterns("/user/**"); // 放行 user 的请求
        
    
    

3. 统一异常处理

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

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

    @ControllerAdvice // 表示控制器通知类
    @ResponseBody
    public class ExceptionAdvice 
        
    
    
  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;
        
    
    
  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;
        
    
    

    当出现算数异常时,返回的结果应该是 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);
    


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

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

    @ControllerAdvice
    public class MyResponseBodyAdvice implements ResponseBodyAdvice 
    
    
    
  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;
        
    
    

springboot统一功能处理(代码片段)

SpringBoot统一功能处理前言一、用户登录权限效验1.1最初的用户登录验证1.2SpringAOP用户统一登录验证的问题1.3Spring拦截器1.3.1准备工作1.3.2自定义拦截器1.3.3将自定义拦截器加入到系统配置1.4拦截器实现原理1.4.1实现原理源码分析1.4... 查看详情

springboot慕课学习-springboot开发常用技术整合-拦截器的使用

WebMvcConfigurerAdapterisdeprecated  继承的方式已经被弃用了直接实现 WebMvcConfigurer类即可 查看详情

springboot+vue博客项目总结(代码片段)

文章目录Springboot+Vue博客项目总结1.工程搭建1.1新建maven工程1.2application.properties配置1.3配置分页插件1.4配置解决跨域1.5添加启动类2.统一异常处理3.登录功能实现3.1接口说明3.2JWT3.3Controller3.4Service3.5登录参数,redis配置5.获取... 查看详情

axios拦截,api统一管理(代码片段)

...axios封装axiosapi接口统一管理关于封装axios封装axios是我们使用前端经常做的事情,为什么要封装axios,因为在封装他里面,我们可以做好多东西,比如响应拦截,请求拦截,还有加载动画,使我们请求接... 查看详情

axios拦截,api统一管理(代码片段)

...axios封装axiosapi接口统一管理关于封装axios封装axios是我们使用前端经常做的事情,为什么要封装axios,因为在封装他里面,我们可以做好多东西,比如响应拦截,请求拦截,还有加载动画,使我们请求接... 查看详情

axios请求封装,请求异常统一处理

...,我对axios进行了简单的封装,这里主要使用了axios中的拦截器功能。封装后的网络请求工具js如下:封装之后的错误信息这个大家一目了然,没啥好说的,唯一要说的是当出错的时候我执行的是:Promise.resolve(err);,而不是Promise.r... 查看详情

基于springboot独立开发的一套统一认证解决方案,易读易拓展

...,所以笔者决定手动撸一套统一认证解决方案。笔者通过springboot框架,编写了一套统一认证解决方案,目前已使用于公司内部CRM、权限系统等。统一认证核心在拦截器的编写,其他内容都是围绕拦截器展开。一句话说明白统一... 查看详情

springboot项目快速实现过滤器功能

...AOP,而不是过滤器和拦截器?过滤器和拦截器在Springboot中怎么实现?这三者之间有什么区别?Springboot项目快速实现Aop功能中分享了AOP的相关实现,下面将再用两到三篇文章,分别和大家分享一下过滤器、... 查看详情

springboot整合freemarker和常用功能演示(代码片段)

...jdk8开始使用元空间)宏定义,方便功能的封装2.springboot整合freemarker2.1pom.xml<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency>2.2项目配置文件application... 查看详情

springboot整合freemarker和常用功能演示(代码片段)

...jdk8开始使用元空间)宏定义,方便功能的封装2.springboot整合freemarker2.1pom.xml<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency>2.2项目配置文件application... 查看详情

springboot实战——个人博客项目(代码片段)

...新增发表博客修改、删除自己的博客项目技术栈SSM(SpringBoot& 查看详情

springboot2----拦截器和文件上传功能(代码片段)

CRUD中遇到的知识点整理表单重复提交问题不经过登录直接来到某一页面的问题----拦截器我们这里的拦截器拦截的路径是/**:任意多层路径下的所有请求都会被拦截,那么静态资源就会被拦截如何解决静态资源被拦截器拦截的... 查看详情

springboot三种拦截器的使用与比较

springboot中有三种拦截器可供选择:filter、interceptort和aop。本文主要讨论三种拦截器的使用场景与使用方式。下文中的举例功能是计算每个请求的从开始到结束的时间,例子来源是慕课网。一、filter特点:可以获取原始的ServletRequ... 查看详情

controller层统一拦截进行日志处理

...ontroller层添加切面,以处理请求和返回的各项参数数据。使用切面进行日志处理下面我们就看一个例子说明基本的使用方式:packagecom.ding.test.core.aspect;importjavax.servlet.http.HttpServletRequest;importorg.aspectj.lang.Proc 查看详情

springboot自定义拦截器和跨域配置冲突

参考技术A在我们使用springboot中,难免遇到前后端分离的场景,因此也带来的问题是跨域请求,虽然springboot在配置跨域请求中非常方便,但是如果投跨域场景和自定义拦截器一起使用的话,那就没那么顺利了。二者功能会有冲突... 查看详情

vue3+ts如何安装封装axios(代码片段)

...ios求头能统一处理便于接口的统一管理解决回调地狱配置拦截器,给不同的实例配置不同的拦截器,支持以对象形式接受多个拦截器配置安装axiosnpminstallaxios引入插件在使用的文件中引入importaxiosfrom"axios";封装request... 查看详情

springboot返回统一的json标准格式(代码片段)

自定义状态码枚举类封装返回结果全局异常捕获处理,使用@RestControllerAdvice注解拦截Controller方法的返回值,统一处理返回值/响应体创建Controller,准备测试请求接口,查看响应结果近年来,随着移动互联网... 查看详情

springboot返回统一的json标准格式(代码片段)

自定义状态码枚举类封装返回结果全局异常捕获处理,使用@RestControllerAdvice注解拦截Controller方法的返回值,统一处理返回值/响应体创建Controller,准备测试请求接口,查看响应结果近年来,随着移动互联网... 查看详情