当前位置: 首页 > 知识库问答 >
问题:

如何在Spring中使用自定义验证器

孟福
2023-03-14

我正在构建一个Spring Boot应用程序,并试图为我将在服务层验证的一些DTO/实体实现自定义验证。基于Spring关于这个问题的文档,我认为一种方法是实现org。springframework。验证。验证程序界面。

作为一个最小、完整、可重复的示例,请考虑以下代码:

Spring初始化引导项目

在src/main/java/com中添加了以下代码。实例使用验证:

// Person.java

package com.example.usingvalidation;

public class Person {
    private String firstName;
    private String lastName;
    private int age;
    private String gender;

    public Person() {
    }

    public Person(String firstName, String lastName, int age, String gender) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.gender = gender;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Person{" +
                "firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }
}
// PersonValidator.java

package com.example.usingvalidation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

@Component
public class PersonValidator implements Validator {

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Override
    public boolean supports(Class<?> clazz) {
        log.info("supports called");
       return Person.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        log.info("validate called");
        Person person = (Person) target;
        errors.reject("E00001", "This is the default error message, just to test.");
    }
}
// MyController.java
package com.example.usingvalidation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.ConstraintViolation;
import java.util.Set;

@RestController
public class MyController {
    private final Logger log = LoggerFactory.getLogger(this.getClass());
    private final LocalValidatorFactoryBean validatorFactory;

    @Autowired
    public MyController(LocalValidatorFactoryBean validatorFactory) {
        this.validatorFactory = validatorFactory;
    }

    @GetMapping("/")
    public Person getPerson(@RequestBody Person person) {
        log.info("calling validate");
        Set<ConstraintViolation<Person>> errors =  validatorFactory.validate(person);
        log.info("called validate, result: {}", errors);
        return null;
    }
}
// UsingValidationApplication.java  nothing changed here

package com.example.usingvalidation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {
    private final Logger log = LoggerFactory.getLogger(this.getClass());
    private final LocalValidatorFactoryBean validatorFactory;

    @Autowired
    public MyController(LocalValidatorFactoryBean validatorFactory) {
        this.validatorFactory = validatorFactory;
    }

    @GetMapping("/")
    public Person getPerson(@RequestBody Person person) {
        log.info("calling validate");
        validatorFactory.validate(person);
        return null;
    }
}

如果我点击endpoint触发验证,则不会发生任何事情。我看到调用validate日志消息。但errors对象为空。PersonValidator中的所有日志消息都没有被记录,因此很明显那里没有呼叫。

我的问题是:如何向Spring注册验证器,以便使用验证器?

我已经多次阅读了这些文档和数百个SO问题(比如java——使用JSR-303和spring的验证器堆栈溢出的组合为Spring Bootendpoint实现自定义验证逻辑),但都无济于事。

  • 如果有任何像@NotNull这样的JSR-303注释,那么当前设置将拾取与JSR-303验证相关的错误。但这不是我需要的,我需要它来使用我的自定义验证器。
  • 我看到了其他SO问题,其中InitBinder在控制器中用于向Spring注册验证器。但我不想这样做,因为我计划在服务层进行这些自定义验证。

共有2个答案

燕文昌
2023-03-14

您应该使用已验证的注释对控制器进行注释。不要忘记将spring boot starter验证添加到pom中。

宰父淳
2023-03-14

这对您不起作用的主要原因是您尚未向DataBinder注册验证器。

对您的控制器进行一些更改。不要自动连接LocalValidatorFactoryBean,而是自动将您的验证器连接到控制器并将它们注册到DataBinder。

@Autowired
private PersonValidator personValidator;

@InitBinder
public void initBinder(WebDataBinder binder) {
  binder.addValidators(personValidator);
}

您的控制器方法也会更简单,因为您不再需要显式调用ValidatorFactory,当您向方法参数添加@Valid注释时,Spring将自动调用验证器。将BindingResult参数添加到方法中,来自验证器的所有错误都将出现在BindingResult错误中,这包括由javax验证引起的错误,如@Min、@Max、@NotNull等。

@GetMapping("/")
public Person getPerson(@RequestBody @Valid Person person, BindingResult bindingResult) {
   if (bindingResult.hasErrors()) {
      log.info(bindingResult.getAllErrors());
   }
   return null;
}

由于您希望在服务层中执行此操作,因此必须编写自己的逻辑来处理此操作。Spring在调用自定义验证方面没有任何魔力。这是有意的,进入应用程序是通过控制器进行的,这是您对输入数据控制有限的地方,因此如果您想验证,应该在这里进行处理。您完全控制的控制器下游的Person对象的每一个突变。如果您觉得必须在服务层进行验证,那么您将自己编写这篇文章,坦率地说,我不会为此使用Spring的验证器实现。如果您对在服务层中执行此操作毫无兴趣,这里有一种方法可以实现。

创建一个注释以应用于您的个人类

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;

@Documented
@Constraint(validatedBy = PersonValidator.class)
@Target({TYPE})
@Retention(RUNTIME)
public @interface ValidPerson {

