Guide to Spring Data REST Validators – Spring Data REST验证器指南

最后修改: 2016年 9月 27日

1. Overview

1.概述

This article covers a basic introduction to Spring Data REST Validators. If you need to first go over the basics of Spring Data REST, definitely visit this article to brush up on the basics.

这篇文章涵盖了对Spring Data REST验证器的基本介绍。如果你需要首先了解Spring Data REST的基础知识,一定要访问这篇文章,以了解基础知识。

Simply put, with Spring Data REST, we can simply add a new entry into the database through the REST API, but we of course also need to make sure the data is valid before actually persisting it.

简单地说,通过Spring Data REST,我们可以简单地通过REST API向数据库添加一个新条目,但我们当然也需要在实际持久化之前确保数据是有效的。

This article continues on an existing article and we will reuse the existing project we set up there.

这篇文章是在现有的文章上继续写的,我们将重新使用我们在那里建立的现有项目。

 

And, if you’re looking to first get started with Spring Data REST – here’s a good way to hit the ground running:

而且,如果你想首先开始使用Spring Data REST–这里有一个很好的方法,可以让你一蹴而就。

2. Using Validators

2.使用验证器

Starting with Spring 3, the framework features the Validator interface – which can be used to validate objects.

从Spring 3开始,框架具有Validator接口–可用于验证对象。

2.1. Motivation

2.1.动机

In the previous article, we defined our entity having two properties – name and email.

在上一篇文章中,我们定义的实体有两个属性–nameemail

And so, to create a new resource, we simply need to run:

因此,要创建一个新的资源,我们只需要运行。

curl -i -X POST -H "Content-Type:application/json" -d 
  '{ "name" : "Test", "email" : "test@test.com" }' 
  http://localhost:8080/users

This POST request will save the provided JSON object into our database, and the operation will return:

这个POST请求将把提供的JSON对象保存到我们的数据库中,并且操作将返回。

{
  "name" : "Test",
  "email" : "test@test.com",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/1"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/1"
    }
  }
}

A positive outcome was expected since we provided valid data. But, what will happen if we remove the property name, or just set the value to an empty String?

由于我们提供了有效的数据,所以预期会有一个积极的结果。但是,如果我们删除属性name,或者只是将值设置为空的String,会发生什么?

To test the first scenario, we will run modified command from before where we will set empty string as a value for property name:

为了测试第一种情况,我们将运行之前修改过的命令,我们将设置空字符串作为属性name的值。

curl -i -X POST -H "Content-Type:application/json" -d 
  '{ "name" : "", "email" : "Baggins" }' http://localhost:8080/users

With that command we’ll get the following response:

通过该命令,我们将得到以下响应。

{
  "name" : "",
  "email" : "Baggins",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/1"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/1"
    }
  }
}

For the second scenario, we will remove property name from request:

对于第二种情况,我们将从请求中删除属性name

curl -i -X POST -H "Content-Type:application/json" -d 
  '{ "email" : "Baggins" }' http://localhost:8080/users

For that command we will get this response:

对于该命令,我们将得到这样的回应。

{
  "name" : null,
  "email" : "Baggins",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/2"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/2"
    }
  }
}

As we can see, both requests were OK and we can confirm that with 201 status code and API link to our object.

正如我们所看到的,两个请求都是OK的,我们可以通过201状态代码和API链接来确认我们的对象.

This behavior is not acceptable since we want to avoid inserting partial data into a database.

这种行为是不可接受的,因为我们要避免在数据库中插入部分数据。

2.2. Spring Data REST Events

2.2.Spring Data REST事件

During every call on Spring Data REST API, Spring Data REST exporter generates various events which are listed here:

在每次调用Spring Data REST API期间,Spring Data REST导出器都会产生各种事件,这里列出了这些事件。

  • BeforeCreateEvent
  • AfterCreateEvent
  • BeforeSaveEvent
  • AfterSaveEvent
  • BeforeLinkSaveEvent
  • AfterLinkSaveEvent
  • BeforeDeleteEvent
  • AfterDeleteEvent

