在 SpringBoot 中的异常处理

本文概述

本文将完成一个 springboot 中异常处理的小 demo,将使用 try-catch 和@RestControllerAdvice 两种方法。本文代码地址。

demo 概述

需求很简单访问 http://localhost:8080/{id} 接口,如果id是3则抛出用户不存在的异常,如果id小于等于0则抛出参数异常。为了方便阅读,我们先定义一些类和函数。

定义HelloController

@RestController
public class HelloController {
    @Autowired
    private UserInfoManager userInfoManager;

    @GetMapping("/{id}")
    public String index(@PathVariable("id") long id) {
        return userInfoManager.getUserById(id);
    }
}

定义 ServiceException,这是所有自定义异常的父类。

public class ServiceException extends RuntimeException {
    private int statusCode;
    private ServiceException.ErrorType errorType;
    private String code;

    public enum ErrorType {
        Client,
        Service,
        Unknown
    }
}

定义 ResourceNotFoundException

public class ResourceNotFoundException extends ServiceException {
    public ResourceNotFoundException(String message) {
        super(message);
        this.setStatusCode(HttpStatus.NOT_FOUND.value());
        this.setCode("USER_INFO_NOT_FOUND");
        this.setErrorType(ErrorType.Client);
    }
}

在 UserInfoManager 中,如果查找的用户的id是3我们抛出 ResourceNotFoundException 的异常。

@Component
public class UserInfoManager {
    public String getUserById(long id) {
        if (id == 3L) {
            throw new ResourceNotFoundException("user 3 is not found");
        }
        return "This is user " + id;
    }
}

使用try-catch处理异常

此时,我们访问 http://localhost:8080/3 ,会得到以下的 response,这是spring 自带的 response。

{
    "timestamp": "2021-03-03T13:24:21.217+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "message": "",
    "path": "/3"
}

如何修改 status 呢?很简单,加上注释 @ResponseStatus(code = xxx)

@ResponseStatus(code = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends ServiceException {
    public ResourceNotFoundException(String message) {
        super(message);
        this.setStatusCode(HttpStatus.NOT_FOUND.value());
        this.setCode("USER_INFO_NOT_FOUND");
        this.setErrorType(ErrorType.Client);
    }
}

再次访问接口,我们得到了以下的 response。

{
    "timestamp": "2021-03-03T13:28:50.473+00:00",
    "status": 404,
    "error": "Not Found",
    "message": "",
    "path": "/3"
}

springboot 自带的异常处理返回的 response 往往不能满足我们的需求,如何产生自己的 response 呢?首先是一种比较传统的方法,我们使用try-catch 在 HelloController 中抓住 ResourceNotFoundException 异常,并返回一个 ResponseEntity,其body内容是我们定义的一个处理错误返回的类 ErrorResponse。

public class ErrorResponse {
    private String code;
    private String message;
    private int statusCode;
    private ServiceException.ErrorType errorType;
}
@RestController
public class HelloController {
    @Autowired
    private UserInfoManager userInfoManager;

    @GetMapping("/{id}")
    public ResponseEntity index(@PathVariable("id") long id) {
        try {
            return ResponseEntity.ok(userInfoManager.getUserById(id));
        } catch (ResourceNotFoundException ex) {
            ErrorResponse errorResponse = new ErrorResponse();
            errorResponse.setCode(ex.getCode());
            errorResponse.setErrorType(ex.getErrorType());
            errorResponse.setMessage(ex.getMessage());
            errorResponse.setStatusCode(ex.getStatusCode());
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(errorResponse);
        }
    }

}

再次访问接口,返回了我们想要的结果。但是这种 try-catch 的方法非常麻烦,而且代码非常的丑。

{
    "code": "USER_INFO_NOT_FOUND",
    "message": "user 3 is not found",
    "statusCode": 404,
    "errorType": "Client"
}

使用 @RestControllerAdvice 进行异常统一处理

我们使用注解 @RestControllerAdvice 对 controller 进行增强,使用 @ExceptionHandler(Class) 对异常进行处理。其实函数内容和上文中try-catch代码块是一摸一样的。只是 springboot 给我们带来了更为方便的操作,他将 controller 进行增强,代我们处理了所有异常。

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ResourceNotFoundException.class)
    ResponseEntity handleResourceNotFoundException(ResourceNotFoundException ex) {
        ErrorResponse errorResponse = new ErrorResponse();
        errorResponse.setCode(ex.getCode());
        errorResponse.setErrorType(ex.getErrorType());
        errorResponse.setMessage(ex.getMessage());
        errorResponse.setStatusCode(ex.getStatusCode());
        return ResponseEntity.status(ex.getStatusCode())
                .body(errorResponse);
    }
}

此时的 HelloConroller也变得异常简单。

@RestController
public class HelloController {
    @Autowired
    private UserInfoManager userInfoManager;

    @GetMapping("/{id}")
    public ResponseEntity index(@PathVariable("id") long id) {
        return ResponseEntity.ok(userInfoManager.getUserById(id));
    }

}

如果还有其他的异常,我们还需要写很多个带有 @ExceptionHandler 的函数吗?DO NOT REPEAT YOURSELF!很显然是不需要的。任何需要子类的地方,我们都可以传他的父类。所以我们所有的自定义异常都只要继承 ServiceException 就好了。

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ServiceException.class)
    ResponseEntity handleServiceException(ServiceException ex) {
        ErrorResponse errorResponse = new ErrorResponse();
        errorResponse.setCode(ex.getCode());
        errorResponse.setErrorType(ex.getErrorType());
        errorResponse.setMessage(ex.getMessage());
        errorResponse.setStatusCode(ex.getStatusCode());
        return ResponseEntity.status(ex.getStatusCode())
                .body(errorResponse);
    }
}

测试一下吧,我们定义一个新的异常 InvalidParameterException。

@ResponseStatus(code = HttpStatus.BAD_REQUEST)
public class InvalidParameterException extends ServiceException {
    public InvalidParameterException(String message) {
        super(message);
        this.setStatusCode(HttpStatus.BAD_REQUEST.value());
        this.setErrorType(ErrorType.Client);
        this.setCode("INVALID_PARAMETER");
    }
}

在用户id小于等于0的时候抛出 InvalidParameterException,修改 UserInfoManager。

@Component
public class UserInfoManager {
    public String getUserById(long id) {
        if (id == 3L) {
            throw new ResourceNotFoundException("user 3 is not found");
        }
        if (id <= 0L) {
            throw new InvalidParameterException(String.format("user %s is invalid", id));
        }
        return "This is user " + id;
    }
}

访问一个异常接口 http://localhost:8080/0,得到了想要的返回。抛出异常可以在 manage 层,也可以在 controller 层,这是无所谓的,因为异常会自下而上往上抛出,最终都会在 controller 层被发现并处理。

{
    "code": "INVALID_PARAMETER",
    "message": "user 0 is invalid",
    "statusCode": 400,
    "errorType": "Client"
}

完。

你可能感兴趣的:(在 SpringBoot 中的异常处理)