统一返回值类型无论项目前后端是否分离都是非常必要的,方便对接接口的开发人员更加清晰地知道这个接口的调用是否成功(不能仅仅简单地看返回值是否为 null 就判断成功与否,因为有些接口的设计就是如此)。
返回结果类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private int code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data);
}
public static <T> Result<T> success(String message, T data) {
return new Result<>(ResultEnum.SUCCESS.getCode(), message, data);
}
public static Result<?> failed() {
return new Result<>(ResultEnum.COMMON_FAILED.getCode(), ResultEnum.COMMON_FAILED.getMessage(), null);
}
public static Result<?> failed(String message) {
return new Result<>(ResultEnum.COMMON_FAILED.getCode(), message, null);
}
public static Result<?> failed(ResultEnum enumEx){
return new Result<>(enumEx.getCode(),enumEx.getMessage(),null);
}
public static Result<?> failed(ResultEnum enumEx,String message){
return new Result<>(enumEx.getCode(),message,null);
}
public static Result<?> failed(BaseException ex){
return new Result<>(ex.getCode(),ex.getMsg(),null);
}
public static <T> Result<T> instance(Integer code, String message, T data) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
result.setData(data);
return result;
}
}
枚举类父接口
public interface IResponseEnum {
public int getCode();
public String getMessage();
}
基础异常类:
public abstract class BaseException extends RuntimeException{
private static final long serialVersionUID = 1L;
private int code;
private String msg;
public BaseException(int code,String msg){
super(msg);
this.code = code;
this.msg = msg;
}
public BaseException(IResponseEnum exEnum){
super(exEnum.getMessage());
this.code = exEnum.getCode();
this.msg = exEnum.getMessage();
}
public BaseException(int code,String msg,Throwable cause){
super(msg,cause);
this.code = code;
this.msg = msg;
}
public BaseException(IResponseEnum exEnum,Throwable cause){
super(exEnum.getMessage(),cause);
this.code = exEnum.getCode();
this.msg = exEnum.getMessage();
}
public BaseException(int code,String msg,Throwable cause,
boolean enableSuppression,
boolean writableStackTrace){
super(msg,cause,enableSuppression,writableStackTrace);
this.code = code;
this.msg = msg;
}
public BaseException(IResponseEnum exEnum,Throwable cause,
boolean enableSuppression,
boolean writableStackTrace){
super(exEnum.getMessage(),cause,enableSuppression,writableStackTrace);
this.code = exEnum.getCode();
this.msg = exEnum.getMessage();
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
常用返回结果枚举类
public enum ResultEnum implements IResponseEnum {
SUCCESS(2001,"接口调用成功!"),
COMMON_FAILED(2001, "接口调用失败"),
private int code;
private String message;
ResultEnum(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
定义好统一的返回结果类型后,就可以在controller中使用了,但是如果controller中每个方法结尾都写一段构建返回结果的操作,这些都是很重复的工作,所以还要继续想办法进一步处理统一返回结构。
Spring 中提供了一个接口 ResponseBodyAdvice 以及@RestControllerAdvice注解,能帮助我们实现上述需求:
public interface ResponseBodyAdvice<T> {
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
}
ResponseBodyAdvice 是对 Controller 返回的内容在 HttpMessageConverter 进行类型转换之前拦截,进行相应的处理操作后,再将结果返回给客户端。但是只拦截加了@RequestMapping和@ResponseBody的方法的返回结果,这点要注意。
supports:判断是否要交给 beforeBodyWrite 方法执行,ture:需要;false:不需要
beforeBodyWrite:对 response 进行具体的处理
ResponseAdvice 示例:
@RestControllerAdvice(basePackages="com.example.demo.controller")
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@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;
}else if(body instanceof String){
try {
return new ObjectMapper().writeValueAsString(Result.success(body));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}else if(body instanceof Throwable){
return Result.failed(ResultEnum.COMMON_FAILED,"接口调用出错,请联系管理员!");
}
return Result.success(body);
}
}
注意事项:
1、如果controller方法中返回的是String类型,但是加了@ResponseBody注解,那么在ResponseBodyAdvice中拦截到String类型并且处理完后需要最后返回一个String类型(可以转换成json字符串),否则会报错。
业务异常类:
public class BusinessException extends BaseException {
private static final long serialVersionUID = 1L;
public BusinessException(int code, String msg) {
super(code, msg);
}
public BusinessException(IResponseEnum exEnum) {
super(exEnum);
}
public BusinessException(int code, String msg, Throwable cause) {
super(code, msg, cause);
}
public BusinessException(IResponseEnum exEnum, Throwable cause) {
super(exEnum, cause);
}
public BusinessException(int code, String msg, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(code, msg, cause, enableSuppression, writableStackTrace);
}
public BusinessException(IResponseEnum exEnum, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(exEnum, cause, enableSuppression, writableStackTrace);
}
}
业务异常枚举类:
public enum BusinessExceptionEnum implements IResponseEnum {
VALIDATE_FAILED(3001, "参数校验失败"),
USERNOTFOUND(3002,"用户不存在");
private int code;
private String message;
BusinessExceptionEnum(int code, String message){
this.code = code;
this.message = message;
}
@Override
public int getCode() {
return this.code;
}
@Override
public String getMessage() {
return this.message;
}
}
定义好异常类以及对于的异常枚举类,以后新增的异常类型,就只需要在枚举类中新增即可,无需再新增异常类。
定义好异常类后,就可以使用了,使用@ExceptionHandler和@RestControllerAdvice就可以拦截异常了。
@RestControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler(BusinessException.class)
public Result handleBusinessException(BusinessException e){
return Result.failed(e);
}
//返回错误页面
@ExceptionHandler(RuntimeException.class)
public ModelAndView handleError(RuntimeException e){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error");
modelAndView.addObject("code", 500);
modelAndView.addObject("msg", "服务器异常!");
return modelAndView;
}
@ExceptionHandler(Exception.class)
public Throwable handleException(Exception e){
return e;
}
}
controller方法:
@RequestMapping("/testException")
public String testException() throws Exception {
//throw new Exception();
throw new RuntimeException();
//throw new BusinessException(BusinessExceptionEnum.USERNOTFOUND);
}
在 @ExceptionHandler中定义好拦截的异常类型即可拦截指定的异常了。使用@RestControllerAdvice注解表示拦截Controller里的@RequestMapping方法。
注意事项:
1、@RestControllerAdvice是在@ControllerAdvice的基础上加上了@ResponseBody,表明加了该注解的方法返回类型是@ResponseBody。所以这里的返回结果也会被ResponseBodyAdvice拦截,所以在ResponseBodyAdvice中也需要统一处理出现异常后的返回结果,参考上文ResponseAdvice类代码。如果使用@ControllerAdvice注解,但是不加上@ResponseBody,则不会被拦截
2、在单个Controller中也可以定义@ExceptionHandler方法作为本Controller的异常处理方法。优先级是,Controller中的异常处理方法>全局的异常处理方法。定义的处理异常类型越详细优先级越高。比如:如果出现RuntimeException,那么它会被处理RuntimeException异常的方法拦截,不会被处理Exception异常的方法拦截。但是如果处理Exception异常的方法定义在它自己的Controller中,那么它只会被本Controller中的异常拦截方法拦截。