【Spring Boot 】Spring Boot 统一功能处理

news2024/11/17 20:34:09

🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!

欢迎志同道合的朋友一起加油喔🤺🤺🤺


目录

前言

1. Spring 拦截器

1.1 自定义拦截器

1.2 将自定义拦截器加入到系统配置中

1.3 拦截器实现原理

统一访问前缀添加 (扩展)

2. 统一异常的处理 (@ControllerAdvice 和 @ExceptionHandler)

3.统一数据返回格式

实现统一数据返回格式的功能

特殊情况,返回String类型

解决方案



前言

在学习了SpringAOP的原生操作后,我就立即着手想着去写一个统一处理用户登陆权限验证的功能。可能大多数小伙伴的想法和我一致,直接使用SpringAOP的前置通知方法或者环绕通知方法来实现不就行了吗?但是在真正使用原生SpringAOP对该功能进行实现时,我遇到了以下几个问题:

  • 首先是要验证用户的登陆状态,就要先获取到内存中的session对象,但是通过前置或者环绕通知的方式时很难拿到请求对象的,也就很难拿到session对象进行判断。
  • 其次是与我们用户相关的控制器中并非所有方法都要进行拦截判断(像登录、注册方法),那这样就大大增加了通过原生SpringAOP的切点表达式配置拦截规则的难度

那该怎么解决上述的问题呢?——更好的解决办法就是使用Spring拦截器~

1. Spring 拦截器

对于上述问题, Spring 提供的拦截器就可以很好地解决.

Spring 拦截器和传统 AOP的区别就类似 Servlet 和 Spring 的区别, 拦截器也是将传统 AOP 进行了封装, 内置了 reuqest, response 对象, 提供了更加方便的功能. 

一个项目里面实现统一用户验证登录的处理, 一般有三种解决方案:

  1. 使用传统的 AOP,
  2. 使用拦截器,
  3. 使用过滤器.

既然有三种解决方案, 为什么要选择使用拦截器呢 ?

  •  对于传统的 AOP, 功能比较简单, 写法过于复杂, 所以不使用.
  •  对于过滤器 (web容器提供的), 因为它的执行时机太靠前了, Spring 框架还没初始化, 也就是说触发过滤器的时候, request, response 对象还没有实例化. 所以过滤器用的也比较少. 

🍁实现拦截器的两大步骤

  1. 创建自定义拦截器, 实现 HandlerInterceptor 接口并重写preHandle (执行方法前的预处理) 方法.

  2. 将自定义拦截器加入 WebMvcConfigurer 的 addInterceptors 方法中. 【配置拦截规则】

  • a) 给当前的类添加 @Configuration 注解
  • b) 实现WebMvcConfigurer 接口
  • c) 重写 addInterceptors 方法

1.1 自定义拦截器

