前言
本demo的github链接: https://github.com/heruideid/springBoot_demo/tree/master
我的公众号「Rui的后端手册」,先赞后看,月入百万
代码分层
springBoot开发的web项目中,强调分层的概念,一个完整的项目一般会划分出controller层和service层。
controller层负责**service层业务方法的路由和参数的校验**。应用跑起来后,根据收到请求的url和请求类型调用对应controller层的方法处理。controller层方法首先对接收的参数进行校验,包括非空校验、数值范围校验、长度检验等;接着调用service层的方法处理业务逻辑service层负责编写具体的业务代码,比如操作mysql、消息队列、redis等
因此,为了代码的可维护性,controller层代码应该尽量简洁,验证一下参数,直接丢给service层处理即可
简洁的全局异常处理
异常处理的方式无外乎两种:
try/catch发现异常,立即处理throw往上抛出,交给调用者处理
在springBoot的web项目中可以采用全局异常处理的方式:
-
针对项目可能出现的异常,定义一个
ServiceException类,该类包括enum类型的变量ExceptionType(代表异常类型)和string类型的变量msg(代表异常信息) -
service层的方法套一个大的try/catch;捕获到异常后,在日志里打印异常,并往controller层抛出新的ServiceException实例; -
controller层方法将ServiceException继续抛出 -
定义一个全局异常处理类,接收并统一处理
controller层抛出的ServiceException -
而
controller层方法的参数检验通过以下方式:- 参数加
@Validated注解 - 在全局异常处理类中定义参数检验发生异常时的处理方法
- 参数加
具体实现
自定义异常:
public enum ExceptionType {
/**
* 自定义异常
*/
REDIS_EXCEPTION(1),
MYSQL_EXCEPTION(2),
ILLEGAL_PARAM(3);
final int code;
final Level level;
ExceptionType(int code){
this.code=code;
this.level=Level.WARNING;
}
ExceptionType(int code,Level level){
this.code=code;
this.level=level;
}
public int getCode(){
return code;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServiceException extends Exception{
protected String msg;
protected ExceptionType exceptionType;
}
自定义ErrorVO和CommonJsonResponse,这两个类定义了当出现异常时,返回给调用方的Json数据格式:
@Data
@Slf4j
public class ErrorVO {
private Date timestamp;
private Map<String, String[]> params;
private String desc;
public ErrorVO(Date timestamp, Map<String, String[]> params, String desc) {
this.timestamp = timestamp;
this.params = params;
this.desc = desc;
}
@Override
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (Exception e) {
log.warn("toString error.", e);
return String.format("ErrorDTO{timestamp=%s, params=%s, desc='%s'}",timestamp,params,desc);
}
}
}
@Data
public class CommonJsonResponse<T> {
public static final String MSG_SUCCESS = "SUCCESS";
public static final String MSG_NOT_FOUND = "NOT FOUND";
public static final int ERROR_CODE_SUCCESS = 0;
public static final int ERROR_CODE_NO_SUCH_OBJECT = 404;
/**
* 200 OK, 500 internal error, mapping of response code
*/
private int status;
/**
* 0 means success
*/
private int errorCode;
/**
* SUCCESS or others
*/
private String msg;
/**
* the result data, may user info or others
*/
private T data;
public CommonJsonResponse() {
}
public CommonJsonResponse(int status, int errorCode) {
this.status = status;
this.errorCode = errorCode;
}
public CommonJsonResponse(int status, int errorCode, String msg) {
this.status = status;
this.errorCode = errorCode;
this.msg = msg;
}
public static CommonJsonResponse ok() {
CommonJsonResponse res = new CommonJsonResponse(HttpStatus.OK.value(), CommonJsonResponse.ERROR_CODE_SUCCESS);
res.setMsg(CommonJsonResponse.MSG_SUCCESS);
return res;
}
public static<T> CommonJsonResponse ok(T t) {
CommonJsonResponse res = new CommonJsonResponse(HttpStatus.OK.value(), CommonJsonResponse.ERROR_CODE_SUCCESS);
res.setMsg(CommonJsonResponse.MSG_SUCCESS);
res.setData(t);
return res;
}
}
在controller层写一个全局异常处理类,几个关键点:
- 在类上加上
@ControllerAdvice注解 - 继承
ResponseEntityExceptionHandler,并覆写handleMethodArgumentNotValid方法,这是参数检验失败时的处理方法 - 自定义一个全局异常处理方法
globalExceptionHandler,并加以@ExceptionHandler(ServiceException.class)注解,作用是处理对应controller层方法抛出的指定类型异常
@RestController
@ControllerAdvice
@Slf4j
public class CustomizedExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(ServiceException.class)
public ResponseEntity<CommonJsonResponse> globalExceptionHandler(ServiceException ex, WebRequest request){
ErrorVO errorDetails = new ErrorVO(new Date(), request.getParameterMap(),
request.getDescription(false));
ExceptionType exType = ex.getExceptionType();
if(exType==ExceptionType.REDIS_EXCEPTION||exType==ExceptionType.MYSQL_EXCEPTION){
// 500 服务器内部错误
CommonJsonResponse<ErrorVO> response = new CommonJsonResponse<>(HttpStatus.INTERNAL_SERVER_ERROR.value(),
exType.getCode(), ex.getMsg());
response.setData(errorDetails);
return new ResponseEntity<CommonJsonResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
else if(exType==ExceptionType.ILLEGAL_PARAM){
// 400 客户端参数错误
CommonJsonResponse<ErrorVO> response = new CommonJsonResponse<>(HttpStatus.BAD_REQUEST.value(),
exType.getCode(), ex.getMsg());
response.setData(errorDetails);
return new ResponseEntity<CommonJsonResponse>(response, HttpStatus.BAD_REQUEST);
}else {
// catch as 200
CommonJsonResponse<ErrorVO> response = new CommonJsonResponse<>(HttpStatus.OK.value(),
exType.getCode(), ex.getMsg());
response.setData(errorDetails);
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
@Override
public ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
BindingResult exceptions = ex.getBindingResult();
String message = "参数异常";
if (exceptions.hasErrors()) {
List<ObjectError> errors = exceptions.getAllErrors();
if (!errors.isEmpty()) {
FieldError fieldError = (FieldError) errors.get(0);
message = fieldError.getDefaultMessage();
}
}
ErrorVO errorDetails = new ErrorVO(new Date(), request.getParameterMap(),
request.getDescription(false));
CommonJsonResponse<ErrorVO> response = new CommonJsonResponse<>(HttpStatus.BAD_REQUEST.value(),
ExceptionType.ILLEGAL_PARAM.getCode(), message);
response.setData(errorDetails);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
}
controller层在检验参数时加上@Validated注解,这样在校验参数失败时,会调用全局异常处理类中的handleMethodArgumentNotValid方法
@RestController
@RequestMapping("/MyController")
public class MyController {
@Autowired
private MyService myService;
@PostMapping("/test")
public void test(@Validated @RequestBody Student student) throws ServiceException {
myService.method1();
}
}
service层方法就是打印异常日志并向上抛出ServiceException异常:
@Service
@Slf4j
public class MyService {
public void method1() throws ServiceException {
try{
/**
* your service code
*/
throw new Exception();
}catch (Exception e){ //catch各种异常往上抛出对应ServiceException
log.warn("mysql update exception",e);
throw new ServiceException("mysql update exception", ExceptionType.MYSQL_EXCEPTION);
}
}
}
最后的效果图:
参数非法时:

参数合法但service层出现异常时:

310

被折叠的 条评论
为什么被折叠?



