# Spring Boot 异常处理 – `@ExceptionHandler`示例

> 原文： [https://howtodoinjava.com/spring-boot2/spring-rest-request-validation/](https://howtodoinjava.com/spring-boot2/spring-rest-request-validation/)

在本**SpringBoot 异常处理器**教程中，我们将学习**验证发送到 PUT / POST REST API 的请求正文**。 我们还将学习在验证错误的 API 响应中添加自定义错误消息。

在这个 SpringBoot 示例中，我们将主要看到两个主要的验证案例：

1.  HTTP POST `/employees`和请求正文不包含有效值，或者某些字段丢失。 它将在响应正文中返回 HTTP 状态代码 400，并带有正确的消息。
2.  HTTP GET `/employees/{id}`和无效 ID 在请求中发送。 它将在响应正文中返回带有正确消息的 HTTP 状态代码 404。

> 阅读更多： [HTTP 状态代码](https://restfulapi.net/http-status-codes/)

## 1\. 创建 REST API 和模型类

给定的 REST API 来自员工管理模块。

`EmployeeRESTController.java`

```java
@PostMapping(value = "/employees")
public ResponseEntity<EmployeeVO> addEmployee (@RequestBody EmployeeVO employee)
{
    EmployeeDB.addEmployee(employee);
    return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
}

@GetMapping(value = "/employees/{id}") 
public ResponseEntity<EmployeeVO> getEmployeeById (@PathVariable("id") int id)
{
    EmployeeVO employee = EmployeeDB.getEmployeeById(id);

    if(employee == null) {
    	 throw new RecordNotFoundException("Invalid employee id : " + id);
    }
    return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
}

```

`EmployeeVO.java`

```java
@XmlRootElement(name = "employee")
@XmlAccessorType(XmlAccessType.FIELD)
public class EmployeeVO extends ResourceSupport implements Serializable
{
	private Integer employeeId;
	private String firstName;
	private String lastName;
	private String email;

	public EmployeeVO(Integer id, String firstName, String lastName, String email) {
		super();
		this.employeeId = id;
		this.firstName = firstName;
		this.lastName = lastName;
		this.email = email;
	}

	public EmployeeVO() {
	}

	//Removed setter/getter for readability
}

```

## 2\. Spring Boot 异常处理 – REST 请求验证

#### 2.1. 默认的 Spring 验证支持

要应用默认验证，我们只需要在适当的位置添加相关注解即可。 即

1.  **使用所需的验证特定注解（例如`@NotEmpty`，`@Email`等）注解模型类**。

    ```java
    @XmlRootElement(name = "employee")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class EmployeeVO extends ResourceSupport implements Serializable
    {
    	private static final long serialVersionUID = 1L;

    	public EmployeeVO(Integer id, String firstName, String lastName, String email) {
    		super();
    		this.employeeId = id;
    		this.firstName = firstName;
    		this.lastName = lastName;
    		this.email = email;
    	}

    	public EmployeeVO() {
    	}

    	private Integer employeeId;

    	@NotEmpty(message = "first name must not be empty")
    	private String firstName;

    	@NotEmpty(message = "last name must not be empty")
    	private String lastName;

    	@NotEmpty(message = "email must not be empty")
    	@Email(message = "email should be a valid email")
    	private String email;

    	//Removed setter/getter for readability
    }

    ```

2.  **通过`@Valid`注解启用验证请求正文**

    ```java
    @PostMapping(value = "/employees")
    public ResponseEntity<EmployeeVO> addEmployee (@Valid @RequestBody EmployeeVO employee)
    {
        EmployeeDB.addEmployee(employee);
        return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
    }

    ```

#### 2.2. 异常模型类

默认的 Spring 验证有效并提供有关错误的信息过多，这就是为什么我们应根据应用程序的需要对其进行自定义。 我们将仅提供措辞非常明确的必要错误信息。 不建议提供其他信息。

创建有意义的异常并充分描述问题始终是一个很好的建议。 一种方法是创建单独的类来表示特定的业务用例失败，并在该用例失败时返回它们。

> 阅读更多： [Java 异常处理 – 新的应用程序](https://howtodoinjava.com/java/exception-handling/best-practices-for-for-exception-handling/)

例如我为所有通过 ID 要求资源但在系统中找不到资源的场景创建了`RecordNotFoundException`类。

`RecordNotFoundException.java`

```java
package com.howtodoinjava.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class RecordNotFoundException extends RuntimeException 
{
	public RecordNotFoundException(String exception) {
		super(exception);
	}
}

```

同样，我编写了一个特殊的类，将为所有失败情况返回该类。 所有 API 具有一致的错误消息结构，可帮助 API 使用者编写更健壮的代码。

`ErrorResponse.java`

```java
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "error")
public class ErrorResponse 
{
	public ErrorResponse(String message, List<String> details) {
		super();
		this.message = message;
		this.details = details;
	}

	//General error message about nature of error
	private String message;

	//Specific errors in API request processing
	private List<String> details;

	//Getter and setters
}

```

#### 2.3. 自定义`ExceptionHandler`

现在添加一个扩展`ResponseEntityExceptionHandler`的类，并使用`@ControllerAdvice`注解对其进行注解。

`ResponseEntityExceptionHandler`是一个方便的基类，用于通过`@ExceptionHandler`方法跨所有`@RequestMapping`方法提供集中式异常处理。 `@ControllerAdvice`更多用于在应用程序启动时启用自动扫描和配置。

用于**`@ControllerAdvice`异常处理示例**的 Java 程序。

`CustomExceptionHandler.java`

```java
package com.howtodoinjava.demo.exception;

import java.util.ArrayList;
import java.util.List;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
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;

@SuppressWarnings({"unchecked","rawtypes"})
@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler 
{
	@ExceptionHandler(Exception.class)
	public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
		List<String> details = new ArrayList<>();
		details.add(ex.getLocalizedMessage());
		ErrorResponse error = new ErrorResponse("Server Error", details);
		return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler(RecordNotFoundException.class)
	public final ResponseEntity<Object> handleUserNotFoundException(RecordNotFoundException ex, WebRequest request) {
		List<String> details = new ArrayList<>();
		details.add(ex.getLocalizedMessage());
		ErrorResponse error = new ErrorResponse("Record Not Found", details);
		return new ResponseEntity(error, HttpStatus.NOT_FOUND);
	}

	@Override
	protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
		List<String> details = new ArrayList<>();
		for(ObjectError error : ex.getBindingResult().getAllErrors()) {
			details.add(error.getDefaultMessage());
		}
		ErrorResponse error = new ErrorResponse("Validation Failed", details);
		return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
	}
}

```

上面的类处理多个异常，包括`RecordNotFoundException`； 并且还可以处理`@RequestBody`带注解的对象中的请求验证错误。 让我们看看它是如何工作的。

## 3\. Spring Boot 异常处理 – 演示

1）HTTP GET `/employees/1` [有效]

```java
HTTP Status : 200

{
    "employeeId": 1,
    "firstName": "John",
    "lastName": "Wick",
    "email": "howtodoinjava@gmail.com",
}

```

2）HTTP GET `/employees/23` [无效]

```java
HTTP Status : 404

{
    "message": "Record Not Found",
    "details": [
        "Invalid employee id : 23"
    ]
}

```

3）HTTP POST `/employees` [无效]

`Request`

```java
{
    "lastName": "Bill",
    "email": "ibill@gmail.com"
}

```

`Response`

```java
HTTP Status : 400

{
    "message": "Validation Failed",
    "details": [
        "first name must not be empty"
    ]
}

```

4）HTTP POST `/employees` [无效]

`Request`

```java
{
    "email": "ibill@gmail.com"
}

```

`Response`

```java
HTTP Status : 400

{
    "message": "Validation Failed",
    "details": [
        "last name must not be empty",
        "first name must not be empty"
    ]
}

```

5）HTTP POST `/employees` [无效]

`Request`

```java
{
	"firstName":"Lokesh",
    "email": "ibill_gmail.com" //invalid email in request
}

```

`Response`

```java
HTTP Status : 400

{
    "message": "Validation Failed",
    "details": [
        "last name must not be empty",
        "email should be a valid email"
    ]
}
```

## 4\. REST 请求验证注解

在上面的示例中，我们仅使用了很少的注解，例如`@NotEmpty`和`@Email`。 还有更多此类注解可用于验证请求数据。 必要时将其签出。

| 注解 | 用法 |
| --- | --- |
| `@AssertFalse` | 带注解的元素必须为`false`。 |
| `@AssertTrue` | 带注解的元素必须为`true`。 |
| `@DecimalMax` | 带注解的元素必须是一个数字，其值必须小于或等于指定的最大值。 |
| `@DecimalMin` | 带注解的元素必须是一个数字，其值必须大于或等于指定的最小值。 |
| `@Future` | 带注解的元素必须是将来的瞬间，日期或时间。 |
| `@Max` | 带注解的元素必须是一个数字，其值必须小于或等于指定的最大值。 |
| `@Min` | 带注解的元素必须是一个数字，其值必须大于或等于指定的最小值。 |
| `@Negative` | 带注解的元素必须是严格的负数。 |
| `@NotBlank` | 带注解的元素不能为`null`，并且必须至少包含一个非空白字符。 |
| `@NotEmpty` | 带注解的元素不能为`null`，也不能为空。 |
| `@NotNull` | 带注解的元素不能为`null`。 |
| `@Null` | 带注解的元素必须为`null`。 |
| `@Pattern` | 带注解的`CharSequence`必须与指定的正则表达式匹配。 |
| `@Positive` | 带注解的元素必须是严格的正数。 |
| `@Size` | 带注解的元素大小必须在指定的边界（包括在内）之间。 |

## 5\. 总结

在此**Spring REST 验证教程**中，我们学习了：

*   通过 ID 提取资源时验证 ID。
*   验证 POST / PUT API 中的请求正文字段。
*   在 API 响应中发送一致且结构化的错误响应。

将有关 **spring rest 异常处理**的问题交给我。

学习愉快！

参考：[`javax.validation.constraints`包](https://javaee.github.io/javaee-spec/javadocs/javax/validation/constraints/package-summary.html)