我想知道执行用户输入表单验证的最简洁,最好的方法是什么。我见过一些开发人员实现org.springframework.validation.Validator
。关于这一点的问题:我看到它验证了一个类。是否必须使用用户输入中的值手动填充类,然后将其传递给验证器?
我对用于验证用户输入的最干净,最好的方法感到困惑。我知道使用request.getParameter()
然后手动检查nulls
的传统方法,但我不想在Controller
中进行所有验证。关于这方面的一些好建议将不胜感激。我没有在这个应用程序中使用Hibernate。
答案 0 :(得分:313)
使用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
接口(从Spring 2开始)。
所以,假设你有
public class User {
private String name;
private Integer birthYear;
private User responsibleUser;
...
}
并且您希望进行一些“复杂”验证,例如:如果用户的年龄低于18岁,则责任用户不能为空且责任人的年龄必须超过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的混合使用,因为您替换默认验证器)。或者您可以在控制器的默认构造函数中实例化它。或者在控制器中注入(@Autowired)@ Component / @ Service UserValidator:非常有用,因为大多数验证器都是单例+单元测试模拟变得更容易+验证器可以调用其他Spring组件。
方法3: 为什么不使用这两种方法的组合?使用注释验证简单的东西,比如“name”属性(它可以快速完成,简洁易读)。保持验证器的重要验证(如果编写自定义复杂验证注释需要数小时,或者只是在无法使用注释时)。我是在一个以前的项目中做到这一点,它的工作就像一个魅力,快速和容易。
警告:您不得将验证处理误认为是异常处理。 Read this post知道何时使用它们。
参考文献:
答案 1 :(得分:29)
有两种方法可以验证用户输入:注释和继承Spring的Validator类。对于简单的情况,注释很好。如果您需要复杂的验证(例如跨字段验证,例如“确认电子邮件地址”字段),或者如果你的模型是与不同的规则你的应用程序在多个地方验证,或者如果你没有修改您的能力模型对象通过在其上放置注释,Spring的基于继承的Validator是要走的路。我将展示两者的例子。
无论您使用哪种类型的验证,实际验证部分都是相同的:
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。
希望有所帮助。
答案 2 :(得分:12)
我想延伸杰罗姆·达尔伯特的好回答。我发现很容易用JSR-303方式编写自己的注释验证器。您不限于“一个字段”验证。您可以在类型级别创建自己的注释并进行复杂验证(请参阅下面的示例)。我更喜欢这种方式,因为我不需要像Jerome那样混合不同类型的验证(Spring和JSR-303)。此验证器也是“弹簧识别”,因此您可以使用@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;
}
答案 3 :(得分:3)
如果您对不同的方法处理程序有相同的错误处理逻辑,那么您最终会得到许多具有以下代码模式的处理程序:
if (validation.hasErrors()) {
// do error handling
}
else {
// do the actual business logic
}
假设您正在创建RESTful服务,并希望为每个验证错误案例返回400 Bad Request
以及错误消息。然后,对于需要验证的每个REST端点,错误处理部分都是相同的。在每个处理程序中重复相同的逻辑不是那么 DRY ish!
解决此问题的一种方法是在每个 To-Be-Validated 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!
}
}
您仍然可以使用BindingResult
的{{1}}方法检查基础getBindingResult
。
答案 4 :(得分:1)
查找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);
}
}
答案 5 :(得分:0)
将此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();
}
用于手动验证bean。然后你将获得BindingResult中的所有结果,你可以从那里检索。