Why is #Validated required for validating Spring controller request parameters? - java

With the following validation setup in an annotated MVC controller:
#RestController
#RequestMapping("/users")
#Validated // <-- without this, the #Size annotation in setPassword() has no effect
public class UserController {
#PutMapping("/{id}/password")
public void setPassword(#PathVariable long id, #RequestBody #Size(min = 8) String password) {
/* ... */
}
#PutMapping("/{id}/other")
public void setOther(#PathVariable long id, #RequestBody #Valid MyFormObject form) {
/* ... */
}
}
#Validated on the controller is required for the method parameter since it's not a "complex" object. In comparison, the #Valid annotation on the setOther method works without the #Validated annotation.
Why is #Validated required? Why not enable it by default? Is there a cost to its use?
edit
Note that Difference between #Valid and #Validated in Spring is related (I read it before asking this), but it doesn't address the why in my question.

Validation of objects is done by Hibernate Validator using the annotations from Jakarta Bean Validation 2.0. Something needs to trigger hibernate validator to run.
SpringMVC calls the controller methods when it sees a parameter with #Valid it will pass that object to hibernate validator. Hibernate validator will
Examine the class of the object to figure out what validation rules have been put on the class fields
Execute the validation rules against the fields marked up with validation annotations.
So in this case
#PutMapping("/{id}/other")
public void setOther(#PathVariable long id, #RequestBody #Valid MyFormObject form) {
/* ... */
}
MyFormObject has annotations on it that the hibernate validator can find to validate the object.
In this case
#PutMapping("/{id}/password")
public void setPassword(#PathVariable long id, #RequestBody #Size(min = 8) String password) {
/* ... */
}
java.lang.String does not have any annotations defined on it for hibernate validator to discover.
#Valid comes from the Bean validation package javax.validation.Valid while #Validated comes from Spring org.springframework.validation.annotation.Validated
#Validated annotation activates the Spring Validation AOP interceptor and it will examine method parameters to see if they have any validation annotations on them, if they do then Spring will call hibernate validator with each specific annotation for example #Size(min = 8) String password means call hibernate size validator and pass the value of the parameter password in this case hibernate validator does not need to scan java.lang.String to see if it has validation annotations on it. #Validated works on any spring #Component you can use it on #Service classes for example.
There is extra overhead for using #Validated similar to using #Transactional so that is why you have to opt into it. In the case of javax.validation.Valid Spring MVC needs to check the annotations on the controller method parameters so when it sees #Valid it is easy for it to send that object the Hibernate Validator without needing to add an AOP interceptor.

Related

Spring boot - How to validate Multipartfile in rest application

I want to validate extension of Multipartfile object. I added #Valid and my custon annotation to parameter #ImageFileValid but it doesn't work.
#PutMapping("/{id}")
ProductDto updateProduct(#RequestPart #Valid ProductDto product, #PathVariable Long id,#RequestPart #Valid #ImageFileValid MultipartFile image) {
return productMapper.productToProductDto(productService.update(productMapper.productDtoToProduct(product),id));
}
Very short but clear reference from Spring-Boot, Validation:
The method validation feature supported by Bean Validation 1.1 is automatically enabled as long as a JSR-303 implementation (such as Hibernate validator) is on the classpath. This lets bean methods be annotated with javax.validation constraints on their parameters and/or on their return value. Target classes with such annotated methods need to be annotated with the #Validated annotation at the type level for their methods to be searched for inline constraint annotations.
So, please annotate (the containing) controller class with #Validated & report if any issues.
A sample repo at github.

#Valid bean validation

Consider the two frameworks shown below. Here I need to validate the bean
Controller
In controller I m using #Valid and does the java validation. Works fine
#RequestMapping("")
void testIt(#Valid #RequestBody User user){
}
Normal Spring application without controller
Is there any way to do validation here. Its not a controller and #Valid doesn't work here.
Anyways to use #Valid or any similar type of validation for normal function?
void testIt(#Valid User user){
}
You can enable method validation by declaring beans of type org.springframework.validation.beanvalidation.MethodValidationPostProcessor and org.springframework.validation.beanvalidation.LocalValidatorFactoryBean and annotating the class containing testIt() with the #Validated annotation.
#Validated
#Component
public class TestIt {
public void testIt(#Valid User user) {
...
}
}
ConstraintViolationException will be thrown if validation errors occur when calling testIt(). Also, make sure you have Hibernate Validator in your classpath.

Resteasy Bean Validation Not Being Invoked

