How to build Rest Service with Spring Boot - A Step by Step Tutorial for Beginner - 4

In the previous article of the series, I will show you how to format the data returned by the Restful service interface in Spring Boot. This article will continue in depth and demonstrate how to verify the validity of the entered data.

User input data validation is a task that must be completed by the server-side system, and Spring Boot has built-in support for [JSR 303 Bean Validation] (https://beanvalidation.org/1.0/spec/), which together with minimal programming (AOP) makes the task of data validation very simple.

Add validation logic

First, we implement a simple validation rule with validation annotations supported by JSR 303: title cannot be empty.

To implement this rule, you only need to add annotations to the corresponding properties of the entity class, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Todo {

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;

@NotNull
private String title;

private String desc;

}

As you can see, we’ve annotated @NotNull on the title property of the Todo class to limit that property from being null. Note that here the empty and empty strings are two different annotations

After you have changed it, run the program, access this API in Postman, and enter the following parameters in the body:

1
{"desc":"desc of task 1"}

Get the following return:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
"timestamp": "2019-07-16T01:27:17.388+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotNull.todo.title",
"NotNull.title",
"NotNull.java.lang.String",
"NotNull"
],
"arguments": [
{
"codes": [
"todo.title",
"title"
],
"arguments": null,
"defaultMessage": "title",
"code": "title"
}
],
"defaultMessage": "不能为null",
"objectName": "todo",
"field": "title",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotNull"
}
],
"message": "Validation failed for object='todo'. Error count: 1",
"path": "/todo"
}

As you can see, @NotNull is already working. The data was not inserted into the database and an error message was returned - defaultMessage

[JSR 303 Bean Validation] (https://beanvalidation.org/1.0/spec/) provides a number of ready-made verification rules, you can refer to its official documentation.

Uniform return format for error

Now we can easily use validation rules to verify the data, but the returned error format is the standard format in Spring Boot, and the return format we defined in previous article, which will lead to client development needs to use different code for different situations to deal with, which is not conducive to system maintenance. Therefore, we need to convert the format of the returned data.

Converting error handling in Spring Boot is as simple as adding a handling method to the RestController class, specifically adding a handleException method to The TodoApi and then decorating the method with a @ExceptionHandler annotation indicating that it is used to handle exceptions. The code is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResult handleException(MethodArgumentNotValidException exception) {

ApiResult.ApiResultBuilder builder = ApiResult.builder().succ(false).build().toBuilder();

String errorMsg = exception.getBindingResult().getFieldErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.findFirst()
.orElse(exception.getMessage());

return builder.msg(errorMsg).build();
}

After running the program after modification and accessing it with Postman, you can find that the information has become our unified format. The client only needs to judge succ to know whether the call was successful, and if not, it takes the information of the msg.

1
2
3
4
5
6
{
"succ": false,
"code": null,
"msg": "Can not null",
"data": null
}

Use global error handling

In the above method, we standardize the error handling information by adding methods to the RestController, considering that if there are many RestControllers in the system, it is too cumbersome and not conducive to maintenance, then there is no global
What about the unified approach?

Of course, with Spring’s AOP - Facet Programming feature, it is easy to implement and look directly at the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {

@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception, HttpHeaders headers, HttpStatus status, WebRequest request) {

ApiResult.ApiResultBuilder builder = ApiResult.builder().succ(false).build().toBuilder();

String errorMsg = exception.getBindingResult().getFieldErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.findFirst()
.orElse(exception.getMessage());

return handleExceptionInternal(exception, builder.msg(errorMsg).build(), headers, HttpStatus.BAD_REQUEST, request);
}
}

In the code, we define a RestExceptionHandler class, which inherits from the ResponseEntityExceptionHandler. ResponseEntityExceptionHandler is a class built into Spring Boot, providing basic methods and mechanisms for handling errors and exceptions, we can override its methods to obtain exception handling capabilities for specific processing procedures, and then insert classes into the rest request process through @ControllerAdvice this AOP annotation.

With this class, we can delete the handleException that we just added to RestController.

After modifying it, run the program, access it with Postman, and you can see the same result as the previous section.

Handle business logic errors

The above we are through the JSR 303 annotation of the data verification, which is feasible for some relatively simple data verification, but for more complex verification scenarios (such as the need to check the database, according to the query results to determine whether it is correct), JSR303 is a bit inadequate, then it needs to be verified in the business object. In our case, data validation should be done in the TodoBiz class under the biz package. As an example, suppose the title attribute has a restriction that can only be “tom”. Then we add validation to TodoBiz’s business method addTodo as follows:

1
2
3
4
5
6
7
8
9
@Transactional
public void addTodo(Todo todo) {

if (!"tom".equals(todo.getTitle())) {
todoRepository.save(todo);
} else {
throw new BizException("Title can not be tom");
}
}

Here, our custom exception class BizException is used to throw exceptions to the presentation layer - RestController. BizException is defined as follows:

1
2
3
4
5
6
public class BizException extends RuntimeException {

public BizException(String msg) {
super(msg, null);
}
}

Note that we inherit runtimeException, which has two benefits:

  1. There is no need to force the use of try … in code that uses business classes (in this case, API classes) Catch structure to simplify the code.
  2. You can trigger Spring Boot’s transaction management mechanism to roll back transactions.

Then we add method classes to RestExceptionHandler to handle BizException uniformly.

1
2
3
4
5
6
7
8
@ExceptionHandler({BizException.class})
public ResponseEntity<Object> handleBizException(BizException e, WebRequest request) {

ApiResult.ApiResultBuilder builder = ApiResult.builder().succ(false).build().toBuilder();

return new ResponseEntity<Object>(builder.msg(e.getMessage()).build(), new HttpHeaders(), HttpStatus.OK);

}

With this modification, we can further simplify the code for apIs and remove try… Catch structure. The new code is as follows:

TodoApi.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    @PostMapping("/todo")
public ApiResult addTodo(@Valid @RequestBody Todo todo) {

ApiResult.ApiResultBuilder builder = ApiResult.builder().succ(false).build().toBuilder();

// try {
todoBiz.addTodo(todo);

builder.succ(true);
// } catch(BizException e) {
// builder.msg(e.getMessage());
// }

return builder.build();
}

When you have finished changing the code, run the program, access it with Postman, using the following parameters:

1
{"title":"tom", "desc":"desc of task 1"}

You can get the following results:

1
2
3
4
5
6
{
"succ": false,
"code": null,
"msg": "Title can not be tom",
"data": null
}

Next

In next article, you’ll learn how to use Swagger to automatically generate documentation and test pages for your API.

本文标题:How to build Rest Service with Spring Boot - A Step by Step Tutorial for Beginner - 4

文章作者:Morning Star

发布时间:2022年01月19日 - 12:01

最后更新:2022年01月21日 - 09:01

原始链接:https://www.mls-tech.info/java/springboot-rest-api-tutorial-4-en/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。