通过本 Spring Boot 教程,我将分享如何在使用 Spring Boot 构建的 RESTful Web 服务应用程序中处理异常。
发生异常时怎么办
好消息是,如果发生异常,而您的代码没有处理它,Spring Boot 将为您处理异常,并向调用的客户端应用程序返回格式良好的 JSON 或 XML 消息。
{ "timestamp": "2018-04-18T23:38:30.376+0000", "status": 500, "error": "Internal Server Error", "message": "could not execute statement; SQL [n/a]; constraint [UK_6dotkott2kjsp8vw4d0m25fb7]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement", "path": "/users" }
|
为了生成这个 JSON 文档,我无需以编程方式创建 JSON 文档或 Java 类,然后将其转换为 JSON。我无需设置错误、路径或状态代码的值。所有这些都由框架代为处理。
如果抛出自定义异常怎么办
另一个好消息是,如果您抛出自定义异常,调用客户端程序将获得相同的 JSON 或 XML 有效负载结构。当然,错误消息和时间戳值会有所不同,但文档结构是一样的。 因此,您无需以编程方式创建它并设置其值。
让我们创建自己的运行时异常并抛出它。
public class UserServiceException extends RuntimeException{ private static final long serialVersionUID = 5776681206288518465L; public UserServiceException(String message) { super(message); } }
|
现在,让我们抛出这个异常
@Override public UserDetails loadUserByUsername(String username) throws UserServiceException {
UserEntity userEntity = userRepository.findUserByEmail(username);
if (userEntity == null) { throw new UserServiceException("User " + username + " not found"); }
return new User(userEntity.getEmail(), userEntity.getEncryptedPassword(), new ArrayList<>()); }
|
响应返回的 JSON 有效负载将是
{ "timestamp": "2018-04-18T23:51:54.405+0000", "status": 500, "error": "Internal Server Error", "message": "User sergey not found", "path": "/users/sergey" }
|
如何处理任何异常
您可以处理使用 Spring Boot 构建的 RESTful Web 服务应用程序中发生的任何异常。为此,我们需要定义一个新的 Java 类,其中包含一个负责捕获所有异常的方法。
1、创建一个带 @ControllerAdvice 注释的新类
2、添加一个注释为 @ExceptionHandler 的方法,指定该方法需要处理的异常类。
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.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice public class EntityExceptionHandler {
@ExceptionHandler(value = { Exception.class }) public ResponseEntity<Object> handleAnyException(Exception ex, WebRequest request) { return new ResponseEntity<>( ex.getMessage(), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR); } }
|
返回自定义错误消息对象
要使返回的错误信息具有特定的 JSON 或 XML 结构,可以创建一个 Java Bean 类并将其用作响应实体。
import java.util.Date; public class ErrorMessage { private Date timestamp; private String message; private String details;
public ErrorMessage(){}
public ErrorMessage(Date timestamp, String message, String details) { this.timestamp = timestamp; this.message = message; this.details = details; } public Date getTimestamp() { return timestamp; }
public void setTimestamp(Date timestamp) { this.timestamp = timestamp; } public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getDetails() { return details; }
public void setDetails(String details) { this.details = details; }
}
|
现在,将此自定义 ErrorMessage 类作为异常处理程序类中的响应实体。
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.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import java.util.Date;
@ControllerAdvice public class MyExceptionHandler { @ExceptionHandler(Exception.class) public final ResponseEntity<ErrorMessage> handleAllExceptions(Exception ex, WebRequest request) { ErrorMessage errorObj = new ErrorMessage(new Date(), ex.getMessage(), request.getDescription(false)); return new ResponseEntity<>(errorObj, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR); } }
|
处理自己的自定义运行时异常
在本教程的前面部分,我们创建了自定义 UserServiceException,并对 RuntimeException 进行了扩展。
public class UserServiceException extends RuntimeException{ private static final long serialVersionUID = 5776681206288518465L; public UserServiceException(String message) { super(message); } }
|
如果抛出异常,我们可以使用下面的异常处理程序类来处理。要让它处理我们自己的自定义异常,我们只需在 @ExceptionHandler 注解中指定我们的异常名称即可。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.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import java.util.Date;
@ControllerAdvice public class MyExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(UserServiceException.class) public final ResponseEntity<ErrorMessage> handleSpecificExceptions(Exception ex, WebRequest request) {
ErrorMessage errorMessage = new ErrorMessage(new Date(), ex.getMessage(), request.getDescription(false)); return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR); } }
|
处理多个异常
如果需要一个方法处理多个异常,可以在 @ExceptionHandler 注解中用逗号分隔这些异常。例如
@ExceptionHandler(UserNotFoundException.class, RecordExistsException.class)
下面介绍如何在类中使用它:
@ControllerAdvice public class MyExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(UserNotFoundException.class, RecordExistsException.class) public final ResponseEntity<ErrorMessage> handleUserExceptions(Exception ex, WebRequest request) {
ErrorMessage errorMessage = new ErrorMessage(new Date(), ex.getMessage(), request.getDescription(false)); return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR); }
}
|
在 @RestController 类内处理异常
如果愿意,您也可以在抛出异常的同一个类中处理异常。例如,您可以让 @RestController 类中的一个方法来处理异常消息。虽然我不喜欢在我的 @RestController 类中使用处理异常的方法,但这样做效果很好。
下面是一个使用 @RestController 注解的非常简单的根资源类的示例。它故意立即抛出异常,以演示如何在同一个类中处理异常。
注意到同一类中的 @ExceptionHandler 吗?
此外,作为响应实体使用的 ErrorMessage 类的源代码也可从本教程中获取。就在上面几段。
@RestController @RequestMapping("users") public class UserController {
@Autowired UserService userService;
@PostMapping public UserRest createUser(@RequestBody UserDetailsRequestModel requestUserDetails) { UserRest returnValue = new UserRest();
// THROW EXCEPTION NOW JUST FOR DEMONSTRATION PURPOSES if(true) throw new MissingRequiredFieldException("One of the required fields is missing"); ///// UserDto userDto = new UserDto(); BeanUtils.copyProperties(requestUserDetails, userDto);
UserDto createdUser = userService.createUser(userDto); BeanUtils.copyProperties(createdUser, returnValue);
returnValue.setHref("/users/" + createdUser.getUserId());
return returnValue; }
@ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MissingRequiredFieldException.class) public ErrorMessage handleBadRequest(HttpServletRequest req, Exception ex) { return new ErrorMessage(new Date(), ex.getMessage(), req.getRequestURI()); }
}
|