/**
 * 拦截器
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
    //调用目标方法执行之前的方法
    //此方法返回的是boolean 类型的值
    //如果返回的true 表示(拦截器)验证成功, 继续走后续的流程,执行目标方法
    //如果返回false, 表示拦截器执行失败, 验证为通过, 后续的流程和目标方法就不要执行了
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //用户登录判断业务
        HttpSession session = request.getSession(false);
        if(session != null && session.getAttribute("session_userinfo") != null) {
            //用户已经登录
            return true;
        }
        //登录失败,页面返回一个错误状态码
        response.setStatus(401);
        return false;
    }
}

自定义的拦截器是一个普通的类, 如果返回 true, 才会继续执行后续代码.

1.2 将自定义拦截器加入到系统配置中

前面写的自定义拦截器, 只是一个普通的类, 需要把它加入到系统配置中, 并配置拦截规则, 才是一个真正有用的拦截器.

@Configuration
public class myConfig implements WebMvcConfigurer {
    @Autowired //属性注入(注入loginInterceptor对象)
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") //拦截所有url
                .excludePathPatterns("/user/login")  //排除url /user/login 登录不拦截
                .excludePathPatterns("/user/reg")     //注册不拦截
                .excludePathPatterns("/image/**")  //排除image 文件夹下的所有文件
        ;
    }
}

1. addInterceptor 方法的作用 : 将自定义拦截器添加到系统配置中.
2. addPathPatterns : 表示需要拦截的 URL.
3. excludePathPatterns : 表示不拦截, 需要排除的 URL.
4. 拦截器不仅可以拦截方法, 还可以拦截静态文件 (.png, .js, .css) 

咱们定义个UserController,用于验证我们自定义拦截器的功能:

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/login")
    public String login() {
        return "登录成功";
    }
    @RequestMapping("/reg")
    public String reg() {
        return "注册成功";
    }
    @RequestMapping("/index")
    public String index() {
        return "其他方法";
    }
}

前两个步骤我们已经做好了准备工作, 并配置好了拦截规则, 规定除了登录和注册功能不拦截外, 拦截其他所有 URL (getInfo). 下面来进行验证一下拦截器是否生效.

 

 通过浏览器显示结果和抓包结果来看, 我们自定义的拦截器确实拦截了 index() 方法, 并且设置了状态码 401, 这也符合我们的预期. 

1.3 拦截器实现原理

有了拦截器之后,会在调用Controller之前进行相应的业务处理,流程如下:

  • 首先我们要知道 Controller 的执行都会通过一个调度器 (DispatcherServlet) 来实现.
  • 随便访问 controller 中的一个方法就能在控制台的打印信息就能看到, 这个可以类比到线程的调度上.

 然后所有 Controller 中方法都会执行 DispatcherServlet 中的调度方法 doDispatch().

我们通过分析源码, 发现源码中的这两个主要步骤.预处理的过程就 和 前边代码 LoginInterceptor 拦截器做的事情差不多,判断拦截的方法是否符合要求, 如果符合要求, 就返回 true,然后继续执行后续业务代码, 否则, 后面的代码都不执行.

 进入 applyPreHandle() 方法继续分析:

我们发现源码中就是通过遍历存放拦截器的 List, 然后不断判断每一个拦截器是否都返回 true 了, 但凡其中有一个拦截器返回 false, 后面的拦截器都不要走了, 并且后面的业务代码也不执行了. 看到这, 我们恍然大悟了.

通过前面的分析, 我们就能发现 Spring 中的拦截器其实就是封装了传统的 AOP , 它也是通过 动态代理的和环绕通知的思想来实现的        

统一访问前缀添加 (扩展)

如果我们想要在所有的请求地址前面加一个地址,我们可以进行以下操作,我们以加前缀api为例

@Configuration
public class MyConfig implements WebMvcConfigurer {
     // 所有的接口添加 api 前缀
     @Override
     public void configurePathMatch(PathMatchConfigurer configurer) {
         configurer.addPathPrefix("api", c -> true);
     }
}

我们addPathPrefix的第二个参数是一个表达式,设置为true表示启动前缀

 在浏览器输入url,测试结果如下:


2. 统一异常的处理 (@ControllerAdvice 和 @ExceptionHandler

为什么要统一异常的处理呢 ??

    就拿用户在银行取钱这件事来说, 如果用户在办理业务的时候, 后端程序报错了, 它不返回任何信息, 或者它返回的信息不统一, 这都会让前端程序猿不知道咋办, 他不知道咋办, 那么就无法给用户提供相应的提示. 此时用户见程序没反应, 他自己也会怀疑是自己没点到, 还是程序出 bug 了. 所以需要进行统一异常的处理. 

实现统一异常的处理是需要两个注解来实现的:

  • @ControllerAdvice : 定义一个全局异常处理类,用于处理在Controller层中抛出的各种异常,并对这些异常进行统一的处理。使用@ControllerAdvice注解可以将异常处理逻辑从Controller中解耦,提高代码复用性。
  • @ExceptionHandler : 定义异常处理方法,使用@ExceptionHandler,可以根据不同类型异常进行处理

二者结合表示, 当出现异常的时候执行某个通知 (执行某个方法事件)

1.建立统一异常处理类,并加入@ControllerAdvice注解

@ControllerAdvice  //表示这个类将被用于全局的异常处理
@ResponseBody      //表示该类返回的是json数据
public class MyExceptionAdvice {
}

2.定义异常处理方法,使用@ExceptionHandler,可以根据不同类型异常进行处理
我们来进行一个空指针异常处理:

@ControllerAdvice  //表示这个类将被用于全局的异常处理
@ResponseBody      //表示该类返回的是json数据
public class MyExceptionAdvice {

    //这个注解表示当应用程序中发生NullPointerException时,会调用此方法进行处理
    @ExceptionHandler(NullPointerException.class)
    //这个方法返回一个HashMap,其中包含了异常处理的结果。结果以键值对的形式存储,键是字符串,值是Object对象。
    public HashMap<String,Object> doNullPointerException(NullPointerException e) {
        HashMap<String ,Object> result = new HashMap<>();
        result.put("code",-1);   //"code":异常的状态码,这里是-1,表示发生了错误
        //"msg":异常的信息,这里添加了"空指针"的前缀,并附加上异常的具体信息(e.getMessage()获取异常的信息)。
        result.put("msg","空指针" + e.getMessage());
        //data":这里设置为null,表示没有返回的数据。
        result.put("data", null);
        return result;
    }
}

定义一个UserController 类:

@RestController  //它结合了@Controller和@ResponseBody两个注解的功能。
//这个类级别的注解指定了这个类处理的所有请求的URL路径的公共部分
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/login")
    public int login() {
        Object obj = null;
        //因为obj是null,所以当调用hashCode方法时,会抛出NullPointerException。
        System.out.println(obj.hashCode());
        return 1;
    }
}

我们再来访问login,看是什么情况:

 为了看出区别,我们来做两个测试:
1.首先我们去掉@ControllerAdvice注解

  •  我们发现直接报了500的错误信息了

 

2.如果不是空指针异常,而是算术异常呢

  •  我们发现同样还是报了500的错误信息,因为我们统一异常处理只处理了空指针异常

  • 这种情况,我们需要再写一个算术处理异常的处理类,我们可以直接使用所有异常类的父类Exception进行异常处理,这样所有的异常都能处理到了
    @ExceptionHandler(Exception.class)
    public HashMap<String,Object> doException(Exception e) {
        HashMap<String ,Object> result = new HashMap<>();
        result.put("code",-1);   //"code":异常的状态码,这里是-1,表示发生了错误
        //"msg":异常的信息,这里添加了"空指针"的前缀,并附加上异常的具体信息(e.getMessage()获取异常的信息)。
        result.put("msg","Exception" + e.getMessage());
        //data":这里设置为null,表示没有返回的数据。
        result.put("data", null);
        return result;
    }
  • 我们再来进行测试:

3.统一数据返回格式

为什么需要进行统一数据返回格式:

  1. 方便前端程序员更好的接收和解析后端数据接口返回的数据
  2. 降低前端程序员和后端程序员的沟通成本,按照某个格式进行
  3. 有利于项目统一数据的维护和修改
  4. 后端的统一规范的标准制定

实现统一数据返回格式的功能

实现步骤可以分为以下两步:

  •     自定义统一数据返回处理类,标注上@ControllerAdvice注解同时实现ResponseBodyAdvice接口。
  •     重写接口中的 supports方法和beforeBodyWrite方法 并在该方法中进行统一数据格式的处理。
     

实现代码如下:

  • supports决定是否执行beforeBodyWrite(数据重写),返回true表示重写,false表示不重写
  • 我们这里假设标准的数据格式是HashMap
//定义的全局响应体建议(Response Body Advice)。
//其作用是在返回数据到客户端之前,对返回的数据进行处理或修改
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {

    //是否执行 beforeBodyWrite 方法,true = 执行, 重写返回结果
    @Override
    //supports方法,用于决定是否对返回的数据进行处理
    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) {
        //Hash<String, Object> -> code,msg,data
        if(body instanceof HashMap) {
            return body;
        }
        //重写返回结果, 让其返回一个统一的数据格式
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", body);
        result.put("msg", "");
        return result;
    }
}

我们写一下Contoller,试着返回两组数据:

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

1.HashMap格式数据:

@RequestMapping("/reg")
public HashMap<String, Object> reg() {
    HashMap<String, Object> result = new HashMap<>();
    result.put("code", 200);
    result.put("msg", "");
    result.put("data", 1);
    return result;
}

 2.返回其他格式数据:

@RequestMapping("/login1")
public int login1() {
    return 1;
}

 我们可以发现即使我们在Controller层返回的不是标准格式的数据,也会进行重写。

特殊情况,返回String类型:

@RequestMapping("/sayHi")
public String sayHi() {
    return "say hi";
}

 这里报了类型转换异常,HashMap不能转换为String,为什么会出现这个问题呢,我们返回String会进行三个步骤:

  1. 方法返回String
  2. 统一数据格式返回的是 -> String 转为 HashMap
  3. 将HashMap转换为application/json字符串给前端

那么问题到底出现在哪一步呢?答案是我们在进行类型转换时出错了

  • 对于String 类型的数据 -> 会使用 StringHttpMessageConverter 这个转换器 进行类型转换(由于这个转换器 无法将HashMap转换为String,所以会报错)
  • 对于非String 类型的数据 ->会使用 HttpMessageConverter 这个转换器 进行类型转换
     

解决方案:

1. 将 StringHttpMessageConverter 转换器去掉,那么在进行类型转换的时候就会使用 HttpMessageConverter  这个转换器了

@Configuration
public class MyConfig implements WebMvcConfigurer {
    @Override
    //configureMessageConverters 方法使用 removeIf 方法删除了所有 StringHttpMessageConverter 的实例。
    // 这样,Spring MVC 就不会再使用 StringHttpMessageConverter 来处理 String 类型的数据了。
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.removeIf(converter -> converter instanceof StringHttpMessageConverter);
    }
}

 2.在统一数据重写时,单独处理String类型,直接让其返回一个json格式的字符串,而不是HashMap

    //返回数据之前进行数据重写
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if(body instanceof HashMap) {
            return body;
        }
        //重写返回结果, 让其返回一个统一的数据格式
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", body);
        result.put("msg", "");
        if(body instanceof String) {
            //将String类型的字符串转换成json格式的字符串返回
            return objectMapper.writeValueAsString(result);
        }
        return result;
    }
}

 

今天的内容到处就结束了! 如果你觉得这篇文章有价值,或者你喜欢它,那么请点赞并分享给你的朋友。你的支持是我创作更多有用内容的动力,感谢你的阅读和支持。祝你有个美好的一天!"

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

6月19日第壹简报,星期一,农历五月初二

6月19日第壹简报&#xff0c;星期一&#xff0c;农历五月初二坚持阅读&#xff0c;静待花开1. 未来10天江南华南等地多降雨过程&#xff0c;国家防总将防汛四级响应范围扩至八省份&#xff0c;加派工作组赴地方协助指导。2. “一天有四季、十里不同天”&#xff0c;“最美”独库…

LVS - DR集群

LVS - DR集群 数据包流向分析&#xff1a; &#xff08;1&#xff09;客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。 &#xff08;2&#xff09;Direct…

【VS2022】win 10 / win 11:Visual Studio 2022 社区版免费下载与安装

目录 一、Visual Studio 2022 下载 二、Visual Studio 2022 安装 三、Visual Studio 2022 快捷方式创建 四、Visual Studio 2022 使用 一、Visual Studio 2022 下载 Visual Studio 2022 官方下载https://visualstudio.microsoft.com/zh-hans/downloads/首先登陆 Visual Stu…

attention unet + cldice 论文总结

Blood Vessel Segmentation from Low-Contrast and Wide-Field Optical Microscopic Images of Cranial Window by Attention-Gate-Based Network论文总结 论文&#xff1a;Blood Vessel Segmentation by Attention-Gate-Based Network 目录 一、论文背景和出发点 二、创新点…

vue源码阅读之Observer

我们上次学习了vue数据驱动的概念&#xff0c;以及简单的vue怎么知道数据更新&#xff0c;然后采取行动的。今天我们就来继续深入学习&#xff0c;vue怎么把数据和视图给绑定在一起的&#xff0c;数据发生变化&#xff0c;视图怎么会自动发生变化的。 vue中的Observer 之前讲…

chatgpt赋能python:Python截取某一段文字

Python截取某一段文字 Python是最流行的编程语言之一&#xff0c;用于开发各种类型的应用程序&#xff0c;包括Web应用程序、桌面应用程序、游戏等。在本文中&#xff0c;我们将讨论如何使用Python截取某一段文字。这对于Web开发者和SEO专家非常有用&#xff0c;因为他们需要查…

【干货】Android系统定制基础篇:第八部分(增加以太网设置菜单、支持多摄像头、替换默认签名)

一、增加以太网设置菜单 Android 系统设置默认并没有以太网相关设置项&#xff0c;但以太网功能是支持的&#xff0c;因此我们仅仅需要增加设置界面即可。以太网设置界面如下&#xff1a; 修改 diff --git a/packages/apps/Settings/AndroidManifest.xml b/packages/apps/Se…

【第六次】21级计科计算机组成原理课外练习

【第六次】21级计科计算机组成原理课外练习 一、单选题二、多选题三、填空题 一、单选题 2-1 假定某计算机按字节编址&#xff0c;采用小端方式&#xff0c;有一个float型变量x的地址为0xffffc000&#xff0c;x12345678H&#xff0c;则在内存单元0xffffc001中存放的内容是 A.…

计算机网络和Linux网络

计算机网络和Linux网络 计算机网络概论 什么是计算机网络 计算机网络&#xff08;结构上&#xff09; 由节点&#xff08;主机、网络交换设备设备&#xff09;、边&#xff08;通信设备&#xff09;、协议构成协议&#xff1a;对等层的实体在通讯过程中应该遵守的规则的集合&…

软考A计划-网络工程师-易混淆知识汇总

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

chatgpt赋能python:Python编程:如何隐藏输入以保护信息安全?

Python编程&#xff1a;如何隐藏输入以保护信息安全&#xff1f; 随着数字化时代的到来&#xff0c;人们越来越多地依赖于互联网和技术设备进行通信和交易。然而&#xff0c;信息安全成为越来越大的问题&#xff1a;黑客和其他恶意分子通过各种手段获取和利用个人信息。为保障…

【瑞萨RA_FSP】GPT—— PWM功能详解

文章目录 一、GPT比较匹配功能详解1. 锯齿波PWM模式&#xff08;普通PWM模式&#xff09;2. 三角波PWM模式1&#xff08;波谷32位传输&#xff09;3. 三角波PWM模式2&#xff08;波峰和波谷32位传输&#xff09;4. 三角波PWM模式3&#xff08;波谷64位传输&#xff09;5. 设置死…

【随机种子初始化】一个神经网络模型初始化的大坑

1 问题起因和经过 半年前写了一个模型&#xff0c;取得了不错的效果&#xff08;简称项目文件1&#xff09;&#xff0c;于是整理了一番代码&#xff0c;保存为了一个新的项目&#xff08;简称项目文件2&#xff09;。半年后的今天&#xff0c;我重新训练这个整理过的模型&…

【C数据结构】带头双向循环链表_HDList

目录 带头双向循环链表_HDList 【1】链表概念 【2】链表分类 【3】带头双向循环链表 【3.1】带头双向循环链表数据结构与接口定义 【3.2】带头双向循环链表初始化 【3.3】带头双向循环链表开辟节点空间 【3.4】带头双向循环链表销毁 【3.5】带头双向循环链表头插 【3…

【C数据结构】带头单向非循环链表_HList

目录 带头单向非循环链表_HList 【1】链表概念 【2】链表分类 【3】有头单向非循环链表 【3.1】非循环链表数据结构与接口定义 【3.2】带头单向非循环链表初始化 【3.3】带头单向非循环链表释放空间 【3.4】带头单向非循环链表创建节点 【3.5】带头单向非循环链表头插…

HTML学习(二)

视频 <video width"320" height"240" controls> <source src"movie.mp4" type"video/mp4"> <source src"movie.ogg" type"video/ogg"> </video> 音频 <audio controls> <…

C++【AVL树】

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f383;操作环境&#xff1a; Visual Studio 2019 版本 16.11.17 文章目录 &#x1f307;前言&#x1f3d9;️正文1、认识AVL树1.1、AVL树的定义 2、AVL树的插入操作2.1、抽象图2.2、插入流程…

控制层调用接口的http请求封装

目录 0.碎碎念1.controller层2.util层3.测试3.1中间层调用GET请求3.2中间层调用POST请求 0.碎碎念 因为只是为了写这个帮助类&#xff0c;解耦&#xff0c;不敢拿已经写了一堆的代码改&#xff0c;就单独拆了个项目出来&#xff0c;持久层全是mybatisplus生成的。     所以…

Kafka源码解析之索引

Kafka源码解析之索引 索引结构 Kafka有两种类型的索引&#xff1a; TimeIndex: 根据时间戳索引&#xff0c;可以通过时间查找偏移量所在位置&#xff0c;目录下以.timeindex结尾Index: 根据偏移量索引&#xff0c;.index结尾 构建索引时机 由log.index.interval.bytes 参…

3. redis cluster集群运维与核心原理剖析

分布式缓存技术Redis 1. Redis集群方案比较2. Redis高可用集群搭建 本文是按照自己的理解进行笔记总结&#xff0c;如有不正确的地方&#xff0c;还望大佬多多指点纠正&#xff0c;勿喷。 课程内容&#xff1a; 1、哨兵集群与Redis Cluster架构异同 2、Redis高可用集群快速实…