Problem Background
I have a Resteasy service that uses Spring through Resteasy's SpringContextLoaderListener. This is built on Resteasy version 3.0-beta-6.
I would like to use bean validation on the incoming requests, but I can not get Resteasy to call the validator. It acts like there is no validation configured and simply passes the method the invalid input object.
Question
How do I enable bean validation in Resteasy?
What I've Tried
I have done the following:
Annotated my resource class with #ValidateRequest
Annotated the method parameter with #Valid
Annotated the constraints on my input class.
Added a dependency on resteasy-hibernatevalidator-provider
Resource:
#Named
#Path("users")
#ValidateRequest
public class UserResource
{
/**
*
* #param user
*
* curl -x POST http://localhost:7016/api/1.0/users
*
*/
#POST
#Consumes({MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_JSON})
public Response createUser(#Valid User user)
{
//User creation logic here.
}
}
User POJO:
#JsonPropertyOrder({
"user_id",
"user_name",
"email"
})
public class User
{
#JsonProperty("user_id")
private Long userId;
#JsonProperty("user_name")
#NotNull(message = "Username must be provided")
private String username;
#Email(message = "Invalid email address.")
private String email;
//Getters and Setters Removed for Brevity
}
POM Entry for Validation:
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-hibernatevalidator-provider</artifactId>
<version>${resteasy.version}</version>
</dependency>
The resteasy-hibernatevalidator-provider dependency brings in the HibernateValidatorContextResolver and its associated HibernateValidatorAdapter.
Update (6/18/2013):
I reverted the Resteasy version in my pom to 2.3.5.Final and bean validation started working without any code changes.
Running with Resteasy '3.0.6.Final' and Spring '4.1.0.RELEASE'.
The 'resteasy-hibernatevalidator-provider' does not evaluate the #Valid annotated params. Using the 'resteasy-validator-provider-11' makes everything work and as a bonus is using Hiberbate validator '5.0.1.Final' instead of needing a Hibernate validator version 4 when using the 'resteasy-hibernatevalidator-provider'.
Have you done this:
Providing a ValidatorAdapter to RESTEasy
RESTEasy will try to obtain an implementation of ValidatorAdapter through a ContextResolver provider in the classpath. We can provide RESTEasy with an implementation like follow:
#Provider
public class MyValidatorContextResolver implements ContextResolver<ValidatorAdapter> {
#Override
public ValidatorAdapter getContext(Class<?> type) {
return new MyValidator();
}
}
Make sure you have the META-INF/services/javax.ws.rs.Providers defined in your war.
You can use the hibernate validator provided with resteasy-3.0.
Check chapter 48, of the resteasy documentation :
http://docs.jboss.org/resteasy/docs/3.0.0.Final/userguide/html_single/index.html#JBoss AS 6

One Transaction for Hibernate Validation and Spring controller

I am trying to implement the registration controller for a Rest API. I have read about where to place #Transactional quite a bit. (Not at DAO level but at the services maybe orchestrated). In my use case I want not only services but also a hibernate validation to use the same transaction.
This is the code of the controller:
#Autowired
private UserService userService;
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
#Transactional
public DefaultResponse register(#Valid RegisterIO registerIO, BindingResult errors) {
DefaultResponse result = new DefaultResponse();
if (errors.hasErrors()) {
result.addErrors(errors);
} else {
userService.register(registerIO);
}
return result;
}
I have written an custom contraint annotation, which validates an attribute of the parameter registerIO. Both, this validator and userService.register(registerIO); access the database (check if the email address is already in use).
Therefore I want both methods use the same Hibernate session and transaction.
This approach results in the following exception:
org.hibernate.HibernateException: No Session found for current thread
org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:97)
org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:941)
The problem is the #Transactional annotation. When I place this annotation at the methods witch call the database everything works find but two transactions are startet. I suspect that when i place it at the register Method the hibernate validation is performed before #Transactional starts the transaction for this method.
I developed the following functional workaround but I am not happy with it. This codes does not use the #Valid annotation but calls the validator by itself:
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
#Transactional
public DefaultResponse register( RegisterIO registerIO, BindingResult errors) {
DefaultResponse result = new DefaultResponse();
ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
Validator validator = vf.getValidator();
Set<ConstraintViolation<RegisterIO>> valResult = validator.validate(registerIO);
I try to summarise my question:
Using Spring MVC and Hibernate-Validation together with #Valid and #Transactional, how is it possible to encapsulate the whole request into one transaction?
Thank you :)
Your workaround could be improved by using a single Validator and injecting it intp the controller. Have you tried:
#Autowired
private Validator validator;
This way you skip the overhead of creating the validator on each request.
You should also be careful with race conditions. While you are checking the database whether a given email exists another request can create this record, so that you still get an exception at the time you insert the data.

Is spring Form validation different than JSR bean validation

I have seen that annotation like #NotNull etc are given in JSR Specifications.
Now as i am currently studying Spring MVC and building website in that
so i am confused are they same or different and have they any relation with spring MVC.
because for .eg if i use #NotEmpty then will spring knows that if i leave it empty then it displays in form with error message like we code in validator and its messages
This is my method , i am confused were to add #Valid
public String add(#ModelAttribute("personAttribute") Person person) {
logger.debug("Received request to add new person");
personService.add(person);
// This will resolve to /WEB-INF/jsp/addedpage.jsp
return "hibernate/addedpage";
}
Spring form validation is different, but it also support JSR-303 validation. You can validate a whole model attribute by annotating it with #Valid (as a method parameter)
public String add(#Valid #ModelAttribute("personAttribute") Person person) { .. }
You would need:
a JSR-303 provider on the classpath
<mvc:annotation-driven /> in the spring-mvc xml to enable validation

Categories

Resources