Spring Boot提供了一种高效、优雅的方式来处理REST API的返回数据和异常情况,确保API的响应数据结构统一,并且能够优雅的处理服务端异常。以下是如何在Spring Boot应用中实现响应体的统一包装和全局异常处理。
【CV即用】 使用Axios拦截器实现前端HTTP请求的统一处理
统一响应体结构
为了确保API的响应格式具有一致性,我们可以创建一个ApiResponse
类作为API响应的标准格式包装,这个类包含三个属性:code
, message
, 和 data
,代表了状态码、返回信息和数据负载。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
}
控制器增强:响应体包装
在Spring中使用@ControllerAdvice
和ResponseBodyAdvice
接口,可以对响应体进行预处理。以下是一个ApiResponseAdvice
类的示例,该类确保所有的响应体都将会被包装在ApiResponse
对象中。
import cn.hutool.json.JSONUtil;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
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 javax.annotation.Resource;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
@ControllerAdvice
public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 检查方法的返回类型,跳过 ResponseEntity<Resource> 类型或其他特定类型的处理
if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
Type type = returnType.getGenericParameterType();
if (type instanceof ParameterizedType) {
Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
for (Type argument : typeArguments) {
if (argument instanceof Class && Resource.class.isAssignableFrom((Class<?>) argument)) {
return false; // 对于文件下载等不做处理
}
// 添加其他逻辑以正确处理 WildcardType 或其他复杂类型
}
}
}
// 在此处添加其他条件检查,以决定是否应用这个ResponseBodyAdvice
// 默认情况下适用这个ResponseBodyAdvice
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 设置字符编码为UTF-8
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
// 如果body已经是ApiResponse,则无需处理
if (body instanceof ApiResponse) {
return body;
}
// 对于String特殊处理,因为Spring默认StringHttpMessageConverter将String转换成HTTP响应体时
// 无法正确处理非String类型(如果直接包装成ApiResponse的话)
if (body instanceof String) {
// 把处理后的ApiResponse对象转换成JSON字符串,因为直接返回ApiResponse对象会和String的预期类型冲突。
ApiResponse<Object> res = new ApiResponse<>(HttpStatus.OK.value(), "成功", body);
return JSONUtil.toJsonStr(JSONUtil.parse(res));
}
// 其他情况,把body包装在ApiResponse里面返回
return new ApiResponse<>(HttpStatus.OK.value(), "成功", body);
}
}
控制器示例:API接口
我们创建了一个TestController
类,其中包含了几种HTTP方法示例。一个是通过@GetMapping
处理GET请求的示例;另一个是用@PostMapping
处理POST请求的示例;第三个是一个将引发异常的GET方法。
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/get")
public String test() {
return "成功";
}
@PostMapping("/post")
public String post(@RequestBody String body) {
log.info("接收===>{}", body);
return body;
}
@GetMapping("/exception")
public String exception() {
int i = 1 / 0;
return "";
}
}
自定义异常类
在开发复杂的应用时,我们可能需要定义自定义异常类以更好地描述特定的错误情况。自定义异常可以帮助我们在出现特定业务规则违反或异常情况时,提供更加详细和具体的错误信息。
在示例代码中,我们定义了一个名为CustomException
的自定义异常类,它继承自RuntimeException
。
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class CustomException extends RuntimeException {
public CustomException(String message) {
super(message);
}
}
通过@ResponseStatus
注解,我们指定了当CustomException
被抛出时,默认的HTTP状态码是400 Bad Request
。这意味着如果在控制器方法中抛出这个异常,Spring框架会捕获它,并自动返回状态码为400的响应给客户端。
自定义异常通常用于业务逻辑错误或应用的某些特定状态下,相比较于Java标准库中的异常,自定义异常提供了更好的可读性和错误追踪能力。在全局异常处理器中捕获到这个自定义异常时,我们可以返回一个包含错误详情的ApiResponse
,这使得API的消费者能够轻松地理解和处理错误。
使用自定义异常的好处包括:
提高代码的可读性和维护性
实现错误处理的一致性
方便和前端进行协议上的约定,提高前后端分离开发的效率
请注意,总是建议在抛出自定义异常前进行仔细考虑,确保像这样的自定义异常是为了处理特定的业务场景,而不是简单地替换标准异常。
全局异常处理
除了响应体增强,我们还需要一个全局异常处理机制来捕获和包装异常。我们可以通过@ControllerAdvice
注解创建一个全局异常处理类GlobalExceptionHandler
。
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理所有未知的异常
*
* @param ex
* @param request
* @return
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleGlobalException(Exception ex, WebRequest request) {
ApiResponse<String> response = new ApiResponse<>();
response.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setMessage(ex.getMessage());
response.setData(null);
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* 处理自定义异常
*
* @param ex
* @param request
* @return
*/
@ExceptionHandler(CustomException.class)
public ResponseEntity<ApiResponse<String>> handleCustomException(CustomException ex, WebRequest request) {
ApiResponse<String> response = new ApiResponse<>();
response.setCode(HttpStatus.BAD_REQUEST.value());
response.setMessage(ex.getMessage());
response.setData(null);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
}
这个类包含了处理所有异常handleGlobalException
的方法,当程序抛出异常时,方法会自动捕获,并返回一个格式良好的ApiResponse
对象。还有一个专门处理自定义异常handleCustomException
的方法。
这两种机制结合在一起,无论是正常的响应还是异常,都可以确保用户接收到统一格式的JSON响应。通过这种方式,我们能提高API的可用性和客户端开发人员的效率。