阅读(4866) (0)

Micronaut 数据验证

2023-02-23 11:24:34 更新

使用验证建议可以很容易地验证 Micronaut 控制器传入的数据。

Micronaut 为具有 micronaut-validation 依赖项的 javax.validation 注解提供原生支持:

 Gradle Maven 
implementation("io.micronaut:micronaut-validation")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-validation</artifactId>
</dependency>

或者完全符合 micronaut-hibernate-validator 依赖的 JSR 380:

 Gradle Maven 
implementation("io.micronaut.beanvalidation:micronaut-hibernate-validator")
<dependency>
    <groupId>io.micronaut.beanvalidation</groupId>
    <artifactId>micronaut-hibernate-validator</artifactId>
</dependency>

我们可以在类级别使用 javax.validation 注释和 Validated 注释来验证参数。

示例

 Java Groovy  Kotlin 
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.validation.Validated;

import javax.validation.constraints.NotBlank;
import java.util.Collections;

@Validated // (1)
@Controller("/email")
public class EmailController {

    @Get("/send")
    public HttpResponse send(@NotBlank String recipient, // (2)
                             @NotBlank String subject) { // (2)
        return HttpResponse.ok(Collections.singletonMap("msg", "OK"));
    }
}
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.validation.Validated

import javax.validation.constraints.NotBlank

@Validated // (1)
@Controller("/email")
class EmailController {

    @Get("/send")
    HttpResponse send(@NotBlank String recipient, // (2)
                      @NotBlank String subject) { // (2)
        HttpResponse.ok(msg: "OK")
    }
}
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.validation.Validated
import javax.validation.constraints.NotBlank

@Validated // (1)
@Controller("/email")
open class EmailController {

    @Get("/send")
    open fun send(@NotBlank recipient: String, // (2)
                  @NotBlank subject: String): HttpResponse<*> { // (2)
        return HttpResponse.ok(mapOf("msg" to "OK"))
    }
}
  1. 使用 Validated 注释控制器

  2. 主题和收件人不能为空。

如果发生验证错误,则抛出 javax.validation.ConstraintViolationException。默认情况下,集成的 io.micronaut.validation.exception.ConstraintExceptionHandler 处理异常,导致如下测试所示的行为:

示例测试

 Java Groovy  Kotlin 
@Test
public void testParametersAreValidated() {
    HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () ->
        client.toBlocking().exchange("/email/send?subject=Hi&recipient="));
    HttpResponse<?> response = e.getResponse();

    assertEquals(HttpStatus.BAD_REQUEST, response.getStatus());

    response = client.toBlocking().exchange("/email/send?subject=Hi&recipient=me@micronaut.example");

    assertEquals(HttpStatus.OK, response.getStatus());
}
def "test parameter validation"() {
    when:
    client.toBlocking().exchange('/email/send?subject=Hi&recipient=')

    then:
    def e = thrown(HttpClientResponseException)
    def response = e.response
    response.status == HttpStatus.BAD_REQUEST

    when:
    response = client.toBlocking().exchange('/email/send?subject=Hi&recipient=me@micronaut.example')

    then:
    response.status == HttpStatus.OK
}
"test params are validated"() {
    val e = shouldThrow<HttpClientResponseException> {
        client.toBlocking().exchange<Any>("/email/send?subject=Hi&recipient=")
    }
    var response = e.response

    response.status shouldBe HttpStatus.BAD_REQUEST

    response = client.toBlocking().exchange<Any>("/email/send?subject=Hi&recipient=me@micronaut.example")

    response.status shouldBe HttpStatus.OK
}

要使用您自己的 ExceptionHandler 来处理约束异常,请使用 @Replaces(ConstraintExceptionHandler.class) 对其进行注释

通常您可能希望使用 POJO 作为控制器方法参数。

 Java Groovy  Kotlin 
import io.micronaut.core.annotation.Introspected;

import javax.validation.constraints.NotBlank;

@Introspected
public class Email {

    @NotBlank // (1)
    String subject;

