SpringBoot 统一功能处理

目录

一、用户登录权限验证

1.1 SpringAOP可以进行处理吗?

1.2 创建自定义拦截器

 1.3 将自定义拦截器配置到系统配置项中

1.4 拦截器的实现原理

1.4.1 实现原理源码分析

1.5 统一访问前缀添加

二、统一异常处理

2.1 为什么需要使用统一异常处理?

2.2 统一异常处理的实现

三、统一数据返回格式

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

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

 3.3 返回值为String类型时,应该如何处理?

3.3.1 将 StringHttpMessageConverter 去掉。

3.3.2 在统一数据返回的时候,单独处理String类型,让其返回一个String字符串,而非 HashMap

 总结:


前言:

一般Spring Boot统一功能处理模块,也是AOP的实战环节,要实现课程目标有以下3个:

  • 统一用户登录权限验证
  • 统一数据格式
  • 统一异常处理

一、用户登录权限验证

1.1 SpringAOP可以进行处理吗?

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class UserAspect {// 定义切点⽅法 controller 包下、⼦孙包下所有类的所有⽅法@Pointcut("execution(* com.example.demo.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 throwable) {throwable.printStackTrace();}System.out.println("Around ⽅法结束执⾏");return obj;}
}

我们知道SpringAOP虽然就提供了对用户登录的处理逻辑,但是存在一些问题:

  • 没有办法获取HttpSession对象
  • 如果要对一部分方法拦截,一部分方法不拦截,这种情况很难处理。(比如注册和登录方法在用户登录权限验证中是不能进行拦截的)

拦截器和SpringAOP虽然都是AOP的实现方式,但是这两个其实是完全不同的技术体系。

Spring提供了具体的实现拦截器:HandlerInterceptor,该SpringBoot 拦截器实现分为以下两个步骤:

  1. 自定义拦截器
  2. 将自定义拦截器配置到系统配置项,并且设置合理的拦截规则

1.2 创建自定义拦截器

自定义拦截器继承HandlerInterceptor后,需要重写相对应的方法,这里我们重写 preHandle方法:

 代码如下:

package com.example.demo.common;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;/*** 自定义拦截器*/
public class LoginInterceptor implements HandlerInterceptor {/*** 以下方法是调用目标方法之前执行的方法。此方法返回boolean类型的值* 返回true标识验证成功,程序会继续执行后续流程* 返回false, 表示拦截器拦截失败, 验证未通过, 后续的流程和目标方法不再执行。* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic 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;}return false;}
}

 1.3 将自定义拦截器配置到系统配置项中

 重写addInterceptors方法:

 设置拦截规则,代码如下

package com.example.demo.config;import com.example.demo.common.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;public class MyConfig implements WebMvcConfigurer {@AutowiredLoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**") //拦截所有的url.excludePathPatterns("user/login") // url为:user/login 不进行拦截 以下同理.excludePathPatterns("user/reg").excludePathPatterns("image/**"); // image夹目录下的所有url都不进行拦截}
}

或者使用Spring方法,通过DI注入的方式,这样可以实现不用new一个实例:

首先需要将拦截器添加到spring中,也就是给其添加一个五大类注解,这里我们就使用@Component。接着就可以使用@Autowired来得到实例。

 其中:

  • addPathPatterns: 表示需要拦截的URL,“**”表示拦截任意方法(也就是所有方法)。
  • excludePathPatterns: 表示需要排除的URL。

 说明:以上拦截规则可以拦截此项目中的URL,包括静态文件(图片文件,JS和CSS等文件)。

1.4 拦截器的实现原理

下面我们先来看一组正常情况下的调用顺序:

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

1.4.1 实现原理源码分析

所有的Controller执行都会通过一个调度器DispatcherServlet来实现,这一点可以从Spring Boot控制台的打印信息看出,如下图所示:

在IDEA中,通过全局搜索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 = HttpMethod.GET.matches(method);if (isGet || HttpMethod.HEAD.matches(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);}}}

观察DispatcherServlet中的某段代码:

我们发现,在执行后续的Controller代码之前,都会先执行这个applyPreHandle方法,于是鼠标双击 applyPreHandle,得到代码如下:

从上述源码可以看出,在applyPreHandle中会获取所有的拦截器HandlerInterceptor并执行拦截器中的preHandle方法,这样就和之前定义的拦截器对应上了,如下图所示:

package com.example.demo.common;import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;/*** 自定义拦截器*/
@Component
public class LoginInterceptor implements HandlerInterceptor {/*** 以下方法是调用目标方法之前执行的方法。此方法返回boolean类型的值* 返回true标识验证成功,程序会继续执行后续流程* 返回false, 表示拦截器拦截失败, 验证未通过, 后续的流程和目标方法不再执行。* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic 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.setContentType("application/json");response.setCharacterEncoding("utf8");response.getWriter().println("asdasd");return false;}
}

1.5 统一访问前缀添加

所有请求地址添加api前缀:

代码如下:

package com.example.demo.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class AppConfig implements WebMvcConfigurer {@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {configurer.addPathPrefix("fox", c -> true);}
}

二、统一异常处理

2.1 为什么需要使用统一异常处理?

通俗来讲,统一异常处理的主要目的是为了方便前端,让其更好的处理后端的信息,尽量将逻辑处理这块放置于后端,前端的目的其实主要是为用户服务。

比如:可以跟前端约定出现异常报错时候的状态码是多少,这样方便前端的处理,也方便后续后端在日志文件中将其找到,并修改异常。

2.2 统一异常处理的实现

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

以上方法表示,如果出现了异常就返回给前端一个HashMap对象, 其中包含的字段如代码定义那样。

 注意:

方法名和返回值都是可以自定义的,另外 @ExceptionHandler()中的参数是可以选择的,这里是Exception.class:表示的是可以在程序抛出异常的时候执行这里的代码,让其返回数据给前端,如果填入的参数是NullPointerException:那么表示的是当程序出现空指针异常的时候,会执行这里的代码。

这里的实现逻辑和Java中的异常处理是相似的,如果开发者有对Exception和NullPointerException分别进行了处理,那么当程序出现NullPointerException异常的时候,还是会根据我们写的NullPointerException执行逻辑进行处理,并不会直接走Exception的逻辑。

示例如下:

访问页面后效果如下:

总结:当有多个异常通知时,匹配顺序为当前类及其子类向上依次匹配。

三、统一数据返回格式

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

统一数据返回格式的优点如下,比如以下几个:

  • 方便前端程序员更好的接受和解析后端数据接口的数据
  • 降低前端程序员和后端程序员的沟通成本
  • 有利于项目统一数据的维护和修改
  • 有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容

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

统一的数据返回格式可以使用@ControllerAdvice + ResponseBodyAdvice 的方式实现,具体实现代码如下:

package com.example.demo.common;import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import java.util.HashMap;/*** 统一数据格式处理*/
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {/*** 是否执行 beforeBodyWrite 方法, 返回 true 就执行, 返回 false 就不执行* @param returnType* @param converterType* @return*/@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}/*** 返回数据之前进行数据重写* @param body 原始返回值* @param returnType* @param selectedContentType* @param selectedConverterType* @param request* @param response* @return*/@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// 这里我们规定统一的数据返回为HashMapif (body instanceof HashMap) {return body;}// 重写返回结果,让其返回一个统一的数据格式HashMap<String, Object> result = new HashMap<>();result.put("code",200);result.put("data",body);result.put("msg","");return result;}
}

 访问user/login1:

经过统一功能处理后代码展现如下:

 3.3 返回值为String类型时,应该如何处理?

 但是如果将返回值改为String类型,按照以上的执行逻辑,那么就无法走上述的正常数据统一处理:

我们发现,当返回类型为String的时候,程序会抛出异常,从而被我们的 统一异常处理模块拦截。

观察异常信息,发现 抛出异常:java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.String

可能会感到奇怪,为什么会抛出这个异常呢?

下面我们来看看后端返回前端时候的执行流程:

1.  一开始,前端访问该网址时,方法返回的是 String:

2. 统一数据返回之前会进行处理,将 String 转换为 HashMap:

3. 将HaspMap转换成 application/json 字符串给前端(接口)

这个步骤有两种情况,先判断原Body的类型:

  • 是 String 类型,那么就会使用 StringHttpMessageConverter 进行类型转换
  • 如果不是 String 类型,那么使用 HttpMessageConverter 进行类型转换

以上报错就是因为原始Body是String类型,所以在类型转换时候报错了

解决方案有如下两种:

  • 将 StringHttpMessageConverter 去掉。
  • 在统一数据返回的时候,单独处理String类型,让其返回一个String字符串,而非HashMap

3.3.1 将 StringHttpMessageConverter 去掉。

在配置文件中使用以下代码即可;

package com.example.demo.config;import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.List;@Configuration
public class MyConfig implements WebMvcConfigurer {/*** 移除 StringHttpMessageConverter* @param converters*/@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.removeIf(converter -> converter instanceof StringHttpMessageConverter);}
}

 访问地址后显示如下:

3.3.2 在统一数据返回的时候,单独处理String类型,让其返回一个String字符串,而非 HashMap

引入ObjectMapper(ObjectMapper 是Jackson库中的一个类,用于在Java对象(POJO,Plain Old Java Objects)和JSON数据之间进行相互转换):

对Body为String进行单独处理: 

访问页面如下所示:

 总结:

本文主要介绍了统一用户登录权限的效验,使用WebMvcConfigurer + HandlerInterceptor 来实现。统一异常处理使用 @ControllerAdvice + @ExceptionHandler 来实现,统一返回值处理使用@ControllerAdvice + ResponseBodyAdvice来处理。

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

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

相关文章

基于antd+vue2来实现一个简单的绘画流程图功能

简单流程图的实现&#xff08;基于antdvue2的&#xff09;代码很多哦~ 实现页面如下 1.简单操作如下 2.弹框中使用组件&#xff1a; <vfdref"vfd"style"background-color: white;":needShow"true":fieldNames"fieldNames"openUse…

day-04 基于UDP的服务器端/客户端

一.理解UDP &#xff08;一&#xff09;UDP套接字的特点 UDP套接字具有以下特点&#xff1a; 无连接性&#xff1a;UDP是一种无连接的协议&#xff0c;这意味着在发送数据之前&#xff0c;不需要在发送方和接收方之间建立连接。每个UDP数据包都是独立的&#xff0c;它们可以独…

FOXBORO FBM232 P0926GW 自动化控制模块

Foxboro FBM232 P0926GW 是 Foxboro&#xff08;福克斯博罗&#xff09;自动化控制系统的一部分&#xff0c;通常用于监测和控制工业过程。以下是关于这种类型的自动化控制模块可能具有的一些常见功能&#xff1a; 数字输入通道&#xff1a; FBM232 P0926GW 控制模块通常具有多…

【漏洞库】Fastjson_1.2.24_rce

文章目录 漏洞描述漏洞编号漏洞评级影响版本漏洞复现- 利用工具- 漏洞环境- 漏洞扫描- 漏洞验证- 深度利用- GetShell- EXP 编写 漏洞挖掘- 指纹信息 修复建议- 漏洞修复 漏洞原理 漏洞描述 Fastjson 存在反序列化远程代码执行漏洞&#xff0c;当应用或系统使用 Fastjson 对由…

十一、做高并发内存池项目过程中遇到的bug以及调试bug的方法和心得

十一、做高并发内存池项目过程中遇到的bug以及调试bug的方法和心得 第一个bug是内存问题&#xff0c;程序直接崩溃&#xff0c;问题出现在&#xff1a;GetOneSpan函数中的切分span的时候结尾的span1的next没有置空。 第二个bug是还小内存块给span的时候找不到小内存所属的spa…

使用Android原生制作毛玻璃效果图片

毛玻璃效果&#xff0c;也被称为模糊效果&#xff0c;是许多现代应用中流行的一种视觉效果。在 Android 中&#xff0c;我们可以通过多种方式实现该效果。本文将探讨如何使用 Android 原生的 Bitmap 类和 RenderScript 来实现毛玻璃效果。 1. 准备工作 首先&#xff0c;你需要…

BLE Mesh蓝牙mesh网多跳大数据量高带宽传输数据方法

1、BLE Mesh数据传输现状 BLE Mesh网络技术是低功耗蓝牙的一个进阶版&#xff0c;Mesh扩大了蓝牙在应用中的规模和范围&#xff0c;因为它同时支持超过三万个网络节点&#xff0c;可以跨越大型建筑物&#xff0c;不仅可以使得医疗健康应用更加方便快捷&#xff0c;还能监测像学…