  String message() default "This isn't correct";

  Class[] groups() default {};

  Class[] payload() default {};
}

将上述注释添加到您的Person类中。

@ValidPerson
public class Person {

将您的Person Validator修改为ConstraintValidator,我已经组合了一个实现来验证Person上的两个字段。

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.util.ObjectUtils;


public class PersonValidator implements ConstraintValidator<ValidPerson, Person> {

  @Override
  public void initialize(ValidPerson constraintAnnotation) {
    ConstraintValidator.super.initialize(constraintAnnotation);
  }

  @Override
  public boolean isValid(Person value, ConstraintValidatorContext context) {
     boolean isErrored = false;

     if (ObjectUtils.isEmpty(value.getLastName())) {
        context.disableDefaultConstraintViolation();
        context.buildConstraintViolationWithTemplate("lastName can't be empty").addConstraintViolation();
        isErrored = true;
     }
     if (value.getAge() < 0) {
        context.disableDefaultConstraintViolation();
        context.buildConstraintViolationWithTemplate("You're not old enough to be alive").addConstraintViolation();
        isErrored = true;
     }

    return !isErrored;
  }
}

在您的服务类中,注入一个验证器并在方法中调用它,这将调用您定义并添加到人员中的约束验证程序

import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class PersonService {

  @Autowired
  private Validator validator;

  public Person updatePerson(Person person) {
    Set<ConstraintViolation<Person>> validate = validator.validate(person);

    return person;
  }
}

您可以使用AOP做一些花哨的事情来自动执行此操作,就像Spring在控制器方面所做的那样,但我会让您自己去发现。

 类似资料:
  • 问题内容: 我有其他人编写的REST api,其中处理请求到特定url的方法接受一堆从路径参数填充的参数。 撰写此文章的人曾经使用过DropWizard,但我以前没有使用它的经验。我的任务是通过将其与db中的值进行比较来验证studentId字段。这将非常简单,但是我被告知要使用自定义验证程序来执行此操作。我对编写注释非常陌生,但经过大量挖掘后,这样编写了注释, 此后,我将注释添加到了要像这样运行

  • 问题内容: 我在这里尝试了答案中的步骤:HibernateValidator,自定义ResourceBundleLocator和Spring 但是仍然只是作为输出而不是消息。 dispatcher-servlet.xml /WEB-INF/validationMessages.properties: 表格(上课地点) 这是怎么了 问题答案: 得到它了!:-) 我在我的计算机中添加了以下bean而不

  • 我想确保在创建新用户名或更新用户名时,用户名是唯一的。我写了下面的代码,它可以很好地创建和更新用户名。但是,如果只更新其他用户字段,如年龄或性别,并保留相同的用户名,它将返回invalid,因为用户名已经存在。 重要的是,我想要的验证是使用BindingResault。我已经有一个数据库,将用户名视为唯一的限制。但是现在我想要用BindingResault进行同样的验证。但是我更新用户时的方式会导

  • 我有一个自定义组件在我的应用程序上继承ControlValueAccessor,它集成了ng-select 这个组件的目标是有一个中心位置来集成ng-select,这样我就可以在整个应用程序中重用它。 这就是我调用验证方法的方式: 这就是我通常在父组件上实例化formControl的方式: 我想给父级一个选项,在我的自定义组件上使用内置的角度验证器。(必填项,电子邮件,最小值,最大值……)。 问题

  • 问题内容: 我正在使用带有自定义验证的Angular2的FormBuilder开发表单。问题:在customValidator中,我用来访问本地对象。执行验证时出现错误。 看起来customValidator是在其他对象中执行的,因此更改了引用 问题: 如何传递对customValidator 的引用? 问题答案: 使用箭头函数,以确保该函数绑定到此:

  • 我正在尝试编写一个自定义bean验证器,并根据用户界面上的语言环境显示验证消息。 为此,我创建了一个验证器,如下所示: 我还注册了messageSource和validator bean: 在我的控制器中,我使用initBinder注册我的验证器: 不过,验证错误消息在用户界面上显示为{myproject.myclass.validation.name}。即使我设置了LocalValidatorF