Bean Validation in Jersey – 泽西岛的Bean验证

最后修改: 2018年 9月 16日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’re going to take a look at Bean Validation using the open source framework Jersey.

在本教程中,我们将使用开源框架Jersey来了解Bean Validation。

As we’ve already seen in previous articles, Jersey is an open source framework for developing RESTful Web Services. We can get more details about Jersey in our introduction on how to create an API with Jersey and Spring.

正如我们在之前的文章中已经看到的,Jersey是一个用于开发RESTful Web服务的开源框架。我们可以在关于如何用Jersey和Spring创建一个API的介绍中获得更多关于Jersey的细节

2. Bean Validation in Jersey

2.泽西岛的Bean验证

Validation is the process of verifying that some data obeys one or more pre-defined constraints. It is, of course, a very common use case in most applications.

验证是验证某些数据是否服从一个或多个预先定义的约束的过程。当然,在大多数应用程序中,这是一个非常常见的用例。

The Java Bean Validation framework (JSR-380) has become the de-facto standard for handling this kind of operations in Java. To recap on the basics of Java Bean Validation please refer to our previous tutorial.

Java Bean 验证框架(JSR-380)已成为处理Java中此类操作的事实标准。要回顾一下Java Bean验证的基础知识,请参考我们之前的教程

Jersey contains an extension module to support Bean Validation. To use this capability in our application, we first need to configure it. In the next section, we’ll see how to configure our application.

Jersey包含一个扩展模块,支持Bean Validation。为了在我们的应用程序中使用这一功能,我们首先需要配置它。在下一节,我们将看到如何配置我们的应用程序。

3. Application Setup

3.应用程序的设置

Now, let’s build on the simple Fruit API example from the excellent Jersey MVC Support article.

现在,让我们在优秀的Jersey MVC支持文章中的简单的Fruit API示例的基础上再接再厉。

3.1. Maven Dependencies

3.1.Maven的依赖性

First of all, let’s add the Bean Validation dependency to our pom.xml:

首先,让我们把Bean Validation的依赖关系添加到我们的pom.xml

<dependency>
    <groupId>org.glassfish.jersey.ext</groupId>
    <artifactId>jersey-bean-validation</artifactId>
    <version>2.27</version>
</dependency>

We can get the latest version from Maven Central.

我们可以从Maven Central获得最新版本。

3.2. Configuring the Server

3.2.配置服务器

In Jersey, we normally register the extension feature we want to use in our custom resource configuration class.

在Jersey中,我们通常在我们的自定义资源配置类中注册我们想要使用的扩展功能。

However, for the bean validation extension, there is no need to do this registration. Fortunately, this is one of the few extensions that the Jersey framework registers automatically.

然而,对于bean验证扩展来说,不需要进行这种注册。幸运的是,这是Jersey框架自动注册的少数扩展之一。

Finally, to send validation errors to the client we’ll add a server property to our a custom resource configuration:

最后,为了向客户端发送验证错误,我们将向我们的自定义资源配置添加一个服务器属性

public ViewApplicationConfig() {
    packages("com.baeldung.jersey.server");
    property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
}

4. Validating JAX-RS Resource Methods

4.验证JAX-RS资源方法

In this section, we’ll explain two different ways of validating input parameters using constraint annotations:

在本节中,我们将解释使用约束注解验证输入参数的两种不同方式。

  • Using built-in Bean Validation API constraints
  • Creating a custom constraint and validator

4.1. Using Built-in Constraint Annotations

4.1.使用内置约束注解

Let’s start by looking at built-in constraint annotations:

让我们先来看看内置的约束注释。

@POST
@Path("/create")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void createFruit(
    @NotNull(message = "Fruit name must not be null") @FormParam("name") String name, 
    @NotNull(message = "Fruit colour must not be null") @FormParam("colour") String colour) {

    Fruit fruit = new Fruit(name, colour);
    SimpleStorageService.storeFruit(fruit);
}

In this example, we create a new Fruit using two form parameters, name and colour. We use the @NotNull annotation which is already part of to the Bean Validation API.

在这个例子中,我们使用两个表单参数namecolour创建一个新的Fruit。我们使用@NotNull注解,这已经是Bean Validation API的一部分。

