Spring MVC: 如何执行验证?

我想知道执行用户输入的表单验证的最干净和最好的方法是什么。我见过一些开发人员实现 org.springframework.validation.Validator。关于这个问题: 我看到它验证了一个类。是否必须用来自用户输入的值手动填充类,然后传递给验证器?

我对验证用户输入的最干净和最好的方法感到困惑。我知道使用 request.getParameter()然后手动检查 nulls的传统方法,但是我不想在我的 Controller中做所有的验证。在这方面提出一些好的建议,我们将不胜感激。我在这个应用程序中不使用 Hibernate。

205826 次浏览

有两种方法可以验证用户输入: 注释和继承 Spring 的 Validator 类。对于简单的情况,注释是很好的。如果你需要复杂的验证(比如跨字段验证,例如“验证邮件地址”字段) ,或者如果你的模型在应用程序的多个地方用不同的规则进行了验证,或者如果你没有能力通过在模型对象上放置注释来修改模型对象,Spring 的基于继承的验证器就是最好的选择。我将展示两者的例子。

无论您使用哪种类型的验证,实际的验证部分都是相同的:

RequestMapping(value="fooPage", method = RequestMethod.POST)
public String processSubmit(@Valid @ModelAttribute("foo") Foo foo, BindingResult result, ModelMap m) {
if(result.hasErrors()) {
return "fooPage";
}
...
return "successPage";
}

如果您正在使用注释,那么您的 Foo类可能看起来像:

public class Foo {


@NotNull
@Size(min = 1, max = 20)
private String name;


@NotNull
@Min(1)
@Max(110)
private Integer age;


// getters, setters
}

上面的注释是 javax.validation.constraints注释,你也可以使用 Hibernate 的 org.hibernate.validator.constraints,但是看起来你并没有在使用 Hibernate。

或者,如果您实现 Spring 的 Validator,您将创建一个类,如下所示:

public class FooValidator implements Validator {


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


@Override
public void validate(Object target, Errors errors) {


Foo foo = (Foo) target;


if(foo.getName() == null) {
errors.rejectValue("name", "name[emptyMessage]");
}
else if(foo.getName().length() < 1 || foo.getName().length() > 20){
errors.rejectValue("name", "name[invalidLength]");
}


if(foo.getAge() == null) {
errors.rejectValue("age", "age[emptyMessage]");
}
else if(foo.getAge() < 1 || foo.getAge() > 110){
errors.rejectValue("age", "age[invalidAge]");
}
}
}

如果使用上面的验证器,您还必须将验证器绑定到 Spring 控制器(如果使用注释则不必) :

@InitBinder("foo")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new FooValidator());
}

也请参阅 Spring Docs

希望能帮上忙。

对于 Spring MVC,有3种不同的方法来执行验证: 使用注释,手动,或者两者兼而有之。没有一个独特的“最干净和最好的方式”来验证,但可能有一个更适合您的项目/问题/上下文。

让我们有一个用户:

public class User {


private String name;


...


}

方法1: 如果你有 Spring 3。要做 x + 和简单验证,可以使用 javax.validation.constraints注释(也称为 JSR-303注释)。

public class User {


@NotNull
private String name;


...


}

您的库中将需要一个 JSR-303提供者,比如 Hibernate Validator,它是参考实现(这个库与数据库和关系映射无关,它只是进行验证: ——)。

然后在你的控制器里,你会有这样的东西:

@RequestMapping(value="/user", method=RequestMethod.POST)
public createUser(Model model, @Valid @ModelAttribute("user") User user, BindingResult result){
if (result.hasErrors()){
// do something
}
else {
// do something else
}
}

注意@Valid: 如果用户正好有一个空名称,则 result. hasErrors()将为 true。

方法2: 如果您有复杂的验证(比如大型业务验证逻辑、跨多个字段的条件验证等) ,或者由于某些原因您不能使用方法1,那么使用手动验证。将控制器的代码从验证逻辑中分离出来是一种很好的做法。不要从头创建验证类,Spring 提供了一个方便的 org.springframework.validation.Validator接口(自 Spring2以来)。

假设你有

public class User {


private String name;


private Integer birthYear;
private User responsibleUser;
...


}

您需要进行一些“复杂的”验证,比如: 如果用户的年龄小于18岁,则 responbleUser 必须不为 null,且 responbleUser 的年龄必须大于21岁。

你会做这样的事情

public class UserValidator implements Validator {


@Override
public boolean supports(Class clazz) {
return User.class.equals(clazz);
}


@Override
public void validate(Object target, Errors errors) {
User user = (User) target;


if(user.getName() == null) {
errors.rejectValue("name", "your_error_code");
}


// do "complex" validation here


}


}

然后在你的控制器中你会看到:

@RequestMapping(value="/user", method=RequestMethod.POST)
public createUser(Model model, @ModelAttribute("user") User user, BindingResult result){
UserValidator userValidator = new UserValidator();
userValidator.validate(user, result);


if (result.hasErrors()){
// do something
}
else {
// do something else
}
}

如果存在验证错误,result. hasErrors()将为 true。

注意: 您还可以使用“ binder.setValidator (...)”在控制器的@InitBinder 方法中设置验证器(在这种情况下,混合使用方法1和方法2是不可能的,因为您替换了默认的验证器)。或者您可以在控制器的缺省构造函数中实例化它。或者在控制器中注入一个@Component/@Service UserValidator (@Autowired) : 非常有用,因为大多数验证器是单例的 + 单元测试模拟变得更容易 + 你的验证器可以调用其他 Spring 组件。

