Spring Boot 统一功能处理(拦截器实现用户登录权限的统一校验、统一异常返回、统一数据格式返回)

目录

1. 用户登录权限校验

1.1 最初用户登录权限效验

1.2 Spring AOP 用户统⼀登录验证

1.3 Spring 拦截器

(1)创建自定义拦截器

(2)将自定义拦截器添加到系统配置中,并设置拦截的规则

1.4 练习:登录拦截器

(1)实现 UserController 实体类

(2)返回的登录页面:login.html

(3)实现效果

 1.5 拦截器实现原理

(1)实现原理源码分析

1.6 统一访问前缀添加

(1)在系统的配置文件中设置

 (2)在 application.properies 中配置

2. 统一的异常处理

2.1 异常的统一封装

(1)创建一个类,并在类上标识:@ControllerAdvice

 (2)添加方法 @ExceptionHandler 来订阅异常

3. 统一数据返回格式

3.1 为什么要统一数据返回格式

3.2 统一数据返回格式的实现

(1)创建一个类,并添加 @ControllerAdvice

(2)实现 ResponseBodyAdvice 接口,并重写 supports 和 beforeBodyAdvice 方法

4. @ControllerAdvice 源码分析

(1) @ControllerAdvice 源码

(2)查看 initializingBean 有哪些实现类

 (3)查询 initControllerAdviceCache 方法


本节主要讲解Spring Boot 统一功能处理,同样也是 AOP 的实战环节,我们希望能够实现以下目标:

  1. 统一用户登陆权限验证
  2. 统一异常处理
  3. 统一数据格式返回

1. 用户登录权限校验

回顾一下最初用户登录验证的实现方法:

  1. 最初的用户登录校验版本:在每个方法中获取 Session 以及 Session 中的信息,对用户账号以及密码进行校验,正确则登录成功,反之则失败
  2. 第二版本:实现统一方法去校验是否登陆成功,在每个需要验证的方法中调用统一的用户登录身份效验方法来判断
  3. 第三版本:使用 Spring AOP 来进行用户统一登录校验
  4. 第四版本:使用 Spring 拦截器来实现用户的统一登录验证

1.1 最初用户登录权限效验

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/a1")
    public Boolean login (HttpServletRequest request) {
        // 有 Session 就获取,没有就不创建
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            // 说明已经登录,进行业务处理
            return true;
        } else {
            // 未登录
            return false;
        }
    }

    @RequestMapping("/a2")
    public Boolean login2 (HttpServletRequest request) {
        // 有 Session 就获取,没有就不创建
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            // 说明已经登录,进行业务处理
            return true;
        } else {
            // 未登录
            return false;
        }
    }
}

这种方式写的代码,每个方法中都有相同的用户登录验证权限,缺点是:

  •     每个方法中都要单独写用户登录验证的方法,即使封装成公共方法,也一样要传参调用和在方法中进行判断
  •     添加控制器越多,调用用户登录验证的方法也越多,这样就增加了后期的修改成功和维护成功
  •     这些用户登录验证的方法和现在要实现的业务几乎没有任何关联,但还是要在每个方法中都要写一遍,所以提供一个公共的 AOP 方法来进行统一的用户登录权限验证是非常好的解决办法。

1.2 Spring AOP 用户统⼀登录验证

统一用户登录验证,首先想到的实现方法是使用 Spring AOP 前置通知或环绕通知来实现:

@Aspect // 当前类是一个切面
@Component
public class UserAspect {
    // 定义切点方法 Controller 包下、子孙包下所有类的所有方法
    @Pointcut("execution(* com.example.springaop.controller..*.*(..))")
    public void  pointcut(){}
    
    // 前置通知
    @Before("pointcut()")
    public void doBefore() {}
    
    // 环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        Object obj = null;
        System.out.println("Around 方法开始执行");
        try {
            obj = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("Around 方法结束执行");
        return obj;
    }
}

