前言
在java项目里,异常的使用是比不可少,但是很多的开发者并不知道异常在项目中要怎么使用会更好一些,今天就给大家说说项目中我是怎么使用的,也希望能引出你的更好的使用方法和想法。
分析
我们先来说说,目前很多项目都是怎么处理自定义异常的呢?因为项目采用的是基本都是MVC代码组织模式,所以很多的项目会按层次定义自己的异常,例如:DaoException、ServiceException、ControllerException,还有按照第三发组件定义自定义异常,例如:MysqlExceptioin、RedisException、ElasticSearchException,RabbitMqException这样的划分(这里只说下大类的异常划分,详细的就不说了),上述这些异常的划分都是有一些好处的,可以很容易定位到时哪个层或那个组件出现了问题,但是有一点如果你没有对这些功能组件或代码层做二次封装,其实是没有特别的必要这样划分异常的,因为功能组件通常都有一个自己的全局异常类,当出现异常的,其实也一眼就可以定位到这个异常原因,没有必要在不厌其烦的将异常一层层抛出,代码的意义其实不是很大,那么接下来说下我们的异常使用方式。
正题
来先让我们熟悉下异常的集成结构,Throwable是所有异常的父类,Error是错误,错误是致命的,所以在程序中我们通常不会碰到Error,也通常不会去自定义实现它,Exception是异常的父类,异常分为两大类,受检异常和运行时异常,受检异常就是在调用某个抛出受检异常方法时,调用方必须去处理掉该异常或手动向上抛出,否则编译不通过,运行时异常就是不会被编译器所检查,当程序运行到特定条件时,才会抛出异常,那么问题来啦,我们自定义的异常,到底是继承Exception(除RuntimeException的子类都是受检异常)还是RuntimeException(运行时异常)呢?我们通常是继承RuntimeException,也建议你也这样做。
下面说下我们的异常继承关系:
对于异常我们会划分出参数无效、数据已存在、数据不存在、权限不足、远程调用、内部错误等等的异常。他们都继承了全局业务异常(BusinessException),更详细的异常可以通过继承它们去向下划分,你可以看到,其实我们划分的异常是按照业务划分的,接口采用的Restful风格,全局异常捕获类会根据特定的异常,返回不同的HTTP状态码, 例如:参数无效会返回400Http状态码,数据不存在会返回404,系统内部错误返500等等。
下面我们展示一下代码:
package com.common.basic;
import lombok.Data;
/**
* @author zcc
*/
@Data
public class BaseException extends RuntimeException {
private int status;
private String message;
public BaseException(int status, String message) {
this.status = status;
this.message = message;
}
}
说明:
该类有四个属性,code、message,code主要是内部自定义的一个放接口返回状态码的枚举,message用来异常抛出时,向上传递一些附加信息,例如:runtimeException报错信息或者一些自定义的错误信息。
无参构造器中有一段初始化其子类的code、message和resultCode的逻辑。
package com.common.enums;
/**
* @author zcc
*/
public enum ApiCode {
/**
* 成功
*/
OK(200, "Ok"),
/**
* 失败
*/
FAIL(202, "Fail"),
/**
* 请求参数有误
*/
BAD_REQUEST(400, "Bad Request"),
/**
* 未授权
*/
UNAUTHORIZED(401, "Unauthorized"),
/**
* 拒绝(权限不足)
*/
FORBIDDEN(403, "Forbidden"),
/**
* 没有数据
*/
NOT_FOUND(404, "Not Found"),
/**
* 数据冲突
*/
CONFLICT(409, "Conflict"),
/**
* 系统异常
*/
ERROR(500, "Internal Server Error");
private int value;
private String message;
public int getValue() {
return value;
}
public String getMessage() {
return message;
}
ApiCode(int value, String message) {
this.value = value;
this.message = message;
}
}
配置handlers执行类:
package com.common.handlers;
import com.common.basic.BaseException;
import com.common.basic.ResponseApi;
import com.netflix.client.ClientException;
import com.common.enums.ApiCode;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.net.SocketTimeoutException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author zcc
*/
@RestControllerAdvice
public class RepairController {
@ExceptionHandler(Exception.class)
public ResponseApi exceptionHandler(Exception e) {
String message;
boolean isRTE = e.getClass() == RuntimeException.class;
Throwable throwable = isRTE ? e.getCause() : e;
String msg = throwable.getMessage();
String className = throwable.getClass().getName();
if (throwable instanceof ClientException) {
String serviceName = Optional.ofNullable(msg).map(item -> item.replace("Load balancer does not have available server for client: ", "")).orElse(msg);
message = MessageFormat.format("服务[{0}]未启动", serviceName);
} else if (throwable instanceof SocketTimeoutException) {
message = MessageFormat.format("服务未启动,请联系部署或运维人员;线索:[{0}]", msg);
} else if (className.startsWith("feign.FeignException")) {
message = MessageFormat.format("服务调用异常,请联系相关功能开发人员并告知环境与重现流程;线索:[{0}]", msg);
} else if (className.startsWith("feign.RetryableException")) {
//RetryableException re = (RetryableException) throwable;
message = MessageFormat.format("服务调用异常,可能是网络造成,请联系部署或开发人员落实;线索:[{0}]", msg);
} else {
message = MessageFormat.format("意料之外错误,请马上联系开发人员并告知环境和重现流程,线索:[{0}]", msg);
}
e.printStackTrace();
return ResponseApi.error(ApiCode.ERROR, message);
}
@ExceptionHandler(BaseException.class)
public ResponseApi baseExceptionHandler(BaseException e) {
ResponseApi ra = new ResponseApi();
ra.setStatus(e.getStatus());
ra.setMessage(e.getMessage());
ra.setData(e.getCause());
e.printStackTrace();
return ra;
}
@ExceptionHandler(ConstraintViolationException.class)
public ResponseApi constraintValidationErrorHandler(ConstraintViolationException ex) {
List<String> eis = ex.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.toList());
ex.printStackTrace();
return ResponseApi.sp(ApiCode.ERROR, eis.toString());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseApi validationErrorHandler(MethodArgumentNotValidException ex) {
List<String> errorInformation = ex.getBindingResult()
.getAllErrors().stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.toList());
ex.printStackTrace();
return ResponseApi.sp(ApiCode.ERROR, errorInformation.toString());
}
@ExceptionHandler(BindException.class)
public ResponseApi exceptionHandler(BindException e) {
List<String> errors = e.getAllErrors().stream().map(item -> {
if (item instanceof FieldError) {
FieldError errItem = (FieldError) item;
return String.format("%s: %s", errItem.getField(), errItem.getDefaultMessage());
} else {
return item.getDefaultMessage();
}
}).collect(Collectors.toList());
e.printStackTrace();
return ResponseApi.sp(ApiCode.ERROR, errors);
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseApi exceptionHandler(HttpRequestMethodNotSupportedException e) {
String method = e.getMethod();
String[] supportedMethods = e.getSupportedMethods();
String supportedStr = Arrays.toString(supportedMethods);
String message = MessageFormat.format("请求方式错误,只能支持{0},当前请求方式为:{1}", supportedStr, method);
e.printStackTrace();
return ResponseApi.error(ApiCode.BAD_REQUEST, message);
}
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ResponseApi exceptionHandler(HttpMediaTypeNotSupportedException e) {
MediaType contentType = e.getContentType();
String message;
if (Objects.isNull(contentType)) {
message = "请求参数类型错误";
} else {
String type = contentType.getType();
String subtype = contentType.getSubtype();
message = MessageFormat.format("请求参数类型错误,当前类型:[{0}/{1}]", type, subtype);
}
e.printStackTrace();
return ResponseApi.error(ApiCode.BAD_REQUEST, message);
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseApi exceptionHandler(HttpMessageNotReadableException e) {
String message = "请求参数格式错误";
e.printStackTrace();
return ResponseApi.error(ApiCode.BAD_REQUEST, message);
}
@ExceptionHandler(UncategorizedSQLException.class)
public ResponseApi exceptionHandler(UncategorizedSQLException e) {
String sql = e.getSql();
String message = MessageFormat.format("数据库操作失败,请联系相关开发处理; SQL:[{0}]", sql);
e.printStackTrace();
return ResponseApi.error(ApiCode.ERROR, message);
}
@ExceptionHandler(NullPointerException.class)
public ResponseApi exceptionHandler(NullPointerException e) {
List<String> sts = Stream.of(e.getStackTrace()).map(item -> {
String fileName = item.getFileName();
String className = item.getClassName();
String methodName = item.getMethodName();
int lineNumber = item.getLineNumber();
String pattern = "File:%s Class:%s Method:%s LineNumber:%d";
return String.format(pattern, fileName, className, methodName, lineNumber);
}).collect(Collectors.toList());
String message = MessageFormat.format("出错了,赶紧联系开发人员吧; 原因:{0}", "NPE");
ResponseApi error = ResponseApi.error(ApiCode.ERROR, message);
error.setData(sts);
e.printStackTrace();
return error;
}
}
说明
主要是对各个exception的处理,对于不同的异常针对性处理,然后返回自定义的异常提示,用于分布式微服务的异常捕获,属于项目common包中的异常捕获工具。