方法三: 为什么不结合使用这两种方法呢?使用注释来验证简单的东西,比如“ name”属性(这样做很快,简洁且更易读)。为验证器保留繁重的验证工作(编写定制的复杂验证注释需要花费数小时,或者只是在不可能使用注释的情况下)。我在以前的一个项目中做到了这一点,它像一个魅力,快速简单。

警告: 不要把 验证处理错当成 异常处理读读这篇文章知道什么时候使用它们。

参考文献:

我想给杰罗姆 · 达尔伯特一个很好的回答。我发现用 JSR-303方式编写自己的注释验证器非常容易。您不限于拥有“一个字段”验证。您可以在类型级别创建自己的注释,并进行复杂的验证(参见下面的示例)。我更喜欢这种方式,因为我不需要像 Jerome 那样混合不同类型的验证(Spring 和 JSR-303)。此外,这个验证器是“支持 Spring 的”,因此您可以使用@Inject/@Autowire 开箱即用。

自定义对象验证示例:

@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { YourCustomObjectValidator.class })
public @interface YourCustomObjectValid {


String message() default "{YourCustomObjectValid.message}";


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


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


public class YourCustomObjectValidator implements ConstraintValidator<YourCustomObjectValid, YourCustomObject> {


@Override
public void initialize(YourCustomObjectValid constraintAnnotation) { }


@Override
public boolean isValid(YourCustomObject value, ConstraintValidatorContext context) {


// Validate your complex logic


// Mark field with error
ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
cvb.addNode(someField).addConstraintViolation();


return true;
}
}


@YourCustomObjectValid
public YourCustomObject {
}

泛型字段相等的示例:

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
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;
import javax.validation.Payload;


@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { FieldsEqualityValidator.class })
public @interface FieldsEquality {


String message() default "{FieldsEquality.message}";


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


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


/**
* Name of the first field that will be compared.
*
* @return name
*/
String firstFieldName();


/**
* Name of the second field that will be compared.
*
* @return name
*/
String secondFieldName();


@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface List {
FieldsEquality[] value();
}
}








import java.lang.reflect.Field;


import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;


public class FieldsEqualityValidator implements ConstraintValidator<FieldsEquality, Object> {


private static final Logger log = LoggerFactory.getLogger(FieldsEqualityValidator.class);


private String firstFieldName;
private String secondFieldName;


@Override
public void initialize(FieldsEquality constraintAnnotation) {
firstFieldName = constraintAnnotation.firstFieldName();
secondFieldName = constraintAnnotation.secondFieldName();
}


@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null)
return true;


try {
Class<?> clazz = value.getClass();


Field firstField = ReflectionUtils.findField(clazz, firstFieldName);
firstField.setAccessible(true);
Object first = firstField.get(value);


Field secondField = ReflectionUtils.findField(clazz, secondFieldName);
secondField.setAccessible(true);
Object second = secondField.get(value);


if (first != null && second != null && !first.equals(second)) {
ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
cvb.addNode(firstFieldName).addConstraintViolation();


ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
cvb.addNode(someField).addConstraintViolation(secondFieldName);


return false;
}
} catch (Exception e) {
log.error("Cannot validate fileds equality in '" + value + "'!", e);
return false;
}


return true;
}
}


@FieldsEquality(firstFieldName = "password", secondFieldName = "confirmPassword")
public class NewUserForm {


private String password;


private String confirmPassword;


}

查找 Spring Mvc 验证的完整示例

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.technicalkeeda.bean.Login;


public class LoginValidator implements Validator {
public boolean supports(Class aClass) {
return Login.class.equals(aClass);
}


public void validate(Object obj, Errors errors) {
Login login = (Login) obj;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName",
"username.required", "Required field");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userPassword",
"userpassword.required", "Required field");
}
}




public class LoginController extends SimpleFormController {
private LoginService loginService;


public LoginController() {
setCommandClass(Login.class);
setCommandName("login");
}


public void setLoginService(LoginService loginService) {
this.loginService = loginService;
}


@Override
protected ModelAndView onSubmit(Object command) throws Exception {
Login login = (Login) command;
loginService.add(login);
return new ModelAndView("loginsucess", "login", login);
}
}

如果对于不同的方法处理程序有相同的错误处理逻辑,那么您最终会得到大量具有以下代码模式的处理程序:

if (validation.hasErrors()) {
// do error handling
}
else {
// do the actual business logic
}

假设您正在创建 RESTful 服务,并且希望为每个验证错误情况返回 400 Bad Request以及错误消息。然后,对于需要验证的每个 REST 端点,错误处理部分都是相同的。在每个单独的处理程序中重复相同的逻辑不是那么 干的ish!

解决这个问题的一种方法是在每个 待验证 bean 之后立即删除 BindingResult。现在,你的联络人会是这样的:

@RequestMapping(...)
public Something doStuff(@Valid Somebean bean) {
// do the actual business logic
// Just the else part!
}

这样,如果绑定的 bean 无效,Spring 将抛出 MethodArgumentNotValidException。您可以使用相同的错误处理逻辑定义处理此异常的 ControllerAdvice:

@ControllerAdvice
public class ErrorHandlingControllerAdvice {
@ExceptionHandler(MethodArgumentNotValidException.class)
public SomeErrorBean handleValidationError(MethodArgumentNotValidException ex) {
// do error handling
// Just the if part!
}
}

您仍然可以使用 MethodArgumentNotValidExceptiongetBindingResult方法检查底层 BindingResult

将这个 bean 放到配置类中。

 @Bean
public Validator localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}

然后你就可以

 <T> BindingResult validate(T t) {
DataBinder binder = new DataBinder(t);
binder.setValidator(validator);
binder.validate();
return binder.getBindingResult();
}

然后您将得到 BindingResult 中的所有结果,并且您可以从那里检索。