但如果只在以上代码 Spring AOP 的切面中实现用户登录权限效验的功能,有这样两个问题:

  1.     没有办法得到 HttpSession 和 Request 对象
  2.     我们要对一部分方法进行拦截,而另一部分方法不拦截,比如注册方法和登录方法是不拦截的,也就是实际的拦截规则很复杂,使用简单的 aspectJ 表达式无法满足拦截的需求
     

1.3 Spring 拦截器

针对上面代码 Spring AOP 的问题,Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现有两步:

  1.     创建自定义拦截器,实现 Spring 中的 HandlerInterceptor 接口中的 preHandle方法
  2.     将自定义拦截器加入到框架的配置中,并且设置拦截规则

(1)创建自定义拦截器

//实现 HandlerInterceptor 接口
public class loginInterceptor implements HandlerInterceptor {
    /**
     * 返回 true 继续下序流程
     * false 表示验证失败
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 用户登录业务判断
        // false 表示当不存在 session 不存在时不需要创造一个会话信息
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null){
            // 说明用户已经登录
            return true;
        }
        // 可以直接跳转到登录页面 或 返回一个 401、403 没有权限码
        response.sendRedirect("/login.html");
//        response.setStatus(401);
        return false;
    }
}

(2)将自定义拦截器添加到系统配置中,并设置拦截的规则

  • addPathPatterns:表示需要拦截的 URL,**表示拦截所有⽅法
  • excludePathPatterns:表示需要排除的 URL
@Configuration // 让随着spring启动而启动
public class AppConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new loginInterceptor())
                .addPathPatterns("/**")// 拦截所有请求
                .excludePathPatterns("/user/login")// 不拦截的 url 地址
                .excludePathPatterns("/user/reg")
                .excludePathPatterns("/**/*.html");
    }
}

1.4 练习:登录拦截器

实现愿望:

  1. 登录、注册页面不拦截,其余页面都拦截
  2. 等登陆成功写入 session 后,拦截页面可访问

(1)实现 UserController 实体类


@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/getUser")
    public String getuser(){
        System.out.println("执行了 getUser !");
        return "get user";
    }

    @RequestMapping("/login")
    public String login(){
        System.out.println("执行了 login !");
        return "get login";
    }

    @RequestMapping("/reg")
    public String reg(){
        System.out.println("执行了 reg !");
        return "get reg";
    }
}

(2)返回的登录页面:login.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>登录页面</title>
</head>
<body>
<h1>登录页面</h1>
</body>
</html>

(3)实现效果

 

 1.5 拦截器实现原理

 

(1)实现原理源码分析

  1. 所有的 Controller 执行都会通过一个调度器 DispatcherServlet 来实现 
  2. 而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度⽅法,doDispatch 源码分析如下:

 通过源码分析,可以看出,Sping 中的拦截器也是通过动态代理和环绕通知的思想实现的

1.6 统一访问前缀添加

方法:

  1. 在系统的配置文件中设置
  2. 在 application.properies 中配置

(1)在系统的配置文件中设置

/**
 * 所有的接口添加 api 前缀
 * c 代表所有的请求(Controller)
 * 表示所有的地址都会加上这个前缀
 * @param configurer
 */
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
    configurer.addPathPrefix("api",c -> true);
}

现在我们去查看之前不被拦截的地址

 (2)在 application.properies 中配置

 

2. 统一的异常处理

  1. 给当前的类上加 @ControllerAdvice 表示控制器通知类
  2. 给方法上添加 @ExceptionHandler(xxx.class),表示异常处理器,添加异常返回的业务代码

我们先去制造些异常:

 

2.1 异常的统一封装

(1)创建一个类,并在类上标识:@ControllerAdvice

@ControllerAdvice
public class ExceptionHandler {
}

 (2)添加方法 @ExceptionHandler 来订阅异常

@ControllerAdvice
@ResponseBody// 表示当前的所有方法返回的都是数据不是页面
public class ExHandler {

