
🎬 那我掉的头发算什么 :个人主页
🔥 个人专栏 : 《javaSE》《数据结构》《数据库》《javaEE》
⛺️待到苦尽甘来日

文章目录
统一数据返回格式
快速入门
统一的数据返回格式使用 @ControllerAdvice 和 ResponseBodyAdvice 的方式实现@ControllerAdvice 表示控制器通知类添加类 ResponseAdvice , 实现 ResponseBodyAdvice 接口,并在类上添加@ControllerAdvice 注解。
java
package com.hbu.book.responseAdvice;
import org.jspecify.annotations.Nullable;
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.servlet.mvc.method.annotation.ResponseBodyAdvice;
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return false;
}
@Override
public @Nullable Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return null;
}
}
supports 方法:判断是否要执行 beforeBodyWrite 方法. true 为执行,false 不执行。通过该方法可以选择哪些类或哪些方法的 response 要进行处理,其他的不进行处理。

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

java
package com.hbu.book.model;
import com.hbu.book.enums.ResultCodeEnum;
import lombok.Data;
@Data
public class Result<T> {
private ResultCodeEnum code; //-1 未登录 200 正常 -2 出错
private String errMsg;
private T data;
public static <T> Result success(T data){
Result result = new Result();
result.setCode(ResultCodeEnum.SUCCESS);
result.setErrMsg("");
result.setData(data);
return result;
}
public static <T> Result fail(String errMsg){
Result result = new Result();
result.setCode(ResultCodeEnum.FAIL);
result.setErrMsg(errMsg);
result.setData(null);
return result;
}
public static <T> Result fail(String errMsg, T data){
Result result = new Result();
result.setCode(ResultCodeEnum.FAIL);
result.setErrMsg(errMsg);
result.setData(data);
return result;
}
public static <T> Result unlogin(){
Result result = new Result();
result.setCode(ResultCodeEnum.UNLOGIN);
result.setErrMsg("用户未登录");
return result;
}
}
java
package com.hbu.book.responseAdvice;
import com.hbu.book.model.Result;
import org.jspecify.annotations.Nullable;
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.servlet.mvc.method.annotation.ResponseBodyAdvice;
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public @Nullable Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return Result.success(body);
}
}
启动了之后再次访问接口:

存在问题



但是数据库中确实是插入了这个数据。

其他的操作都是正常的。
java
package com.hbu.book.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/test")
@RestController
public class TestController {
@RequestMapping("/t1")
public String t1(){
return "t1";
}
@RequestMapping("/t2")
public boolean t2(){
return true;
}
@RequestMapping("/t3")
public Integer t3(){
return 200;
}
}
多方测试下,只有返回类型是String的接口才会发生这种错误。并且报错方式都是一样的。
核心原因是 Spring 处理 String 类型返回值的消息转换器优先级问题:当控制器方法返回 String 时,Spring 会优先使用 StringHttpMessageConverter 处理响应,但这个转换器只能处理 String 类型,而 ResponseAdvice 把 String 包装成了 Result 对象,导致转换器尝试将 Result 强制转为 String 时抛出类型转换异常。
代码优化
java
package com.hbu.book.config;
import com.hbu.book.model.Result;
import org.springframework.beans.factory.annotation.Autowired;
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 tools.jackson.databind.ObjectMapper;
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
@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) {
if(body instanceof Result){
return body;
}
if(body instanceof String){
return objectMapper.writeValueAsString(Result.success(body));
}
return Result.success(body);
}
}
优点
1.方便前端程序员更好的接收和解析后端数据接口返回的数据
-
降低前端程序员和后端程序员的沟通成本,按照某个格式实现就可以了,因为所有接口都是这样返回的.
-
有利于项目统一数据的维护和修改.
-
有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容.
统一异常处理
统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类, @ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。
java
package com.hbu.book.config;
import com.hbu.book.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@ResponseBody
@Slf4j
public class ExceptionAdvice {
@ExceptionHandler
public Object handler(Exception e){
log.error("出现异常:",e);
return Result.fail(e.getMessage());
}
}
测试一下:
java
package com.hbu.book.controller;
import org.apache.ibatis.jdbc.Null;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/test")
@RestController
public class TestController {
@RequestMapping("/t1")
public String t1(){
Integer x = 7/0;
return "t1";
}
@RequestMapping("/t2")
public boolean t2(){
String a = null;
a.contains("a");
return true;
}
@RequestMapping("/t3")
public Integer t3(){
int[] arr = new int[10];
System.out.println(arr[100]);
return 200;
}
}




对于不同的异常,我们其实可以设置不同的方法:
java
package com.hbu.book.config;
import com.hbu.book.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@ResponseBody
@Slf4j
public class ExceptionAdvice {
@ExceptionHandler
public Object handler(Exception e){
log.error("出现异常:",e);
return Result.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());
}
}
或者也可以:
java
package com.hbu.book.config;
import com.hbu.book.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@ResponseBody
@Slf4j
public class ExceptionAdvice {
@ExceptionHandler
public Object handler(Exception e){
log.error("出现异常:",e);
return Result.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)
public Object handler1(Exception e){
log.error("出现异常:",e);
return Result.fail(e.getMessage());
}
@ExceptionHandler(ArrayIndexOutOfBoundsException.class)
public Object handler2(Exception e){
log.error("出现异常:",e);
return Result.fail(e.getMessage());
}
}
我们一般不把异常的具体内容返回给前端,因此可以这样处理:



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