This imposes a simple not null constraint on our form parameters. In case one of the parameters is null, the message declared within the annotation will be returned.

这对我们的表单参数施加了一个简单的非空约束。如果其中一个参数为null,将返回注释中声明的消息

Naturally, we’ll demonstrate this with a unit test:

当然,我们将用一个单元测试来证明这一点。

@Test
public void givenCreateFruit_whenFormContainsNullParam_thenResponseCodeIsBadRequest() {
    Form form = new Form();
    form.param("name", "apple");
    form.param("colour", null);
    Response response = target("fruit/create").request(MediaType.APPLICATION_FORM_URLENCODED)
        .post(Entity.form(form));

    assertEquals("Http Response should be 400 ", 400, response.getStatus());
    assertThat(response.readEntity(String.class), containsString("Fruit colour must not be null"));
}

In the above example, we use the JerseyTest support class to test our fruit resource. We send a POST request with a null colour and check that the response contains the expected message.

在上述例子中,我们使用JerseyTest支持类来测试我们的水果资源。我们发送一个带有空颜色的POST请求,并检查响应是否包含预期的信息。

For a list of built-in validation constraints, take a look at the docs.

关于内置验证约束的列表,请看一下文档

4.2. Defining a Custom Constraint Annotation

4.2.定义一个自定义约束注释

Sometimes we need to impose more complex constraints. We can do this by defining our own custom annotation.

有时我们需要施加更复杂的约束。我们可以通过定义我们自己的自定义注解来做到这一点。

Using our simple Fruit API example, let’s imagine we need to validate that all fruit have a valid serial number:

使用我们简单的水果API例子,让我们设想一下,我们需要验证所有的水果都有一个有效的序列号。

@PUT
@Path("/update")
@Consumes("application/x-www-form-urlencoded")
public void updateFruit(@SerialNumber @FormParam("serial") String serial) {
    //...
}

In this example, the parameter serial must satisfy the constraints defined by @SerialNumber, which we’ll define next.

在这个例子中,参数serial必须满足@SerialNumber所定义的约束条件,我们接下来将定义这个参数。

We’ll first define the constraint annotation:

我们首先要定义约束注解。

