Spring 两种全局异常比较---泛型,建造者设计模式
把我两次书写全局异常捕获统一处理返回JSON的经验分享给大家。其中真的还是有点小进步的。里面涉及到泛型和建造者的使用,还是能学到挺多的。
实现的思路是
- 全局异常的抓取。
- 封装统一返回结果对象
第一种方式:我记得好像是参考阿里巴巴的黄勇同志写出来的。其实很简单通过ControllerAdvice做一Controller的AOP。然后通过拦截对应的Exception可以自定义返回对应的HttpStatus。然后封装Reponse统一返回数据即可。甚至连正常返回都可以封装为一个Exception返回,可以参照下面的StatusSuccess,具体实现比较简单。
别人亲测,这里有一个天大的坑,入门的小伙伴要注意了。事务回滚的异常处理是需要extend RunTimeException 而继承Exception 是没办法做到事务回滚的。
第二种方式:是我参考简书小伙伴的文章写出来的。另外加入了建造者设计模式,个人见解使用简单,代码可读性更好。实现思路更第一种类似,但是在代码上有了比较大的改动。尤其是泛型的使用,和设计模式的使用。
两种方式的比较:
-
弱点一:Controller返回的都是Object 。虽然可以满足大部分的业务,但是过了两个月,前端问你这个接口返回的是什么东西。多半你要找到你的dao层才能判断了。而第二种方式则可以避免这种问题。使用了泛型。很容易让你清楚的知道data里面放到是什么
-
弱点二:代码可读性更强了。不会在Service里面throw new StatusSuccess()了。这段代码始终让我感觉奇奇怪怪的又说不是是啥。
第二种实现方式,则是在Controller里面使用Builder设计模式,让代码更加优雅帅气,MVC分层明显,代码可读性强,真的是一个很棒的实现方式。
第二种实现方式,直接上代码了。
1 在controller 的实现效果。代码简洁。可读性👍。
@RestController
@RequestMapping("/app/user")
public class ApiController {
@RequestMapping("data")
public RestResult<Object> text() throws Exception {
throw new Exception();
}
@RequestMapping("getData")
public RestResult<String> getData() throws Exception {
return RestResultGenrator.build(
xxxService.getData();
);
}
}
2 统一异常抓取。注意看我的RestResultBuild的构建。看起来更加美观。
/**
* RestExceptionHandler
*
* @author zf
* @date 9/23/16
*/
@ControllerAdvice(annotations = RestController.class)
public class RestExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RestExceptionHandler.class);
@ExceptionHandler
@ResponseBody
@ResponseStatus(HttpStatus.OK)
private <T> RestResult<T> runtimeExceptionHandler(Exception e){
LOGGER.error("------->error !" ,e);
return new RestResultBuilder<T>()
.setErrorCode(ErrorCode.ERROR)
.setMessage(ErrorCode.ErrorMessage.ERROR)
.build();
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
@ResponseStatus(HttpStatus.OK)
private <T> RestResult<T> illegalParamsExceptionHandler(MethodArgumentNotValidException e) {
LOGGER.error("---------> invalid request!", e);
return new RestResultBuilder<T>()
.setErrorCode(ErrorCode.ERROR_METHOD)
.setMessage(ErrorCode.ErrorMessage.ERROR_METHOD)
.build();
}
@ExceptionHandler(ExpireException.class)
@ResponseBody
@ResponseStatus(HttpStatus.OK)
private <T> RestResult<T> expireHandler(MethodArgumentNotValidException e) {
LOGGER.error("---------> invalid expire!", e);
return new RestResultBuilder<T>()
.setErrorCode(ErrorCode.EXPIRED)
.setMessage(ErrorCode.ErrorMessage.EXPIRED)
.build();
}
}
3 使用统一泛型处理,增强Controller的可读性。
/**
* RestResult
*
* @author zf
* @date 9/23/16
*/
public class RestResult <T>{
private int errorCode;
private String message;
private T data;
private RestResult(){}
protected RestResult(int errorCode, String message, T data) {
this.errorCode = errorCode;
this.message = message;
this.data = data;
}
public int getErrorCode() {
return errorCode;
}
public RestResult setErrorCode(int errorCode) {
this.errorCode = errorCode;
return this;
}
public String getMessage() {
return message;
}
public RestResult<T> setMessage(String message) {
this.message = message;
return this;
}
public T getData() {
return data;
}
public RestResult<T> setData(T data) {
this.data = data;
return this;
}
}
4 使用builder设计模式构建统一返回对象。
/**
* RestResultBuilder
*
* @author zf
* @date 9/23/16
*/
public class RestResultBuilder<T> {
private int errorCode = ErrorCode.VALID;
private String message =ErrorCode.ErrorMessage.VALID;;
private T data ;
protected RestResultBuilder<T> setErrorCode(int errorCode){
this.errorCode = errorCode;
return this;
}
public RestResultBuilder<T> setMessage(String message){
this.message = message;
return this;
}
public RestResultBuilder<T> setData(T data){
this.data = data;
return this;
}
public RestResult<T> build(){
return new RestResult<T>(errorCode,message,data);
}
}
5 我是为了不想写new三个字母,所以写了第五个类,Genrator。
public class RestResultGenrator {
public static <T> RestResult<T> build(){
return build(null);
}
public static <T> RestResult<T> build(T t){
return new RestResultBuilder<T>().setData(t).build();
}
}
下面是第一种实现方式。
- 全局异常抓取类
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
Logger logger = Logger.getGlobal();
/**
* 400 - Bad Request
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public Response handleHttpMessageNotReadableException(
HttpMessageNotReadableException e) {
logger.info("参数解析失败" + e);
return new Response().failure("could_not_read_json");
}
/**
* 405 - Method Not Allowed
*/
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Response handleHttpRequestMethodNotSupportedException(
HttpRequestMethodNotSupportedException e) {
logger.info("不支持当前请求方法" + e);
return new Response().failure("request_method_not_supported");
}
/**
* 415 - Unsupported Media Type
*/
@ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public Response handleHttpMediaTypeNotSupportedException(Exception e) {
logger.info("不支持当前媒体类型" + e);
return new Response().failure("content_type_not_supported");
}
/**
* 500 - Internal Server Error
*/
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(Exception.class)
public Response handleException(Exception e) {
e.printStackTrace();
return new Response().failure(e.getMessage());
}
/**
* - Internal Server Error
*/
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(StatusSuccess.class)
public Response handleSuccessStatus(StatusSuccess e) {
logger.info("StatusSuccess");
return new Response(Response.SUCCESS_CODE, e.getMessage(), e.getData());
}
/**
* 500 - Internal Server Error
*/
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(ExpireException.class)
public Response handleExipreStatus(Exception e) {
logger.info("内部错误");
return new Response().timeOut(e.getMessage());
}
/**
* 500 - 内部错误
*/
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(IllegalArgumentException.class)
public Response parameterException(IllegalArgumentException e) {
logger.info("非法参数" + e.toString());
return new Response().failure(e.getMessage());
}
/**
* 500 - Internal Server Error
*/
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(ShopStatusException.class)
public Response errorShopStatus(Exception e) {
logger.info("店铺状态异常");
return new Response().shopStatusException(e.getMessage());
}
/**
* 订单状态异常拦截器
*
* @author yangyanchao
* @date 2016年8月16日
* @param e
* @return
*/
public Response orderStatusException(Exception e) {
return new Response().shopStatusException(e.getMessage());
}
}
- 统一返回实体类
/**
* Response
*
* @author zf
* @date 16/3/21
*/
public class Response {
private static final String OK = "success";
private static final String ERROR = "failure";
private static final String TIMEOUT = "expired";
public static final int SUCCESS_CODE = 1;
public static final int FAIL_CODE = 0;
public static final int TOKEN_INVALID = -1;
public static final int SHOP_ABNORMAL = -2;
@JsonView(BaseView.BaseResponse.class)
private int status;
@JsonView(BaseView.BaseResponse.class)
private String msg;
@JsonView(BaseView.BaseResponse.class)
private Object data;
public Response success() {
this.status = SUCCESS_CODE;
this.msg = OK;
return this;
}
public Response success(Object data) {
this.status = SUCCESS_CODE;
this.msg = OK;
this.data = data;
return this;
}
public Response failure() {
this.status = FAIL_CODE;
this.msg = ERROR;
return this;
}
public Response() {
}
public Response(int status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public Response expireException(String msg) {
this.status = TOKEN_INVALID;
this.msg = msg;
return this;
}
public Response timeOut(String msg) {
this.status = TOKEN_INVALID;
this.msg = msg;
return this;
}
public Response shopStatusException(String msg) {
this.status = SHOP_ABNORMAL;
this.msg = msg;
return this;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public void setData(Object data) {
this.data = data;
}
public Response failure(String message) {
this.status = FAIL_CODE;
this.msg = message;
return this;
}
public Object getData() {
return data;
}
}
3.service 使用例子
/**
* 保存用户
*
* @param principle
* 用户信息
* @param uadId
* 用户地址id
* @return 保存
*/
public void save(Principle principle, Integer uadId) throws NotSamePeopleException, StatusSuccess {
AsUserAddress userAddress = asUserAddressMapper.selectByPrimaryKey(uadId);
if (userAddress.getUserId().equals(principle.getUserId())) {
principle.setAddressId(uadId);
AsUser u = new AsUser();
u.setUserId(principle.getUserId());
u.setAddressId(uadId);
asUsersMapper.updateByPrimaryKeySelective(u);
throw new StatusSuccess();
} else {
throw new NotSamePeopleException();
}
}
3 . 正确返回的异常处理,注意看这里是封装了data 的。
/**
* OrderStatusException
*
* @author zf
* @date 16/7/14
*/
public class StatusSuccess extends Exception{
private static String msg = "操作成功";
private Object data;
public StatusSuccess(){
super(msg);
}
public StatusSuccess(String msg) {
super(msg);
}
public StatusSuccess(Object data) {
super(msg);
this.data = data;
}
public StatusSuccess(String message, Object data) {
super(message);
this.data = data;
}
public static String getMsg() {
return msg;
}
public static void setMsg(String msg) {
StatusSuccess.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
最后用postMan得出最终相同的结果。
Paste_Image.png