    /**
     * 拦截所有的空指针异常,继续统一的数据返回
     */

    @ExceptionHandler(NullPointerException.class)// 空指针异常
    public HashMap<String,Object> nullException(NullPointerException e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("code","-1");
        result.put("msg","空指针异常:" + e.getMessage());//错误码的描述信息
        result.put("date",null);
        return result;
    }

}

但是需要考虑的一点是,如果每个异常都这样写,那么工作量是非常大的,并且还有自定义异常,所以上面这样写肯定是不好的,既然是异常直接写 Exception 就好了,它是所有异常的父类,如果遇到不是前面写的两种异常,那么就会直接匹配到 Exception

当有多个异常通知时,匹配顺序为当前类及其⼦类向上依次匹配

@ControllerAdvice
@ResponseBody// 表示当前的所有方法返回的都是数据不是页面
public class ExHandler {

    /**
     * 拦截所有的空指针异常,继续统一的数据返回
     */

    @ExceptionHandler(NullPointerException.class)// 空指针异常
    public HashMap<String,Object> nullException(NullPointerException e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("code","-1");
        result.put("msg","空指针异常:" + e.getMessage());//错误码的描述信息
        result.put("date",null);
        return result;
    }

    @ExceptionHandler(Exception.class)// 所有异常
    public HashMap<String,Object> AllException(NullPointerException e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("code","-1");
        result.put("msg","异常:" + e.getMessage());//错误码的描述信息
        result.put("date",null);
        return result;
    }
}

 

 

3. 统一数据返回格式

3.1 为什么要统一数据返回格式

  1. 方便前端程序员更好的接收和解析后端数据接口返回的数据。
  2. 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就行了,因为所有接口都是这样返回的
  3. 有利于项目统一数据的维护和修改。
  4. 有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容。

3.2 统一数据返回格式的实现

(1)创建一个类,并添加 @ControllerAdvice

@ControllerAdvice
public class ResponseAdvice {

}

(2)实现 ResponseBodyAdvice 接口,并重写 supports 和 beforeBodyAdvice 方法

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    /**
     * 表示是否需要重写
     * 返回true则执行beforeBodyWrite方法,反之则不执行
     */
    @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<String,Object> hashMap = new HashMap<>();
        hashMap.put("code",200);// 状态码
        hashMap.put("msg","");// 错误的描述信息
        hashMap.put("date",body);
        return hashMap;
    }
}

supports方法相当于是一个开关,只有当 true 时才能执行重写 beforeBodyWrite 方法,false就不重写

当访问 getUser 时发生异常了,类型访问异常 

注意:

我们知道String既不属于基本数据类型,又不属于对象且在重写方法的时候其余类型都是用的统一的格式化工具,而String用的是它自身的格式化工具,String自身的格式化工具在执行的时候还没有加载好,就会导致 原始类型 是String的时候,在转化成HashMap的时候就会报错

所以在统一返回的时候需要对String进行单独的处理

 

jackson就是用于 json 数据转换的,json的转换工具 

