SpringBoot 统一结果与异常处理
1. controller层 现状与优化
1.1 现状
@GetMapping("select")
public Result select(@RequestParam Long productId){
return Result.success(service.sameProduct(productId));
}
存在以下问题:
return Result.success
为controller层固定写法,冗余且不够简洁不能直观看到返回值类型
1.2 优化后
@GetMapping("select")
public List<String> select(@RequestParam Long productId){
return service.sameProduct(productId);
}
1.3 全局结果统一处理
@Configuration
@RestControllerAdvice
public class CommonResultResponseAdvice implements ResponseBodyAdvice<Object> {
@Resource
private ObjectMapper objectMapper;
@Override
public boolean supports(@NotNull MethodParameter returnType, @NotNull Class<? extends HttpMessageConverter<?>> converterType) {
// 忽略 IgnoreResult 注解标识的类或方法的返回结果转换
return !ignoreResult(returnType, IgnoreResult.class);
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, @NotNull MethodParameter returnType, @NotNull MediaType selectedContentType, @NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType, @NotNull ServerHttpRequest request, @NotNull ServerHttpResponse response) {
String typeName = returnType.getGenericParameterType().getTypeName();
if (Void.TYPE.getName().equals(typeName)) {
return body;
}
if (body instanceof Result) {
return body;
}
if (body instanceof String) {
return objectMapper.writeValueAsString(BaseResponse.success(body));
}
return Result.success(body);
}
private boolean ignoreResult(MethodParameter returnType, Class<? extends Annotation> ignoreAnnotation) {
Method method = returnType.getMethod();
return method.getAnnotation(ignoreAnnotation) != null ||
method.getDeclaringClass().getAnnotation(ignoreAnnotation) != null;
}
}
1.4 自定义注解
忽略返回结果统一处理注解
@Target({METHOD, TYPE})
@Retention(RUNTIME)
@Documented
public @interface IgnoreResult {
}
2、异常处理现状与优化
2.1 现状
if (product != null) {
throw new ProductException(ProductExceptionEnum.SID_EXIST, saveProductParam.getSid());
}
// 或着 ,
if (product != null) {
CommonException.exception(ProductExceptionEnum.PARAM_ERROR);
}
public static void exception(ErrorCodeInterface errorCodeInterface) {
throw new CommonException(errorCodeInterface);
}
数据有效性校验存在以下问题:
- 代码不够简洁,定义过多且大部分仅使用一次的ProductExceptionEnum
- 每个项目各自使用自定义异常,有一定的学习了解成本
2.2 优化后
使用Assert进行业务校验,校验失败后直接抛出异常,中断后续操作
Assert.isNull(product,String.format(ProductExceptionEnum.SID_EXIST.getMessage(),saveProductParam.getSid()));
2.3 统一异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
private static final String INTEREST_CLASS_NAME = "org.springframework.util.Assert";
/**
此处捕获业务 失败 的异常,可考虑不计日志
*/
@ExceptionHandler({IllegalArgumentException.class,IllegalStateException.class})
@ResponseBody
public Result illegalArgumentException(Exception e) {
String msg = e.getMessage();
int maxLength = 200;
if(StringUtils.isBlank(msg) || msg.length() > maxLength){
msg = "请求参数异常";
}
StackTraceElement[] stackTraces = e.getStackTrace();
if (Stream.of(stackTraces).anyMatch(s -> s.getClassName().equals(INTEREST_CLASS_NAME))) {
// 不计日志, 或设置warn级别,便于快速快速响应用户反馈
log.warn("业务校验失败", e);
return Result.error(DefaultErrorCodeEnums.ASSERT_EXCEPTION, msg);
}else {
log.error("xxx", e);
return Result.error(DefaultErrorCodeEnums.PARAM_ERROR, msg);
}
}
}
建议使用 org.springframework.util.Assert 或在此基础上扩展
执行顺序: 如果抛异常--> 全局异常捕获 --> 全局统一结果处理