Rinat Mukhamedgaliev
Rinat Mukhamedgaliev

Reputation: 5731

Spring 4 and Hibernate 5 method argument validation

How validate incoming arguments with Hibernate ?

In XML

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
    <property name="validator" ref="validator"/>
</bean>

In Java

 @Validated
 public class UserService

 @Override
 @NotNull
 public User registerUser(@NotEmpty String name, String username, String password, boolean google, boolean facebook)

This approach not work i call method with error params and validation not work.

Upvotes: 4

Views: 12082

Answers (4)

KHanusova
KHanusova

Reputation: 197

For current version you don't have to add all beans that "asinkxcoswt" wrote in https://stackoverflow.com/a/23384229/6028739.

To your pom.xml add this:

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-validator</artifactId>
   <version>5.3.4.Final</version>
</dependency>
<dependency>
   <groupId>org.glassfish.web</groupId>
   <artifactId>javax.el</artifactId>
   <version>2.2.4</version>
</dependency>

Add this line to your application context:

<bean id="validationFactory" class="javax.validation.Validation"  
      factory-method="buildDefaultValidatorFactory" />  

All entities annotated with @Validated are now validated and throw javax.validation.ConstraintViolationException with list of constraint violations with details e.g.

javax.validation.ConstraintViolationException: Validation failed for classes [com.example.Class] during update time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must be greater than or equal to 0', propertyPath=amount, rootBeanClass=class com.example.Class, messageTemplate='{javax.validation.constraints.Min.message}'}
]

Upvotes: 0

asinkxcoswt
asinkxcoswt

Reputation: 2554

Question Clarification

When using Hibernate Validator (HV) 4.2 with Spring-MVC 4, e.g. with dependencies:

dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.0.1'
    compile 'org.springframework:spring-webmvc:4.0.3.RELEASE'
    compile 'org.hibernate:hibernate-validator:4.2.0.Final'
    runtime 'javax.servlet:jstl:1.1.2'
}

one can tell Spring to automatically use HV to validate method calls of classes annotated with @Validated (as in asker's example) by declaring following beans in the configuration file,

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

When the target's method get called and has somethings that violates its constraints, the MethodConstraintViolationException is thrown. For example, something like this will be raised if one happeded to call UserService#registerUser(...), passing an empty String to where annotated with @NotEmpty

... The following constraint violations occurred: [MethodConstraintViolationImpl
[method=public abstract void foo.UserService.registerUser(java.lang.String),
parameterIndex=0, parameterName=arg0, kind=PARAMETER, message=may not be empty, 
messageTemplate={org.hibernate.validator.constraints.NotEmpty.message}, ...

But the problem is if you change the dependencies from above to

dependecies {
    providedCompile 'javax.servlet:javax.servlet-api:3.0.1'
    compile 'org.springframework:spring-webmvc:4.0.3.RELEASE'
    compile 'org.hibernate:hibernate-validator:5.1.0.Final' //**note the change here**
    runtime 'javax.servlet:jstl:1.1.2'
}

then you will get an error:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name
'org.springframework.validation.beanvalidation.MethodValidationPostProcessor#0'
defined in class path resource [spring/config/web/main-config.xml]: Invocation of init
method failed; nested exception is javax.validation.ValidationException: Unable to
instantiate Configuration.

Solution

Add 2 additional dependecies to the classpath as follows,

dependecies {
    providedCompile 'javax.servlet:javax.servlet-api:3.0.1'
    providedCompile 'javax.el:el-api:2.2'
    providedCompile 'org.glassfish.web:el-impl:2.2'
    compile 'org.springframework:spring-webmvc:4.0.3.RELEASE'
    compile 'org.hibernate:hibernate-validator:5.1.0.Final' //**note the change here**
    runtime 'javax.servlet:jstl:1.1.2'
}

And the validation will comeback to life. But this time, instead of MethodConstraintViolationException, it raises ConstranitViolationException with no details of what has been violated.

I am not sure how Spring manage this internally and wheter you can unfold the violation details out of this Exception. HV 5 no longer supports MethodConstraintViolationException and its relatives, e.g. MethodValidator, MethodConstraintViolation. This is something to do with Bean Validation 1.1 specification, which has some changes in method-level validation from BV 1.0. I suggest creating the validation service yourself using aspect and with ExecutableValidator (instead of the old MethodValidator) as explained in HV's Reference guide.

Note that you if you try to obtain the validator like this,

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
ExecutableValidator executableValidator = factory.getValidator().forExecutables();

you may get ValidationException: Unable to instantiate configuration. I can solve this problem by do this

ValidatorFactory factory = Validation.byProvider(HibernateValidator.class).configure().buildValidatorFactory();
ExecutableValidator executableValidator = factory.getValidator().forExecutables();

Upvotes: 6

Serkan Kaba
Serkan Kaba

Reputation: 1

Apparently Spring suggests not to use ExecutableValidator but MethodValidationInterceptor instead, see javadoc: http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.html

Upvotes: 0

Magnus Smith
Magnus Smith

Reputation: 807

I include the following in addition to spring and other project dependencies in the maven pom file.

<properties>
    <hibernate.version>4.3.1.Final</hibernate.version>
    <hibernate.validator.version>5.1.0.Final</hibernate.validator.version>
    <javax.el.version>3.0.0</javax.el.version>
    <asm.version>3.3.1</asm.version>
</properties>
<dependencies>
    <!-- Hibernate et al-->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>${hibernate.version}</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>${hibernate.version}</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>${hibernate.validator.version}</version>
    </dependency>
    <dependency>
        <groupId>javax.el</groupId>
        <artifactId>javax.el-api</artifactId>
        <version>${javax.el.version}</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.web</groupId>
        <artifactId>javax.el</artifactId>
        <version>${javax.el.version}</version>
    </dependency>
    <!-- asm allows hibernate validator to retrieve method parameter names when reporting method validation
        without this you just get arg0 instead of argument name -->
    <dependency>
        <groupId>asm</groupId>
        <artifactId>asm</artifactId>
        <version>${asm.version}</version>
    </dependency>
</dependencies>

In my java config for spring I set

@Bean
public MessageSource messageSource() {
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setBasename("validation");
    messageSource.setCacheSeconds(1);

    return messageSource;
}


@Bean
LocalValidatorFactoryBean getValidator() {
    LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
    validator.setValidationMessageSource(messageSource());
    validator.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer());
    return validator;

}

@Bean
@Autowired
MethodValidationPostProcessor getValidationPostProcessor(LocalValidatorFactoryBean validator) {
    MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
    processor.setValidator(validator);
    return processor;
}

LocalVariableTableParameterNameDiscoverer works with ASM to give proper parameter names in the ConstraintViolationException that will be thrown when validation fails.

Upvotes: 4

Related Questions