    @NotBlank // (1)
    String recipient;

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getRecipient() {
        return recipient;
    }

    public void setRecipient(String recipient) {
        this.recipient = recipient;
    }
}
import io.micronaut.core.annotation.Introspected

import javax.validation.constraints.NotBlank

@Introspected
class Email {

    @NotBlank // (1)
    String subject

    @NotBlank // (1)
    String recipient
}
import io.micronaut.core.annotation.Introspected
import javax.validation.constraints.NotBlank

@Introspected
open class Email {

    @NotBlank // (1)
    var subject: String? = null

    @NotBlank // (1)
    var recipient: String? = null
}
  1. 您可以在 POJO 中使用 javax.validation 注释。

使用 Validated 注释您的控制器,并使用 @Valid 注释绑定 POJO。

例子

 Java Groovy  Kotlin 
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.validation.Validated;

import javax.validation.Valid;
import java.util.Collections;

@Validated // (1)
@Controller("/email")
public class EmailController {

    @Post("/send")
    public HttpResponse send(@Body @Valid Email email) { // (2)
        return HttpResponse.ok(Collections.singletonMap("msg", "OK"));
    }
}
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.validation.Validated

import javax.validation.Valid

@Validated // (1)
@Controller("/email")
class EmailController {

    @Post("/send")
    HttpResponse send(@Body @Valid Email email) { // (2)
        HttpResponse.ok(msg: "OK")
    }
}
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.validation.Validated
import javax.validation.Valid

@Validated // (1)
@Controller("/email")
open class EmailController {

    @Post("/send")
    open fun send(@Body @Valid email: Email): HttpResponse<*> { // (2)
        return HttpResponse.ok(mapOf("msg" to "OK"))
    }
}
  1. 用 Validated 注释控制器

  2. 注释 POJO 以使用 @Valid 进行验证

POJO 的验证显示在以下测试中:

 Java Groovy  Kotlin 
@Test
public void testPojoValidation() {
    HttpClientResponseException e = assertThrows(HttpClientResponseException.class, () -> {
        Email email = new Email();
        email.subject = "Hi";
        email.recipient = "";
        client.toBlocking().exchange(HttpRequest.POST("/email/send", email));
    });
    HttpResponse<?> response = e.getResponse();

    assertEquals(HttpStatus.BAD_REQUEST, response.getStatus());

    Email email = new Email();
    email.subject = "Hi";
    email.recipient = "me@micronaut.example";
    response = client.toBlocking().exchange(HttpRequest.POST("/email/send", email));

    assertEquals(HttpStatus.OK, response.getStatus());
}
def "invoking /email/send parse parameters in a POJO and validates"() {
    when:
    Email email = new Email(subject: 'Hi', recipient: '')
    client.toBlocking().exchange(HttpRequest.POST('/email/send', email))

    then:
    def e = thrown(HttpClientResponseException)
    def response = e.response
    response.status == HttpStatus.BAD_REQUEST

    when:
    email = new Email(subject: 'Hi', recipient: 'me@micronaut.example')
    response = client.toBlocking().exchange(HttpRequest.POST('/email/send', email))

    then:
    response.status == HttpStatus.OK
}
"test pojo validation" {
    val e = shouldThrow<HttpClientResponseException> {
        val email = Email()
        email.subject = "Hi"
        email.recipient = ""
        client.toBlocking().exchange<Email, Any>(HttpRequest.POST("/email/send", email))
    }
    var response = e.response

    response.status shouldBe HttpStatus.BAD_REQUEST

    val email = Email()
    email.subject = "Hi"
    email.recipient = "me@micronaut.example"
    response = client.toBlocking().exchange<Email, Any>(HttpRequest.POST("/email/send", email))

    response.status shouldBe HttpStatus.OK
}

Bean 注入在 Hibernate Validator 配置的自定义约束中受支持。

验证组

您可以使用验证组使用验证组来强制约束的子集。 

 Java Groovy  Kotlin 
import javax.validation.groups.Default;