IDea寻找冲突的依赖包

场景&#xff1a;boot项目运行时&#xff0c;提示log4j2依赖包冲突。 SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/D:/maven/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/Stati…

HTML5Plus

之前写过在 vue 中使用 mui 框架的方法&#xff0c;因为用 vue 开发后打包 5App 会有一些问题&#xff0c;所以当时用到了&#xff0c;最近又一次开发移动端&#xff0c;不同的是这次使用的是 vue3 开发的&#xff0c;导致之前使用的 vue-awesome-mui 依赖不能使用了&#xff0…

快速完成工信部APP备案流程_以阿里云APP备案为例

阿里云APP备案流程分为6步&#xff0c;APP备案成功后应用可以上架&#xff0c;登录阿里云账号填写APP信息&#xff0c;等待阿里云初审&#xff0c;初审通过后进行工信部短信核验&#xff0c;管局审核通过后APP即可备案成功&#xff0c;最后移动APP应用可以分发平台上架&#xf…

网络层重点协议-IP协议(结构分析)

IP协议数据报格式 一.4位版本号 用来表示IP协议的版本&#xff0c;现有的IP协议只有两个版本IPv4和IPv6 二.4位首部长度 IP协议数据报报头的长度 三.8位服务类型 3位优先权字段&#xff08;已经弃用&#xff09;&#xff0c;4位TOS字段&#xff0c;和1位保留 字段&#xff08;必…

企业的固定资产管理怎么操作

企业是一家拥有多台大型设备的工厂&#xff0c;这些设备需要定期进行保养和维护&#xff0c;以确保其正常运转。而企业内部员工由于专业知识和技能的不同&#xff0c;需要分工协作才能更好地完成各项工作任务。因此&#xff0c;在设备资产管理方面&#xff0c;如何实现高效、便…

SpringBoot常用注解

SpringBoot常用注解 SpringBoot摒弃了xml配置方式&#xff0c;改为了全注解驱动。 准备 创建SpringBoot模块 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"ht…

使用JS实现一个简单的观察者模式(Observer)

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 手撸Observer⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领…

Unity之3D物理导航系统

一 介绍 Unity自带寻路(导航)系统是unity官方自带的一种寻路系统。我们可以通过它来制作简单的寻路&#xff0c;比如可以制作点击某个位置&#xff0c;让角色自动的绕开障碍走到目标点的效果&#xff0c;比如可以制作敌人AI&#xff0c;让它可以通过NavMesh绕开障碍追击我方单…

深入理解 JVM 之——字节码指令与执行引擎

更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验 类文件结构 Write Once&#xff0c;Run Anywhere 对于 C 语言从程序到运行需要经过编译的过程&#xff0c;只有经历了编译后&#xff0c;我们所编写的代码才能够翻译为机器可以直接运行的二进制代码&#x…

【web开发】4、JavaScript与jQuery

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、JavaScript与jQuery二、JavaScript常用的基本功能1.插入位置2.注释3.变量4.数组5.滚动字符 三、jQuery常用的基本功能1.引入jQuery2.寻找标签3.val、text、appe…

笔记 | 排序算法实现(Python)

排序算法 一、选择排序二、合并/归并排序三、快速排序四、计数排序 排序类型时间复杂度选择排序(Selection Sort) O ( n 2 ) O(n^{2} ) O(n2)合并/归并排序&#xff08;Merge Sort&#xff09; O ( n log ⁡ n ) O(n\log n ) O(nlogn)快速排序(Quick Sort)平均情况 O ( n log ⁡…

帧动画实现

背景&#xff1a; pag实现痛点 文档&#xff1a;libpag参考文档 1.打包依赖的类型缺失问题 2.pagview初始化文件过大,影响小程序打包 3.兼容性较差关键帧动画实现痛点: 当关键帧变化过多的时候&#xff0c;无法准确分辨出需要写出多少个关键帧&#xff0c;以及各个关键帧的具…

视频监控/视频汇聚/安防视频监控平台EasyCVR如何将默认快照的raw格式改为jpg/base64格式?

视频监控/视频汇聚/安防视频监控平台EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。视频云存储EasyCVR平台能在复…