在 Spring Boot RESTful 服务中处理异常

通过本 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());
    }


}