public interface FinalValidation extends Default {} // (1)
import javax.validation.groups.Default

interface FinalValidation extends Default {} // (1)
import javax.validation.groups.Default

interface FinalValidation : Default {} // (1)
  1. 定义自定义验证组。这一个扩展了默认值,因此使用该组完成的任何验证都将包括默认组中的约束。

 Java Groovy  Kotlin 
import io.micronaut.core.annotation.Introspected;

import javax.validation.constraints.NotBlank;

@Introspected
public class Email {

    @NotBlank // (1)
    String subject;

    @NotBlank(groups = FinalValidation.class) // (2)
    String recipient;

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getRecipient() {
        return recipient;
    }

    public void setRecipient(String recipient) {
        this.recipient = recipient;
    }
}
import io.micronaut.core.annotation.Introspected

import javax.validation.constraints.NotBlank

@Introspected
class Email {

    @NotBlank // (1)
    String subject

    @NotBlank(groups = FinalValidation) // (2)
    String recipient
}
import io.micronaut.core.annotation.Introspected
import javax.validation.constraints.NotBlank

@Introspected
open class Email {

    @NotBlank // (1)
    var subject: String? = null

    @NotBlank(groups = [FinalValidation::class]) // (2)
    var recipient: String? = null
}
  1. 使用默认验证组指定约束。只有在默认处于活动状态时才会强制执行此约束。

  2. 使用自定义 FinalValidation 验证组指定约束。只有在 FinalValidation 处于活动状态时才会强制执行此约束。

使用 Validated 注释您的控制器,指定将激活的验证组或让它默认为 Default。还要用@Valid 注释绑定的POJO。

示例

 Java Groovy  Kotlin 
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.validation.Validated;

import javax.validation.Valid;
import java.util.Collections;

@Validated // (1)
@Controller("/email")
public class EmailController {

    @Post("/createDraft")
    public HttpResponse createDraft(@Body @Valid Email email) { // (2)
        return HttpResponse.ok(Collections.singletonMap("msg", "OK"));
    }

    @Post("/send")
    @Validated(groups = FinalValidation.class) // (3)
    public HttpResponse send(@Body @Valid Email email) { // (4)
        return HttpResponse.ok(Collections.singletonMap("msg", "OK"));
    }
}
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.validation.Validated

import javax.validation.Valid

@Validated // (1)
@Controller("/email")
class EmailController {

    @Post("/createDraft")
    HttpResponse createDraft(@Body @Valid Email email) { // (2)
        HttpResponse.ok(msg: "OK")
    }

    @Post("/send")
    @Validated(groups = [FinalValidation]) // (3)
    HttpResponse send(@Body @Valid Email email) { // (4)
        HttpResponse.ok(msg: "OK")
    }
}
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.validation.Validated
import javax.validation.Valid

@Validated // (1)
@Controller("/email")
open class EmailController {

    @Post("/createDraft")
    open fun createDraft(@Body @Valid email: Email): HttpResponse<*> { // (2)
        return HttpResponse.ok(mapOf("msg" to "OK"))
    }

    @Post("/send")
    @Validated(groups = [FinalValidation::class]) // (3)
    open fun send(@Body @Valid email: Email): HttpResponse<*> { // (4)
        return HttpResponse.ok(mapOf("msg" to "OK"))
    }
}
  1. 在不指定组的情况下使用 Validated 注释意味着默认组将处于活动状态。由于这是在类上定义的,因此它将适用于所有方法。

  2. 默认验证组中的约束将被强制执行,从类继承。效果是调用此方法时不会强制执行 email.recipient 上的 @NotBlank。

  3. 指定组意味着调用此方法时将强制执行这些验证组。请注意,FinalValidation 扩展了 Default,因此将强制执行来自两个组的约束。

  4. Default 和 FinalValidation 验证组中的约束将被强制执行,因为 FinalValidation 扩展了 Default。效果是当调用此方法时,将强制执行电子邮件中的两个 @NotBlank 约束。

