微服务中,全局异常处理是一个必须解决的客观课题,如果这些异常处理不好的话,就会给用户看到一些不友好的错误信息,比如客户看到了空指针异常,SQL执行错误等异常,客户肯定是看不懂的,从而大大降低用户体验。
@controllerAdvice + @ExpectionHandler
,接下来就以这种方式来实现一些全局异常处理。@ControllerAdvice
作为Spring中默认的注解,提供对所有(你的项目包扫描范围内)Controller的异常捕获功能。public class JsonEntity implements java.io.Serializable {
private static final long serialVersionUID = -1771426378340695807L;
T data;
private int status = 200;
private String message;
public JsonEntity() {
}
public JsonEntity(T data) {
this.data = data;
}
// 省略getter/setter
@Override
public String toString() {
return "JsonEntity{" +
"status=" + status +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
public class ExceptionResponse implements Serializable {
private static final long serialVersionUID = -5599209786899732586L;
private String errorMessage;
private String classType;
// 省略getter/setter
}
@Slf4j
public abstract class BaseException extends RuntimeException implements Serializable {
private static final long serialVersionUID = 5047454682872098494L;
private int responseStatus = HttpStatus.INTERNAL_SERVER_ERROR.value();
private Object[] parameters = null;
private Logger logger = LoggerFactory.getLogger(BaseException.class);
public BaseException() {
}
public BaseException(String message) {
super(message);
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(Throwable cause) {
super(cause);
}
public BaseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public BaseException(Object[] parameters) {
this.parameters = parameters;
}
public BaseException(String message, Object[] parameters) {
super(message);
this.parameters = parameters;
}
public static int determineResponseStatus(Throwable throwable) {
if (throwable instanceof BaseException) {
return ((BaseException) throwable).responseStatus();
}
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
public static int determineResponseStatus(Throwable throwable, int defaultValue) {
if (throwable == null) {
return defaultValue;
}
return determineResponseStatus(throwable);
}
public BaseException responseStatus(int responseStatus) {
this.responseStatus = responseStatus;
return this;
}
public int responseStatus() {
return responseStatus;
}
public String getMessage(String fmt) {
if (ObjectUtil.isEmpty(parameters)) {//NOPMD
return fmt;
}
return String.format(fmt, parameters);
}
public String getMessage() {
return (super.getMessage() == null ? "" : super.getMessage()) + formatParameters();
}
public String getLocalizedMessage() {
return getLocalizedMessage(LocaleContextHolder.getLocale());
}
public String getLocalizedMessage(Locale locale) {
Assert.notNull(locale);
return getFromMessageSource(super.getMessage(), parameters, locale);
}
private String getFromMessageSource(String messageCode, Object[] params, Locale locale) {
try {
MessageSource messageSource = SpringContextHelper.afterSpringFullyStarted().getBeanSilently(MessageSource.class);
if (messageSource != null) {
return messageSource.getMessage(messageCode, params, locale);
}
} catch (Exception e) {
logger.trace("error: {}, fallback to non-localized code, code: {}", e.getMessage(), messageCode);
}
logger.trace("messageSource absent, fallback to non-localized message, code: {}", messageCode);
return getMessage();
}
private String formatParameters() {
StringBuffer sb = new StringBuffer();
if (!isEmpty(parameters)) {
sb.append(" [params: ");
for (int i = 0; i < parameters.length; i++) {
sb.append(i).append(":").append(parameters[i]);
if (i < parameters.length - 1) {
sb.append(", ");
}
}
sb.append("] ");
}
return sb.toString();
}
public Object[] getParameters() {
return parameters;
}
public void throwIf(boolean condition) {
if (condition) {
throw this;
}
}
public class BizException extends BaseException {
private static final long serialVersionUID = 4328407468288938158L;
public BizException() {
}
public BizException(String message) {
super(message);
}
public BizException(String message, int status) {
super(message);
this.responseStatus(status);
}
public BizException(String message, Throwable cause) {
super(message, cause);
}
public BizException(String message, Throwable cause, int status) {
super(message, cause);
this.responseStatus(status);
}
public BizException(Throwable cause) {
super(cause);
}
public BizException(Throwable cause, int status) {
super(cause);
this.responseStatus(status);
}
public BizException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public BizException(Object[] params) {
super(params);
}
public BizException(String message, Object[] params) {
super(message, params);
}
public static BaseException ofMessage(String message) {
return new BizException(message);
}
}
@ControllerAdvice
@Controller
@ControllerAdvice
public class GlobalExceptionHandler {
private static Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@Autowired(required = false)
private ResponseStatusExceptionResolver responseStatusExceptionResolver;
@Autowired(required = false)
private DefaultHandlerExceptionResolver defaultHandlerExceptionResolver;
@PostConstruct
public final void postInit() {
if (responseStatusExceptionResolver == null) {
responseStatusExceptionResolver = new ResponseStatusExceptionResolver();
}
if (defaultHandlerExceptionResolver == null) {
defaultHandlerExceptionResolver = new DefaultHandlerExceptionResolver();
}
}
protected ResponseEntity jsonResponse(HttpServletRequest request, HttpServletResponse response, int status, Exception e) {//NOPMD
JsonEntity resp = ResponseHelper.createInstance(ExceptionUtil.getExceptionResponse(request, e));
resp.setStatus(status);
resp.setMessage(ExceptionUtil.getErrorMessage(e));
return new ResponseEntity<>(resp, HttpStatus.valueOf(status));
}
@ExceptionHandler(value = Exception.class)
@Order(Ordered.LOWEST_PRECEDENCE)
public final Object defaultErrorHandler(HttpServletRequest req, HttpServletResponse response, @Nullable Object handler, Exception e) {
if (e instanceof InternalHttpException) {
log.warn(e.getMessage());
} else {
log.error(e.getMessage(), e);
}
ErrorWrapperResponse wrapperResponse = new ErrorWrapperResponse(response);
ModelAndView modelAndView = responseStatusExceptionResolver.resolveException(req, wrapperResponse, handler, e);
if (modelAndView == null) {
modelAndView = defaultHandlerExceptionResolver.resolveException(req, wrapperResponse, handler, e);
}
int status;
if (modelAndView == null) {
if (e instanceof BaseException) {
status = ((BaseException) e).responseStatus();
} else {
status = HttpStatus.INTERNAL_SERVER_ERROR.value();
}
modelAndView = new ModelAndView();
req.setAttribute("javax.servlet.error.exception", e);
} else {
status = wrapperResponse.getStatus();
}
if (WebUtil.isAjax(req)) {
return jsonResponse(req, response, status, e);
} else {
return htmlResponse(req, response, modelAndView, status, e);
}
}
private Object htmlResponse(HttpServletRequest request, HttpServletResponse response, ModelAndView modelAndView, int status, Exception e) {//NOPMD
modelAndView.addObject("requestId", WebUtil.getRequestId());
modelAndView.addObject("javax.servlet.error.exception", e);
modelAndView.addObject("errorStatus", status);
modelAndView.setStatus(HttpStatus.valueOf(status));
switch (status) {
case 400:
case 401:
case 403:
case 404:
case 405:
case 410:
case 415:
case 500:
modelAndView.setViewName("error/" + status);
break;
default:
modelAndView.setViewName("error/500");
break;
}
return modelAndView;
}
private static class ErrorWrapperResponse extends HttpServletResponseWrapper {
private int status = 200;
private String message;
private boolean hasErrorToSend = false;
ErrorWrapperResponse(HttpServletResponse response) {
super(response);
}
@Override
public void sendError(int status) throws IOException {
sendError(status, null);
}
@Override
public void sendError(int status, String message) throws IOException {
this.status = status;
this.message = message;
this.hasErrorToSend = true;
}
@Override
public int getStatus() {
if (this.hasErrorToSend) {
return this.status;
} else {
return super.getStatus();
}
}
public String getMessage() {
return this.message;
}
public boolean hasErrorToSend() {
return this.hasErrorToSend;
}
}
}