Since all events are handled in a similar way, we will only show how to handle beforeCreateEvent which is generated before a new object is saved into the database.

由于所有的事件都以类似的方式处理,我们将只展示如何处理beforeCreateEvent,它是在一个新对象被保存到数据库之前产生的。

2.3. Defining a Validator

2.3.定义一个验证器

To create our own validator, we need to implement the org.springframework.validation.Validator interface with the supports and validate methods.

为了创建我们自己的验证器,我们需要用org.springframework.validation.Validator接口和supportsvalidate方法实现

Supports checks if the validator supports provided requests, while validate method validates provided data in requests.

Supports检查验证器是否支持所提供的请求,而validate方法验证请求中提供的数据。

Let’s define a WebsiteUserValidator class:

让我们定义一个WebsiteUserValidator类。

public class WebsiteUserValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return WebsiteUser.class.equals(clazz);
    }

    @Override
    public void validate(Object obj, Errors errors) {
        WebsiteUser user = (WebsiteUser) obj;
        if (checkInputString(user.getName())) {
            errors.rejectValue("name", "name.empty");
        }
   
        if (checkInputString(user.getEmail())) {
            errors.rejectValue("email", "email.empty");
        }
    }

    private boolean checkInputString(String input) {
        return (input == null || input.trim().length() == 0);
    }
}

Errors object is a special class designed to contain all errors provided in validate method. Later in this article, we’ll show how you can use provided messages contained in Errors object.
To add new error message, we have to call errors.rejectValue(nameOfField, errorMessage).

Errorsobject是一个特殊的类,旨在包含validate方法中提供的所有错误。在这篇文章的后面,我们将展示如何使用Errors对象中所提供的信息。
要添加新的错误信息,我们必须调用errors.rejectValue(nameOfield, errorMessage)/em>。

After we’ve defined the validator, we need to map it to a specific event which is generated after the request is accepted.

在我们定义了验证器之后,我们需要将其映射到一个特定的事件,该事件在请求被接受后产生。

For example, in our case, beforeCreateEvent is generated because we want to insert a new object into our database. But since we want to validate object in a request, we need to define our validator first.

例如,在我们的例子中,beforeCreateEvent的产生是因为我们想在数据库中插入一个新的对象。但由于我们想在请求中验证对象,我们需要先定义我们的验证器。

This can be done in three ways:

这可以通过三种方式进行。

  • Add Component annotation with name “beforeCreateWebsiteUserValidator“. Spring Boot will recognize prefix beforeCreate which determines the event we want to catch, and it will also recognize WebsiteUser class from Component name.
    @Component("beforeCreateWebsiteUserValidator")
    public class WebsiteUserValidator implements Validator {
        ...
    }
  • Create Bean in Application Context with @Bean annotation:
    @Bean
    public WebsiteUserValidator beforeCreateWebsiteUserValidator() {
        return new WebsiteUserValidator();
    }
  • Manual registration:
    @SpringBootApplication
    public class SpringDataRestApplication implements RepositoryRestConfigurer {
        public static void main(String[] args) {
            SpringApplication.run(SpringDataRestApplication.class, args);
        }
    
        @Override
        public void configureValidatingRepositoryEventListener(
          ValidatingRepositoryEventListener v) {
            v.addValidator("beforeCreate", new WebsiteUserValidator());
        }
    }
    • For this case, you don’t need any annotations on WebsiteUserValidator class.

2.4. Event Discovery Bug

2.4.事件发现错误

At the moment, a bug exists in Spring Data REST – which affects events discovery.

目前,Spring Data REST中存在一个bug–它影响了事件发现。

If we call POST request which generates the beforeCreate event, our application will not call validator because the event will not be discovered, due to this bug.

如果我们调用产生beforeCreate事件的POST请求,我们的应用程序将不会调用验证器,因为该事件将不会被发现,这是由于这个错误。

A simple workaround for this problem is to insert all events into Spring Data REST ValidatingRepositoryEventListener class:

解决这个问题的简单方法是将所有事件插入Spring Data REST ValidatingRepositoryEventListener类。

@Configuration
public class ValidatorEventRegister implements InitializingBean {

    @Autowired
    ValidatingRepositoryEventListener validatingRepositoryEventListener;

    @Autowired
    private Map<String, Validator> validators;

    @Override
    public void afterPropertiesSet() throws Exception {
        List<String> events = Arrays.asList("beforeCreate");
        for (Map.Entry<String, Validator> entry : validators.entrySet()) {
            events.stream()
              .filter(p -> entry.getKey().startsWith(p))
              .findFirst()
              .ifPresent(
                p -> validatingRepositoryEventListener
               .addValidator(p, entry.getValue()));
        }
    }
}

3. Testing

3.测试

In Section 2.1. we showed that, without a validator, we can add objects without name property into our database which is not desired behavior because we don’t check data integrity.

第2.1.节中,我们展示了在没有验证器的情况下,我们可以在数据库中添加没有名称属性的对象,这不是我们想要的行为,因为我们没有检查数据完整性。

If we want to add the same object without name property but with provided validator, we will get this error:

如果我们想在没有name属性的情况下添加相同的对象,但提供验证器,我们会得到这个错误。

curl -i -X POST -H "Content-Type:application/json" -d 
  '{ "email" : "test@test.com" }' http://localhost:8080/users
{  
   "timestamp":1472510818701,
   "status":406,
   "error":"Not Acceptable",
   "exception":"org.springframework.data.rest.core.
    RepositoryConstraintViolationException",
   "message":"Validation failed",
   "path":"/users"
}

As we can see, missing data from request was detected and an object was not saved into the database. Our request was returned with 500 HTTP code and message for an internal error.

正如我们所看到的,从请求中检测到缺失的数据,一个对象没有被保存到数据库中。我们的请求被返回500HTTP代码和内部错误的信息。

The error message doesn’t say anything about the problem in our request. If we want to make it more informational, we will have to modify response object.

错误信息并没有说明我们请求中的问题。如果我们想让它更有信息量,我们将不得不修改响应对象。

In the Exception Handling in Spring article, we showed how to handle exceptions generated by the framework, so that’s definitely a good read at this point.

Exception Handling in Spring一文中,我们展示了如何处理框架产生的异常,所以在这一点上,这绝对是一个好的阅读。

Since our application generates a RepositoryConstraintViolationException exception we will create a handler for this particular exception which will modify response message.

由于我们的应用程序会产生一个RepositoryConstraintViolationException异常,我们将为这个特殊的异常创建一个处理程序,它将修改响应信息。

This is ours RestResponseEntityExceptionHandler class:

这是我们的RestResponseEntityExceptionHandler类。

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends
  ResponseEntityExceptionHandler {

    @ExceptionHandler({ RepositoryConstraintViolationException.class })
    public ResponseEntity<Object> handleAccessDeniedException(
      Exception ex, WebRequest request) {
          RepositoryConstraintViolationException nevEx = 
            (RepositoryConstraintViolationException) ex;

          String errors = nevEx.getErrors().getAllErrors().stream()
            .map(p -> p.toString()).collect(Collectors.joining("\n"));
          
          return new ResponseEntity<Object>(errors, new HttpHeaders(),
            HttpStatus.PARTIAL_CONTENT);
    }
}

With this custom handler, our return object will have information about all detected errors.

有了这个自定义处理程序,我们的返回对象将有所有检测到的错误信息。

4. Conclusion

4.结论

In this article, we showed that validators are essential for every Spring Data REST API which provides an extra layer of security for data insertion.

在这篇文章中,我们展示了验证器对于每个Spring Data REST API都是必不可少的,它为数据插入提供了额外的安全层。

We also illustrated how simple is to create new validator with annotations.

我们还说明了创建带有注解的新验证器是多么简单。

As always, the code for this application can be found in the GitHub project.

一如既往,该应用程序的代码可以在GitHub项目中找到。