@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { SerialNumber.Validator.class })
    public @interface SerialNumber {

    String message()

    default "Fruit serial number is not valid";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

Next, we’ll define the validator class SerialNumber.Validator:

接下来,我们将定义验证器类SerialNumber.Validator

public class Validator implements ConstraintValidator<SerialNumber, String> {
    @Override
    public void initialize(SerialNumber serial) {
    }

    @Override
    public boolean isValid(String serial, 
        ConstraintValidatorContext constraintValidatorContext) {
        
        String serialNumRegex = "^\\d{3}-\\d{3}-\\d{4}$";
        return Pattern.matches(serialNumRegex, serial);
    }
}

The key point here is the Validator class must implement ConstraintValidator where T is the type of value we want to validate, in our case a String.

这里的关键点是Validator类必须实现ConstraintValidator,其中T是我们想要验证的值类型。在我们的例子中是一个String

Finally, we then implement our custom validation logic in the isValid method.

最后,我们再实现我们在isValid方法中的自定义验证逻辑

5. Resource Validation

5.资源验证

Furthermore, the Bean Validation API also allows us to validate objects using the @Valid annotation.

此外,Bean Validation API还允许我们使用@Valid注解来验证对象

In the next section, we’ll explain two different ways of validating resource classes using this annotation:

在下一节,我们将解释使用该注解验证资源类的两种不同方式。

  • First, Request resource validation
  • Second, Response resource validation

Let’s begin by adding the @Min annotation to our Fruit object:

让我们首先将@Min注解添加到我们的Fruit对象。

@XmlRootElement
public class Fruit {

    @Min(value = 10, message = "Fruit weight must be 10 or greater")
    private Integer weight;
    //...
}

5.1. Request Resource Validation

5.1.请求资源验证

First of all, we’ll enable validation using @Valid in our FruitResource class:

首先,我们将使用@Valid在我们的FruitResource类中启用验证。

@POST
@Path("/create")
@Consumes("application/json")
public void createFruit(@Valid Fruit fruit) {
    SimpleStorageService.storeFruit(fruit);
}

In the above example, if we try to create a fruit with a weight less than 10 we will get a validation error.

在上面的例子中,如果我们试图创建一个重量小于10的水果,我们将得到一个验证错误。

5.2. Response Resource Validation

5.2.响应资源验证

Likewise, in the next example, we’ll see how to validate a response resource:

同样地,在下一个例子中,我们将看到如何验证一个响应资源。

@GET
@Valid
@Produces("application/json")
@Path("/search/{name}")
public Fruit findFruitByName(@PathParam("name") String name) {
    return SimpleStorageService.findByName(name);
}

Note,  how we use the same @Valid annotation. But this time we use it at the resource method level to be sure the response is valid.

注意,我们如何使用相同的@Valid注解。但这次我们在资源方法级别上使用它,以确保响应是有效的。

6. Custom Exception Handler

6.自定义异常处理程序

In this last part, we’ll briefly look at how to create a custom exception handler. This is useful when we want to return a custom response if we violate a particular constraint.

在最后一部分中,我们将简要地看一下如何创建一个自定义的异常处理程序。当我们想在违反某个特定约束时返回一个自定义的响应时,这很有用。

Let’s begin by defining our FruitExceptionMapper:

让我们开始定义我们的FruitExceptionMapper

public class FruitExceptionMapper implements ExceptionMapper<ConstraintViolationException> {

    @Override
    public Response toResponse(ConstraintViolationException exception) {
        return Response.status(Response.Status.BAD_REQUEST)
            .entity(prepareMessage(exception))
            .type("text/plain")
            .build();
    }

    private String prepareMessage(ConstraintViolationException exception) {
        StringBuilder message = new StringBuilder();
        for (ConstraintViolation<?> cv : exception.getConstraintViolations()) {
            message.append(cv.getPropertyPath() + " " + cv.getMessage() + "\n");
        }
        return message.toString();
    }
}

First of all, we define a custom exception mapping provider. In order to do this, we implement the ExceptionMapper interface using a ConstraintViolationException.

首先,我们定义一个自定义的异常映射提供者。为了做到这一点,我们使用ConstraintViolationException来实现ExceptionMapper接口。

Hence, we’ll see that when this exception is thrown the toResponse method of our custom exception mapper instance will be invoked.

因此,我们将看到,当这个异常被抛出时我们自定义异常映射器实例的toResponse方法将被调用

Also, in this simple example, we iterate through all the violations and append each property and message to be sent back in the response.

另外,在这个简单的例子中,我们遍历了所有的违规行为,并在响应中附加了每个属性和消息,以便送回。

Next, in order to use our custom exception mapper we need to register our provider:

接下来,为了使用我们的自定义异常映射器,我们需要注册我们的提供者

@Override
protected Application configure() {
    ViewApplicationConfig config = new ViewApplicationConfig();
    config.register(FruitExceptionMapper.class);
    return config;
}

Finally, we add an endpoint to return an invalid Fruit to show the exception handler in action:

最后,我们添加一个端点来返回一个无效的Fruit ,以显示异常处理程序的运作。

@GET
@Produces(MediaType.TEXT_HTML)
@Path("/exception")
@Valid
public Fruit exception() {
    Fruit fruit = new Fruit();
    fruit.setName("a");
    fruit.setColour("b");
    return fruit;
}

7. Conclusion

7.结论

To summarize, in this tutorial, we’ve explored the Jersey Bean Validation API extension.

总而言之,在本教程中,我们已经探索了Jersey Bean Validation API扩展。

First, we started by introducing how the Bean Validation API can be used in Jersey. Also, we took a look at how to configure an example web application.

首先,我们开始介绍如何在Jersey中使用Bean Validation API。另外,我们还看了一下如何配置一个网络应用的例子。

Finally, we looked at several ways of doing validation with Jersey and how to write a custom exception handler.

最后,我们看了用Jersey做验证的几种方法,以及如何编写一个自定义的异常处理程序。

As always, the full source code of the article is available over on GitHub.

一如既往,该文章的完整源代码可在GitHub上获得