@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    HashMap<String,Object> hashMap = new HashMap<>();
    hashMap.put("code",200);// 状态码
    hashMap.put("msg","");// 错误的描述信息
    hashMap.put("date",body);
    if (body instanceof String){
        // 判断数据类型是不是 String,是String需要特殊处理,因为 String 在转换的时候会报错
        try {
            return objectMapper.writeValueAsString(hashMap);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
    return hashMap;
}

4. @ControllerAdvice 源码分析

通过对 @ControllerAdvice 源码的分析我们可以知道上面统一异常和统一数据返回的执行流程

(1) @ControllerAdvice 源码

可以看到 @ControllerAdvice 派生于 @Component 组件而所有组件初始化都会调用 InitializingBean 接口

(2)查看 initializingBean 有哪些实现类

在查询过程中发现,其中 Spring MVC 中的实现子类是 RequestMappingHandlerAdapter,它里面有一个方法 afterPropertiesSet()方法,表示所有的参数设置完成之后执行的方法

 (3)查询 initControllerAdviceCache 方法

发现这个方法在执行时会查找使用所有的 @ControllerAdvice 类,发送某个事件时,调用相应的 Advice 方法,比如返回数据前调用统一数据封装,比如发生异常是调用异常的 Advice 方法实现的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/71633.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

idea如何上传项目到github(超详细)

idea如何上传项目到github 1、IDEA配置2、项目上传到本地仓库2.1、创建本地git仓库2.2、Add操作2.3、Commit操作 3、项目上传到Github4、拿到登录Github的token 1、IDEA配置 File-Settings-VersionControl-Git Git的安装路径下bin目录下的git.exe可执行文件 可以直接点 Gene…

UVA-1601 万圣节后的早晨 题解答案代码 算法竞赛入门经典第二版

GitHub - jzplp/aoapc-UVA-Answer: 算法竞赛入门经典 例题和习题答案 刘汝佳 第二版 以三个点的当前位置作为状态&#xff0c;广度优先遍历&#xff0c;找到终点即为最短次数。 注意&#xff1a; 一次可以移动多个点&#xff0c;但是每个点只能移动一步。在同一次中&#xf…

tomcat服务七层搭建动态页面查看

一个服务器多实例复制完成 配置tomcat多实例的环境变量 vim /etc/profile.d/tomcat.sh配置tomcat1和tomcat2的环境变量 进入tomcat1修改配置 测试通信端口是否正常 连接正常 toncat 2 配置修改 修改这三个 端口配置修改完成 修改tomcat1 shudown 分别把启动文件指向tomcat1…

Spring Cloud 面试突击2

Spring Cloud 面试突击2 高并发&#xff1a;是一种系统运行过程中遇到的短时间大量的请求操作 响应时间&#xff1a; 吞吐量&#xff1a; QPS&#xff1a;数据库为维度 TPS 并发用户数 并发的维度&#xff1a;很多的 并发是不是达到的当前系统的瓶颈 缓存 &#xff08…

2023牛客暑期多校训练营7

Beautiful Sequence 贪心&#xff0c;二进制&#xff0c;构造 Cyperation 模拟 &#xff0c;数学 We Love Strings 分块&#xff0c;二进制枚举&#xff0c;二进制容斥dp Writing Books 签到 根据相邻两个异或值B&#xff0c;因为前小于等于后&#xff0c;故从高到低遍历B的每一…

实验二十六、RC桥式正弦波振荡电路参数选择

一、题目 电路如图1所示。利用 Multisim 分析下列问题&#xff1a; &#xff08;1&#xff09;选择合适的 R f R_f Rf​ 和稳压管&#xff0c;使电路产生正弦波振荡&#xff0c;并观察起振过程&#xff1b; &#xff08;2&#xff09;调整电路参数&#xff0c;使输出电压峰值…

CH342/CH343/CH344/CH347/CH9101/CH9102/CH9103/CH9104 Linux串口驱动使用教程

CH343 Linux串口驱动 ch343ser_linux 支持USB转串口芯片 ch342/ch343/ch344/ch347/ch9101/ch9102/ch9103/ch9104等 &#xff0c;同时该驱动配合ch343_lib库还提供了芯片GPIO接口的读写功能&#xff0c;内部EEPROM的信息配置和读取功能等。 芯片型号串口数量GPIO数量CH342F/K2C…

CCLINK IE FIELD BASIC转MODBUS-TCP网关cclink与以太网的区别

协议的不同&#xff0c;数据读取困难&#xff0c;这是很多生产管理系统的难题。但是现在&#xff0c;捷米JM-CCLKIE-TCP通讯网关&#xff0c;让这个问题变得非常简单。这款通讯网关可以将各种MODBUS-TCP设备接入到CCLINK IE FIELD BASIC网络中&#xff0c;连接到MODBUS-TCP总线…

cpu的架构

明天继续搞一下cache,还有后面的, 下面是cpu框架图 开始解释cpu 1.控制器 控制器又称为控制单元&#xff08;Control Unit&#xff0c;简称CU&#xff09;,下面是控制器的组成 1.指令寄存器IR:是用来存放当前正在执行的的一条指令。当一条指令需要被执行时&#xff0c;先按…

模型性能的主要指标

主要参数 ROC 曲线和混淆矩阵都是用来评估分类模型性能的工具 ROC曲线&#xff08;Receiver Operating Characteristic curve&#xff09;&#xff1a; ROC曲线描述了当阈值变化时&#xff0c;真正类率&#xff08;True Positive Rate, TPR&#xff09;和假正类率&#xff0…

Android Studio跳过Haxm打开模拟器

由于公司权限限制无法安装Haxm&#xff0c;这个时候我们可以试试Arm相关的镜像去跳过Haxm运行模拟器。解决方案&#xff1a;安装API27以下的Arm Image. #ifdef __x86_64__if (sarch "arm64" && apiLevel >28) {APANIC("Avds CPU Architecture %s i…

NPM与外部服务的集成(上)

目录 1、关于访问令牌 1.1 关于传统令牌 1.2 关于粒度访问令牌 2、创建和查看访问令牌 2.1 创建访问令牌 在网站上创建传统令牌 在网站上创建粒度访问令牌 使用CLI创建令牌 CIDR限制令牌错误 查看访问令牌 在网站上查看令牌 在CLI上查看令牌 令牌属性 1、关于访问令…

mysql数据库第十二课------mysql语句的拔高2------飞高高

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

【C++】开源:CGAL计算几何库配置使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍CGAL计算几何库配置使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;…

【java】mybatis-plus代码生成

正常的代码生成这里就不介绍了。旨在记录实现如下功能&#xff1a; 分布式微服务环境下&#xff0c;生成的entity、dto、vo、feignClient等等api模块&#xff0c;需要和mapper、service、controller等等分在不同的目录生成。 为什么会出现这个需求&#xff1f; mybatis-plus&am…

【计算机视觉|生成对抗】用深度卷积生成对抗网络进行无监督表示学习(DCGAN)

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks 链接&#xff1a;[1511.06434] Unsupervised Representation Learning with Deep Conv…

微信小程序中键盘弹起输入框自动跳到键盘上方处理

效果展示 键盘未弹起时 键盘弹起后&#xff1a; 实现方式 话就不多说了 我直接贴代码了 原理就是用你点击的输入框的底部 距离顶部的位置 减去屏幕高度除以2&#xff0c;然后设成负值&#xff0c;再将这个值给到最外层相对定位的盒子的top属性&#xff0c;这样就不会出现顶…

服务器安装JDK

三种方法 方法一&#xff1a; 方法二&#xff1a; 首先登录到Oracle官网下载JDK JDK上传到服务器中&#xff0c;记住文件上传的位置是在哪里&#xff08;我放的位置在/www/java&#xff09;&#xff0c;然后看下面指示进行安装 方法三&#xff1a; 首先登录到Oracle官网下载…

修改IDEA的idea.vmoptions参数导致IDEA无法打开(ReservedCodeCacheSize)

事发原因 Maven导依赖的时候OOM&#xff0c;因此怀疑是内存太小&#xff0c;尝试修改idea.vmoptions的参数&#xff0c;然后发现IDEA重启后打不开了&#xff0c;卸载重装后也无法打开。。。 实际上如果导包爆出OOM的话应该调整下图参数&#xff0c;不过这都是后话了 解决思路…

王道机组难题分析

第四章 指令系统 大端方式&#xff1a;就是高地址存放高位&#xff0c; LSB的意思是&#xff1a;全称为Least Significant Bit&#xff0c;在二进制数中意为最低有效位 MSB的意思是&#xff1a;全称为Most Significant Bit&#xff0c;在二进制数中属于最高有效位 操作数可以理…