使用默认验证组的 POJO 验证显示在以下测试中:

 Java Groovy  Kotlin 
@Test
public void testPojoValidation_defaultGroup() {
    HttpClientResponseException e = assertThrows(HttpClientResponseException.class, () -> {
        Email email = new Email();
        email.subject = "";
        email.recipient = "";
        client.toBlocking().exchange(HttpRequest.POST("/email/createDraft", email));
    });
    HttpResponse<?> response = e.getResponse();

    assertEquals(HttpStatus.BAD_REQUEST, response.getStatus());

    Email email = new Email();
    email.subject = "Hi";
    email.recipient = "";
    response = client.toBlocking().exchange(HttpRequest.POST("/email/createDraft", email));

    assertEquals(HttpStatus.OK, response.getStatus());
}
def "invoking /email/createDraft parse parameters in a POJO and validates using default validation groups"() {
    when:
    Email email = new Email(subject: '', recipient: '')
    client.toBlocking().exchange(HttpRequest.POST('/email/createDraft', email))

    then:
    def e = thrown(HttpClientResponseException)
    def response = e.response
    response.status == HttpStatus.BAD_REQUEST

    when:
    email = new Email(subject: 'Hi', recipient: '')
    response = client.toBlocking().exchange(HttpRequest.POST('/email/createDraft', email))

    then:
    response.status == HttpStatus.OK
}
"test pojo validation using default validation groups" {
    val e = shouldThrow<HttpClientResponseException> {
        val email = Email()
        email.subject = ""
        email.recipient = ""
        client.toBlocking().exchange<Email, Any>(HttpRequest.POST("/email/createDraft", email))
    }
    var response = e.response

    response.status shouldBe HttpStatus.BAD_REQUEST

    val email = Email()
    email.subject = "Hi"
    email.recipient = ""
    response = client.toBlocking().exchange<Email, Any>(HttpRequest.POST("/email/createDraft", email))

    response.status shouldBe HttpStatus.OK
}

以下测试显示了使用自定义 FinalValidation 验证组对 POJO 的验证:

 Java Groovy  Kotlin 
@Test
public void testPojoValidation_finalValidationGroup() {
    HttpClientResponseException e = assertThrows(HttpClientResponseException.class, () -> {
        Email email = new Email();
        email.subject = "Hi";
        email.recipient = "";
        client.toBlocking().exchange(HttpRequest.POST("/email/send", email));
    });
    HttpResponse<?> response = e.getResponse();

    assertEquals(HttpStatus.BAD_REQUEST, response.getStatus());

    Email email = new Email();
    email.subject = "Hi";
    email.recipient = "me@micronaut.example";
    response = client.toBlocking().exchange(HttpRequest.POST("/email/send", email));

    assertEquals(HttpStatus.OK, response.getStatus());
}
def "invoking /email/send parse parameters in a POJO and validates using FinalValidation validation group"() {
    when:
    Email email = new Email(subject: 'Hi', recipient: '')
    client.toBlocking().exchange(HttpRequest.POST('/email/send', email))

    then:
    def e = thrown(HttpClientResponseException)
    def response = e.response
    response.status == HttpStatus.BAD_REQUEST

    when:
    email = new Email(subject: 'Hi', recipient: 'me@micronaut.example')
    response = client.toBlocking().exchange(HttpRequest.POST('/email/send', email))

    then:
    response.status == HttpStatus.OK
}
"test pojo validation using FinalValidation validation group" {
    val e = shouldThrow<HttpClientResponseException> {
        val email = Email()
        email.subject = "Hi"
        email.recipient = ""
        client.toBlocking().exchange<Email, Any>(HttpRequest.POST("/email/send", email))
    }
    var response = e.response

    response.status shouldBe HttpStatus.BAD_REQUEST

    val email = Email()
    email.subject = "Hi"
    email.recipient = "me@micronaut.example"
    response = client.toBlocking().exchange<Email, Any>(HttpRequest.POST("/email/send", email))

    response.status shouldBe HttpStatus.OK
}