Spring MVC:Как выполнить проверку?
Я хотел бы знать, что является самым чистым и лучшим способом для выполнения проверки формы пользовательских входов. Я видел, как некоторые разработчики реализуют org.springframework.validation.Validator
. Вопрос об этом: я видел, что он проверяет класс. Должен ли класс быть заполнен вручную значениями из пользовательского ввода, а затем передан валидатору?
Я запутался в самом чистом и лучшем способе проверки пользовательского ввода. Я знаю о традиционном методе использования request.getParameter()
и затем ручная проверка на nulls
, но я не хочу делать все проверки в моем Controller
. Некоторые хорошие советы в этой области будут высоко оценены. Я не использую Hibernate в этом приложении.
6 ответов:
С Spring MVC существует 3 различных способа выполнения проверки: использование аннотации, вручную или сочетание обоих. Существует не единственный "самый чистый и лучший способ" для проверки, но, вероятно, есть тот, который лучше подходит для вашего проекта/проблемы/контекста.
давайте иметь пользователя:
public class User { private String name; ... }
Способ 1 : если у вас есть пружина 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:если у пользователя есть нулевое имя, результат.hasErrors() будет истина.
Способ 2 : если у вас есть сложные проверки (например, логика проверки большого бизнеса, условная проверка по нескольким полям и т. д.), или по какой-то причине вы не можете использовать метод 1, используйте ручную проверку. Рекомендуется отделить код контроллера от логики проверки. Не создавайте свой класс(ы) проверки с нуля, Весна обеспечивает удобный
org.springframework.validation.Validator
интерфейс (с весны 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 } }
если есть ошибки проверки, результат.hasErrors() будет истина.
Примечание: Вы также можете установить валидатор в методе @InitBinder контроллера, с "binder.setValidator(...) "(в этом случае смешанное использование метода 1 и 2 было бы невозможно, поскольку заменить валидатор). Или вы можете создать его экземпляр в конструкторе по умолчанию контроллера. Или у вас есть@Component/ @ Service UserValidator, который вы вводите (@Autowired) в свой контроллер : очень полезно, потому что большинство валидаторов являются синглетами + модульный тест становится проще + ваш валидатор может вызывать другие компоненты Spring.
Способ 3 : Почему бы не использовать комбинацию обоих методов? Проверьте простые вещи, такие как атрибут" имя", с помощью аннотации (это быстро сделать, кратким и более читаемым). Сохраняйте тяжелые проверки для валидаторов (когда потребуется несколько часов для кодирования пользовательских сложных аннотаций проверки или просто когда невозможно использовать аннотации). Я сделал это на прежнем проекте, он работал как шарм, быстро и легко.
предупреждение : вы не должны ошибиться проверка обращения на обработка исключений. читать этот пост чтобы знать, когда использовать их.
ссылки :
существует два способа проверки пользовательского ввода: аннотации и наследование класса валидатора Spring. Для простых случаев аннотации хороши. Если вам нужны сложные проверки (например, кросс-полевая проверка, например. поле" проверить адрес электронной почты"), или если ваша модель проверена в нескольких местах в вашем приложении с разными правилами, или если у вас нет возможности изменить объект модели, поместив на него аннотации, то валидатор 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
Примечание. Вы также можете использовать Hibernateorg.hibernate.validator.constraints
, но это не похоже, что вы используете Hibernate.кроме того, если вы реализуете валидатор Spring, вы создадите класс как следует:
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.
надеюсь, что это поможет.
Я хотел бы выразить хороший ответ Джерома Dalbert. Я нашел очень легко написать свои собственные валидаторы аннотаций в JSR-303 способ. Вы не ограничены проверкой "одного поля". Вы можете создать свою собственную аннотацию на уровне типа и иметь сложную проверку (см. примеры ниже). Я предпочитаю этот способ, потому что мне не нужно смешивать разные типы проверки (Spring и JSR-303), как это делает Джером. Также эти валидаторы "Spring aware", поэтому вы можете использовать @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; }
если у вас есть такая же логика обработки ошибок для разных обработчиков методов, то вы получите множество обработчиков со следующим шаблоном кода:
if (validation.hasErrors()) { // do error handling } else { // do the actual business logic }
Предположим, вы создаете RESTful services и хотите вернуть
400 Bad Request
вместе с сообщениями об ошибках для каждого случая ошибки проверки. Затем часть обработки ошибок будет одинаковой для каждой конечной точки REST, которая требует проверки. Повторение той же самой логики в каждом отдельном обработчике не так сухойишь!один из способов решить эту проблему-отказаться от немедленного
BindingResult
после каждого Чтобы Быть Проверены бобовые. Теперь, ваш обработчик будет выглядеть так:@RequestMapping(...) public Something doStuff(@Valid Somebean bean) { // do the actual business logic // Just the else part! }
таким образом, если связанный Боб был недопустим, a
MethodArgumentNotValidException
будет брошен к весне. Вы можете определитьControllerAdvice
который обрабатывает это исключение с той же логикой обработки ошибок:@ControllerAdvice public class ErrorHandlingControllerAdvice { @ExceptionHandler(MethodArgumentNotValidException.class) public SomeErrorBean handleValidationError(MethodArgumentNotValidException ex) { // do error handling // Just the if part! } }
вы все еще можете изучить базовый
BindingResult
используяgetBindingResult
методMethodArgumentNotValidException
.
найти полный пример проверки 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); } }
поместите этот компонент в свой класс конфигурации.
@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 и вы можете получить оттуда.