不做全局统一异常时,当项目中出现异常,会报500.
如:
@PostMapping(value="/testerror")
@ApiOperation(value = "获取用户信息")
public Result.Base testerror(HttpServletRequest request){
Integer inte = 1/0;
return new Result.Success<>(inte);
}
访问接口时报500错误:
2.处理异常
利用@ControllerAdvice和@ExceptionHandler定义一个统一异常处理类
因此只需要添加如下类即可:
如果是自定义异常,可单独再配。
package com.wps.education.exception;
import com.wps.education.LoginInterceptor.LoginInterceptor;
import com.wps.education.common.response.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result.Base jsonErrorHandler(HttpServletRequest req, Exception e) throws Exception {
logger.error("{} | {}", req.getRequestURL().toString(), e.getMessage());
e.printStackTrace();
return new Result.ErrorData<>(e.getMessage());
}
}
接口返回:
后台输出:
3.项目级处理方式
上面的例子可以全局处理异常,并返回错误内容。
但在项目中使用时,不能给用户显示500,也不能显示这些错误内容。而是定义一套错误码,后台统一返回格式,前端处理接口中的错误码,给出相应的业务处理,或更合理的提示信息。
import org.apache.http.HttpStatus;
/**
* 定义错误类
*/
public abstract class ErrorCode {
/**
3.1 code:0表示成功, -1表示未知错误
3.1 系统集合(1-99):
3.4 业务相关code集合 (100-399):
*/
/** 成功 */
public static final Integer SUCCESS = 0;
/** 未知 */
public static final Integer UNKNOWN = -1;
/** 未知错误 */
public static final Result.Error ERROR_UNKNOWN = new Result.Error(UNKNOWN, "unknown error", HttpStatus.SC_INTERNAL_SERVER_ERROR);
/** system error code, 1-99 */
/** 参数错误,不正确的参数 */
public static final Result.Error ARGUMENT_INVALID = new Result.Error(1, "parameter is invalid", HttpStatus.SC_BAD_REQUEST);
/** 没有Token */
public static final Result.Error NO_TOKEN_ERROR = new Result.Error(2, "no token error", HttpStatus.SC_OK);
/** 没找到对应的对象 */
public static final Result.Error NO_SUCH_ELEMENT_ERROR = new Result.Error(3, "no such element error", HttpStatus.SC_OK);
/** 没有足够的权限 */
public static final Result.Error NO_RIGHT_ERROR = new Result.Error(4, "current user have no right do this", HttpStatus.SC_OK);
/** Token过期 */
public static final Result.Error TOKEN_EXPIRED_ERROR = new Result.Error(5, "token expired", HttpStatus.SC_OK);
/** 用户相关code集合(100-399)*/
/** 登陆参数不正确 */
public static final Result.Error LOGIN_PARAM_ERROR = new Result.Error(100, "login param error, or login expire", HttpStatus.SC_BAD_REQUEST);
/** 创建用户失败*/
public static final Result.Error INSERT_USER_ERROR = new Result.Error(161,"insert user error", HttpStatus.SC_OK);
}
2.定义接口返回格式类Result
package com.gary.exception;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Getter;
import lombok.Setter;
import org.apache.http.HttpStatus;
/**
* 返回客户端结果类
*/
public class Result extends ErrorCode {
@Getter
@Setter
public static class Base {
private Integer code;
public Base(Integer code) {
this.code = code;
}
void check() {
if (code == null)
throw new IllegalArgumentException("code can not be null");
}
}
@Getter
@Setter
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public static class Success<T> extends Base {
private T data;
private Integer status;
public Success(T data) {
super(SUCCESS);
this.data = data;
check();
}
public Success() {
super(SUCCESS);
this.data = (T) "success";
check();
}
void check() {
super.check();
}
}
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
@Getter
@Setter
public static class Error extends Base {
private String result;
private Integer httpStatus;
public Error(Integer code, String result, Integer status) {
super(code);
this.result = result;
this.httpStatus = status;
check();
}
void check() {
super.check();
if (result == null || httpStatus == 0)
throw new IllegalArgumentException("result can not be null and http status cannot be zero");
}
}
@Getter
@Setter
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
public static class ErrorData<T> extends Base {
private T data;
public ErrorData(T data) {
super(HttpStatus.SC_INTERNAL_SERVER_ERROR);
this.data = data;
}
void check() {
super.check();
}
}
}
3.添加自定义异常类
派生自运行时异常,将Result的Error类作为成员:
package com.gary.exception;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CustomException extends RuntimeException{
private static final long serialVersionID = 1L;
private Result.Error error;
public CustomException() {
super();
}
public CustomException(String message) {
super(message);
}
public CustomException(String message, Throwable cause) {
super(message, cause);
}
public CustomException(Throwable cause) {
super(cause);
}
public CustomException(Result.Error error) {
super(error.getResult());
checkErrorMessage(error);
this.error = error;
}
public CustomException(Result.Error error, String message) {
super(message);
checkErrorMessage(error);
this.error = error;
}
public CustomException(Result.Error error, String message, Throwable cause) {
super(message, cause);
checkErrorMessage(error);
this.error = error;
}
public CustomException(Result.Error error, Throwable cause) {
super(cause);
checkErrorMessage(error);
this.error = error;
}
public void checkErrorMessage(Result.Error error){
if(error.getCode()!=null && error.getCode()==0){
throw new RuntimeException("errorMessage code must not be 0");
}
}
}
4.完善统一异常处理
package com.gary.exception;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
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.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result.Base jsonErrorHandler(HttpServletRequest req, Exception e) throws Exception {
logger.error("{} | {}", req.getRequestURL().toString(), e.getMessage());
e.printStackTrace();
return new Result.ErrorData<>(e.getMessage());
}
@ExceptionHandler(CustomException.class)
@ResponseBody
public ResponseEntity
Result.Error error = e.getError();
HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
if (error != null && error.getHttpStatus() != null) {
httpStatus = getHttpStatus(e.getError().getHttpStatus());
} else {
logger.error("unknown error ", e);
error = ErrorCode.ERROR_UNKNOWN;
}
return handleResponse(error, httpStatus);
}
private ResponseEntity
return handleResponse(error, null, status);
}
private ResponseEntity
return new ResponseEntity<>(error, headers, status);
}
private HttpStatus getHttpStatus(Integer statusCode){
HttpStatus httpStatus=HttpStatus.INTERNAL_SERVER_ERROR;
try {
httpStatus=HttpStatus.valueOf(statusCode);
} catch (IllegalArgumentException e) {
logger.error("get HttpStatus error",e);
}
return httpStatus;
}
}
5.使用
这样的好处是正常业务和有异常的情况,接口返回前端的结构一样,前端可统一处理返回格式,获取code进行判断处理,是0则正常,其他值根据业务作不同处理。
正常业务返回:
@GetMapping("/hello")
public Result.Base hello(){
System.out.println("/hello");
return new Result.Success<>("hello world");
}
未处理的异常:
@GetMapping("/testerror")
public Result.Base error(){
System.out.println("/error");
Integer inte = 1/0;
return new Result.Success<>("hello world");
}
默认code为500,内容为错误内容:
自定义异常:
定义错误值:
public static final Result.Error NUMBER_ERROR = new Result.Error(300,"数字不正确", HttpStatus.SC_OK);
Controller中直接返回错误
@GetMapping("/testerror")
public Result.Base error(){
System.out.println("/error");
try{
Integer inte = 1/0;
}catch (Exception e){
return Result.NUMBER_ERROR;
}
return new Result.Success<>("hello world");
}
或者主动抛自定义异常:
@GetMapping("/testerror")
public Result.Base error(){
System.out.println("/error");
try{
Integer inte = 1/0;
}catch (Exception e){
// return Result.NUMBER_ERROR;
throw new CustomException(Result.NUMBER_ERROR);
}
return new Result.Success<>("hello world");
}
效果一样:
两者的差别是:前者只能在conroller中使用,因为是直接返回错误。后者可以在任意地方,如service层dao层。
6.说明
其中用到了httpstatus,和lombok,因此需要添加这两个依赖包:
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.20version>
dependency>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
dependency>