【SpringBoot】统一功能处理详解
🎬 那我掉的头发算什么:个人主页
🔥 个人专栏: 《javaSE》《数据结构》《数据库》《javaEE》
⛺️待到苦尽甘来日
文章目录
统一数据返回格式
快速入门
统一的数据返回格式使用 @ControllerAdvice 和 ResponseBodyAdvice 的方式实现@ControllerAdvice 表示控制器通知类添加类 ResponseAdvice , 实现 ResponseBodyAdvice 接口,并在类上添加@ControllerAdvice 注解。
packagecom.hbu.book.responseAdvice;importorg.jspecify.annotations.Nullable;importorg.springframework.core.MethodParameter;importorg.springframework.http.MediaType;importorg.springframework.http.server.ServerHttpRequest;importorg.springframework.http.server.ServerHttpResponse;importorg.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;publicclassResponseAdviceimplementsResponseBodyAdvice{@Overridepublicbooleansupports(MethodParameter returnType,Class converterType){returnfalse;}@Overridepublic@NullableObjectbeforeBodyWrite(@NullableObject body,MethodParameter returnType,MediaType selectedContentType,Class selectedConverterType,ServerHttpRequest request,ServerHttpResponse response){returnnull;}}supports 方法:判断是否要执行 beforeBodyWrite 方法. true 为执行,false 不执行。通过该方法可以选择哪些类或哪些方法的 response 要进行处理,其他的不进行处理。

beforeBodyWrite方法: 对response方法进行具体操作处理。
此时还没启动统一处理,这时访问接口返回结果是这样的:

