Довольно сложно сочетать проверку Spring и ограничения JSR-303. И нет «готового к использованию» способа. Основное неудобство заключается в том, что проверка Spring использует BindingResult
, а JSR-303 использует ConstraintValidatorContext
в результате проверки.
Вы можете попробовать создать свой собственный механизм проверки, используя Spring AOP. Рассмотрим, что нам для этого нужно сделать. Прежде всего, объявите АОП-зависимости (если вы еще этого не сделали):
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>
Я использую Spring версии 4.2.4.RELEASE
, но вы можете использовать свою собственную. AspectJ необходим для использования аннотации аспекта. Следующим шагом нам нужно создать простой реестр валидатора:
public class CustomValidatorRegistry {
private List<Validator> validatorList = new ArrayList<>();
public void addValidator(Validator validator){
validatorList.add(validator);
}
public List<Validator> getValidatorsForObject(Object o) {
List<Validator> result = new ArrayList<>();
for(Validator validator : validatorList){
if(validator.supports(o.getClass())){
result.add(validator);
}
}
return result;
}
}
Как видите, это очень простой класс, который позволяет нам найти валидатор для объекта. Теперь давайте создадим аннотацию, которая будет помечать методы, которые необходимо проверить:
package com.mydomain.validation;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomValidation {
}
Поскольку стандартный класс BindingException
не является RuntimeException
, мы не можем использовать его в переопределенных методах. Это означает, что нам нужно определить собственное исключение:
public class CustomValidatorException extends RuntimeException {
private BindingResult bindingResult;
public CustomValidatorException(BindingResult bindingResult){
this.bindingResult = bindingResult;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
Теперь мы готовы создать аспект, который выполнит большую часть работы. Аспект будет выполняться перед методами, помеченными аннотацией CustomValidation
:
@Aspect
@Component
public class CustomValidatingAspect {
@Autowired
private CustomValidatorRegistry registry; //aspect will use our validator registry
@Before(value = "execution(public * *(..)) && annotation(com.mydomain.validation.CustomValidation)")
public void doBefore(JoinPoint point){
Annotation[][] paramAnnotations =
((MethodSignature)point.getSignature()).getMethod().getParameterAnnotations();
for(int i=0; i<paramAnnotations.length; i++){
for(Annotation annotation : paramAnnotations[i]){
//checking for standard org.springframework.validation.annotation.Validated
if(annotation.annotationType() == Validated.class){
Object arg = point.getArgs()[i];
if(arg==null) continue;
validate(arg);
}
}
}
}
private void validate(Object arg) {
List<Validator> validatorList = registry.getValidatorsForObject(arg);
for(Validator validator : validatorList){
BindingResult errors = new BeanPropertyBindingResult(arg, arg.getClass().getSimpleName());
validator.validate(arg, errors);
if(errors.hasErrors()){
throw new CustomValidatorException(errors);
}
}
}
}
execution(public * *(..)) && @annotation(com.springapp.mvc.validators.CustomValidation)
означает, что этот аспект будет применяться к любым публичным методам bean-компонентов, которые помечены аннотацией @CustomValidation
. Также обратите внимание, что для пометки проверенных параметров мы используем стандартную аннотацию org.springframework.validation.annotation.Validated
. Но, конечно, мы могли бы сделать наш обычай. Я думаю, что другой код аспекта очень прост и не нуждается в комментариях. Дальнейший код примера валидатора:
public class PersonValidator implements Validator {
@Override
public boolean supports(Class<?> aClass) {
return aClass==Person.class;
}
@Override
public void validate(Object o, Errors errors) {
Person person = (Person)o;
if(person.getAge()<=0){
errors.rejectValue("age", "Age is too small");
}
}
}
Теперь мы настроили конфигурацию и все готово к использованию:
@Configuration
@ComponentScan(basePackages = "com.mydomain")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig{
.....
@Bean
public CustomValidatorRegistry validatorRegistry(){
CustomValidatorRegistry registry = new CustomValidatorRegistry();
registry.addValidator(new PersonValidator());
return registry;
}
}
Обратите внимание, что proxyTargetClass
равно true
, потому что мы будем использовать прокси класса cglib
.
Пример целевого метода в классе обслуживания:
@Service
public class PersonService{
@CustomValidation
public void savePerson(@Validated Person person){
....
}
}
Из-за @CustomValidation
будет применен аспект аннотации, а из-за @Validated
аннотация person
будет проверена. И пример использования сервиса в контроллере (или любом другом классе):
@Controller
public class PersonConroller{
@Autowired
private PersonService service;
public String savePerson(@ModelAttribute Person person, ModelMap model){
try{
service.savePerson(person);
}catch(CustomValidatorException e){
model.addAttribute("errors", e.getBindingResult());
return "viewname";
}
return "viewname";
}
}
Имейте в виду, что если вы будете вызывать @CustomValidation
из методов класса PersonService
, проверка не сработает. Потому что он будет вызывать методы исходного класса, а не прокси. Это означает, что вы можете вызывать эти методы только извне класса (из других классов), если вы хотите, чтобы валидация работала (например, @Transactional works same way
).
Извините за длинный пост. Мой ответ не о «простом декларативном способе», и, возможно, он вам не понадобится. Но мне было любопытно решить эту проблему.
person
Ken Bekov
schedule
19.03.2016
Validator
работал с проверкой JSR-303? - person Ken Bekov   schedule 18.03.2016public void things(@Valid MyMessage msg)
вам не нужно аннотироватьMyMessage
. Он будет передан вConstraintValidator<>
как значение. Вы можете написать свою собственную аннотацию к сообщениям. Но это означает, что вы будете обрабатывать исключение проверки за пределами вашего класса обслуживания. - person Ken Bekov   schedule 18.03.2016