packagecom.hbu.book.model;importcom.hbu.book.enums.ResultCodeEnum;importlombok.Data;@DatapublicclassResult<T>{privateResultCodeEnum code;//-1 未登录 200 正常 -2 出错privateString errMsg;privateT data;publicstatic<T>Resultsuccess(T data){Result result =newResult(); result.setCode(ResultCodeEnum.SUCCESS); result.setErrMsg(""); result.setData(data);return result;}publicstatic<T>Resultfail(String errMsg){Result result =newResult(); result.setCode(ResultCodeEnum.FAIL); result.setErrMsg(errMsg); result.setData(null);return result;}publicstatic<T>Resultfail(String errMsg,T data){Result result =newResult(); result.setCode(ResultCodeEnum.FAIL); result.setErrMsg(errMsg); result.setData(data);return result;}publicstatic<T>Resultunlogin(){Result result =newResult(); result.setCode(ResultCodeEnum.UNLOGIN); result.setErrMsg("用户未登录");return result;}}packagecom.hbu.book.responseAdvice;importcom.hbu.book.model.Result;importorg.jspecify.annotations.Nullable;importorg.springframework.core.MethodParameter;importorg.springframework.http.MediaType;importorg.springframework.http.server.ServerHttpRequest;importorg.springframework.http.server.ServerHttpResponse;importorg.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;publicclassResponseAdviceimplementsResponseBodyAdvice{@Overridepublicbooleansupports(MethodParameter returnType,Class converterType){returntrue;}@Overridepublic@NullableObjectbeforeBodyWrite(@NullableObject body,MethodParameter returnType,MediaType selectedContentType,Class selectedConverterType,ServerHttpRequest request,ServerHttpResponse response){returnResult.success(body);}}启动了之后再次访问接口:

存在问题


但是数据库中确实是插入了这个数据。
其他的操作都是正常的。
packagecom.hbu.book.controller;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;@RequestMapping("/test")@RestControllerpublicclassTestController{@RequestMapping("/t1")publicStringt1(){return"t1";}@RequestMapping("/t2")publicbooleant2(){returntrue;}@RequestMapping("/t3")publicIntegert3(){return200;}}多方测试下,只有返回类型是String的接口才会发生这种错误。并且报错方式都是一样的。
核心原因是 Spring 处理 String 类型返回值的消息转换器优先级问题:当控制器方法返回 String 时,Spring 会优先使用 StringHttpMessageConverter 处理响应,但这个转换器只能处理 String 类型,而 ResponseAdvice 把 String 包装成了 Result 对象,导致转换器尝试将 Result 强制转为 String 时抛出类型转换异常。
代码优化
packagecom.hbu.book.config;importcom.hbu.book.model.Result;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.core.MethodParameter;importorg.springframework.http.MediaType;importorg.springframework.http.server.ServerHttpRequest;importorg.springframework.http.server.ServerHttpResponse;importorg.springframework.web.bind.annotation.ControllerAdvice;importorg.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;importtools.jackson.databind.ObjectMapper;@ControllerAdvicepublicclassResponseAdviceimplementsResponseBodyAdvice{@AutowiredprivateObjectMapper objectMapper;@Overridepublicbooleansupports(MethodParameter returnType,Class converterType){returntrue;}@OverridepublicObjectbeforeBodyWrite(Object body,MethodParameter returnType,MediaType selectedContentType,Class selectedConverterType,ServerHttpRequest request,ServerHttpResponse response){if(body instanceofResult){return body;}if(body instanceofString){return objectMapper.writeValueAsString(Result.success(body));}returnResult.success(body);}}优点
1.方便前端程序员更好的接收和解析后端数据接口返回的数据
2. 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就可以了,因为所有接口都是这样返回的.
3. 有利于项目统一数据的维护和修改.
4. 有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容.
统一异常处理
统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类, @ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。
packagecom.hbu.book.config;importcom.hbu.book.model.Result;importlombok.extern.slf4j.Slf4j;importorg.springframework.web.bind.annotation.ControllerAdvice;importorg.springframework.web.bind.annotation.ExceptionHandler;importorg.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice@ResponseBody@Slf4jpublicclassExceptionAdvice{@ExceptionHandlerpublicObjecthandler(Exception e){ log.error("出现异常:",e);returnResult.fail(e.getMessage());}}测试一下:
packagecom.hbu.book.controller;importorg.apache.ibatis.jdbc.Null;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;@RequestMapping("/test")@RestControllerpublicclassTestController{@RequestMapping("/t1")publicStringt1(){Integer x =7/0;return"t1";}@RequestMapping("/t2")publicbooleant2(){String a =null; a.contains("a");returntrue;}@RequestMapping("/t3")publicIntegert3(){int[] arr =newint[10];System.out.println(arr[100]);return200;}}



对于不同的异常,我们其实可以设置不同的方法:
packagecom.hbu.book.config;importcom.hbu.book.model.Result;importlombok.extern.slf4j.Slf4j;importorg.springframework.web.bind.annotation.ControllerAdvice;importorg.springframework.web.bind.annotation.ExceptionHandler;importorg.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice@ResponseBody@Slf4jpublicclassExceptionAdvice{@ExceptionHandlerpublicObjecthandler(Exception e){ log.error("出现异常:",e);returnResult.fail(e.getMessage());}@ExceptionHandlerpublicObjecthandler(NullPointerException e){ log.error("出现空指针异常:",e);returnResult.fail(e.getMessage());}@ExceptionHandlerpublicObjecthandler(ArithmeticException e){ log.error("出现除0异常:",e);returnResult.fail(e.getMessage());}@ExceptionHandlerpublicObjecthandler(ArrayIndexOutOfBoundsException e){ log.error("出现数组越界异常:",e);returnResult.fail(e.getMessage());}}或者也可以:
packagecom.hbu.book.config;importcom.hbu.book.model.Result;importlombok.extern.slf4j.Slf4j;importorg.springframework.web.bind.annotation.ControllerAdvice;importorg.springframework.web.bind.annotation.ExceptionHandler;importorg.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice@ResponseBody@Slf4jpublicclassExceptionAdvice{@ExceptionHandlerpublicObjecthandler(Exception e){ log.error("出现异常:",e);returnResult.fail(e.getMessage());}// @ExceptionHandler// public Object handler(NullPointerException e){// log.error("出现空指针异常:",e);// return Result.fail(e.getMessage());// }// @ExceptionHandler// public Object handler(ArithmeticException e){// log.error("出现除0异常:",e);// return Result.fail(e.getMessage());// }// @ExceptionHandler// public Object handler(ArrayIndexOutOfBoundsException e){// log.error("出现数组越界异常:",e);// return Result.fail(e.getMessage());// }@ExceptionHandler(NullPointerException.class)publicObjecthandler1(Exception e){ log.error("出现异常:",e);returnResult.fail(e.getMessage());}@ExceptionHandler(ArrayIndexOutOfBoundsException.class)publicObjecthandler2(Exception e){ log.error("出现异常:",e);returnResult.fail(e.getMessage());}}我们一般不把异常的具体内容返回给前端,因此可以这样处理:



这里我们没有针对t1做特别的异常处理,最后打印出来的是内部异常。
所以说,当没有匹配的异常时,会自动去寻找有没有报的异常的父类的处理方法,然后执行处理方法。