We've got a couple of #RequestMapping annotated controllers methods that do stuff. They each have more than 1 argument that we would like to be bound. For example:
#RequestMapping(value = "/a/b", method = RequestMethod.GET)
#ResponseBody
public Response getAB(ARequest aRequest, BRequest bRequest) throws ServiceException {
...
}
This obviously works fine. However, one quirk in our requirements is that we get request parameters that contain underscores. For example; on the endpoint above we'd get a request that looks like: http://x:8000/a/b?req_param=1. This causes default Spring binding to fail, because in our objects we'd like to have bound we define req_param camel-cased (reqParam) rather than use underscores in our Java code.
To solve this we have implemented our own annotation (#Camelize) and our own custom WebArgumentResolver, which we've registered in the Spring context. We then annotate our controller method arguments with #Camelize, which causes our custom WebArgumentResolver to step in and correct the binding. This also works fine.
Now the issues arise, more specifically it would appear that when we want to also validate our arguments this fails. For example, continuing with the example above:
#RequestMapping(value = "/a/b", method = RequestMethod.GET)
#ResponseBody
public Response getAB(#Camelize #Valid ARequest aRequest, Errors aRequestErrors, BRequest bRequest) throws ServiceException {
...
}
This fails with the exception: java.lang.IllegalStateException: Errors/BindingResult argument declared without preceding model attribute. Check your handler method signature!.
It would appear that we cannot use our custom WebArgumentResolver and validate using #Valid at the same time. We have narrowed it down to the custom WebArgumentResolver, in the sense that as soon as this doesn't return UNRESOLVED, Spring MVC will just not bother with validation and throw the exception above.
Can anyone confirm that this is the expected behaviour?
Is there a work-around for this? Currently we're thinking about removing the custom WebArgumentResolver and replacing it with a filter, but this seems very intrusive for what we need.
Thanks!
Edit: we're using Spring 3.0.5.
Related
a question to validation of put calls to a REST endpoint using spring boot and javax.validation (not the spring validation).
You have the following method in the resource:
#PutMapping(...)
public Response getResult(#RequestBody #Valid myBody, #PathVariable #MyIdValidation long id) {
}
When I call the method, myBody gets validated and I get a MethodArgumentNotValidException in my exception handler. But parameter id gets not validated!
Only if myBody is valid, id gets validated as well.
The only solution I found is to not use #Valid, and implement the validation of the body myself.
Are there better solutions?
TIA
Kibu
I don't think its doable by Spring MVC framework itself because framework handles #RequestBody and others like #RequestParam or #PathVariable differently by using different components. Also, both pieces need to be disconnected because you might not like to validate all arguments of a method.
#RequestBody is handled by org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor and validations are called from there & for params or path variable validations are done by org.springframework.validation.beanvalidation.MethodValidationInterceptor.
So in my opinion, method arguments of a controller method are handled one by one by framework & are disconnected in logic so these validations can't be clubbed together.
I've put some effort into this issue and started a project on github
It's possible to validate in one stop, but you need a different approach. Check out the project and test it.
Kibu
I recently stumbled upon some code that I had not seen in this form before. Maybe someone here can help me understand better what's going on.
Namely, I found a method annotated both with #RequestMapping and #ExceptionHandler. I thought that the former were for handling requests, while the latter were for handling exceptions, so I would have thought one normally uses either of both annotations, but not both at the same time.
I found the code snippet here: https://github.com/shopizer-ecommerce/shopizer/blob/2.5.0/sm-shop/src/main/java/com/salesmanager/shop/store/api/exception/RestErrorHandler.java#L24
The code snippet is:
#RequestMapping(produces = "application/json")
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public #ResponseBody ErrorEntity handleServiceException(Exception exception) {
log.error(exception.getMessage(), exception);
ErrorEntity errorEntity = createErrorEntity(null, exception.getMessage(),
exception.getLocalizedMessage());
return errorEntity;
}
I have two questions:
According to the Spring documentation on #RequestMapping, un-annotated method parameters (that are not of some special type) of a #RequestMapping method are implicitly annotated with #ModelAttribute (see "Any other argument" at the end of the table under the above link). So in the above code snippet, is the Exception parameter implicitly annotated with #ModelAttribute as well? And if yes, does that make sense?
Can it generally make sense to annotate a method with both #RequestMapping and #ExceptionHandler (e.g., to handle both requests and exceptions), or would that be bad form?
good question.
I would say try this. on a controller, take two methods. on one method use just RequestMethod and write a code by accepting a model attribute from page.
On this method, create a scenario for a NullPointerException.
On method 2, annotate both RequestMapping and ExceptionHandler. And you can see whether you are getting the request, response with ModelAttributes from method one to method 2.
if yes, then this would help us evaluate the exception and handle invalid scenarios where we would need the model attribute values.
Also as per the explanation that you have pasted above, ModelAttribute is implicit for RequestMapping, not for all annotations on a controller method.
Please let us know.
I'm trying to make sense of how validation works in Spring. So far I've learned that there are two ways to perform validation of user input data:
JSR-303 validation based on javax.validation.constraints annotations. This validation is best suitable for simple object fields validation. But you can also implement your custom type level annotations to perform more complicated validation based on checking values of multiple fields.
Spring Validation based on org.springframework.validation.Validator interface. Seems to be better suited for more complicated validation.
If I want to use both these approaches, what is the right way to register my custom validator in controller?
This is what I'm trying to do.
My custom validator.
public class PasswordPairValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return PasswordPair.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
PasswordPair password = (PasswordPair) target;
if (!password.getPassword().equals(password.getRepeatPassword())) {
errors.reject("passwordField", "passwords don't match");
}
}
}
My controller.
#RestController
#RequestMapping("/api/v1/users")
public class UserController {
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new PasswordPairValidator());
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<UserInfo> createUser(
#RequestBody #Valid UserInfo userInfo) {
userInfo.setId(123);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}").buildAndExpand(userInfo.getId()).toUri();
return ResponseEntity.created(location).body(userInfo);
}
#RequestMapping(value = "/change_password", method = RequestMethod.POST)
public ResponseEntity<UserInfo> changePassword(
#RequestBody #Valid PasswordPair password) {
UserInfo user = new UserInfo("test#gmail.com", "testuser");
user.setId(123);
return ResponseEntity.ok().body(user);
}
}
When I call createUser endpoint the code fails with the following error:
ERROR c.e.testapp.controller.GlobalExceptionHandler - Internal Server Error
java.lang.IllegalStateException: Invalid target for Validator [com.example.testapp.validation.PasswordPairValidator#49acd001]: com.example.testapp.domain.UserInfo#cae4750
The problem apparently is that Spring tries to apply PasswordPairValidator to UserInfo object, which was not my intention.
Why Spring doesn't use validator's supports() method to check to which objects validator can be applied?
In a different stackoverflow question I found out that I need to specify value for #InitBinder annotation to make it work and the value should be "passwordPair". But what is this value as it's not the class name ("PasswordPair") or method parameters value ("password")?
The second question is if I want to add several validators do I need to define multiple #InitBinder("value") methods or is there a less cumbersome way to do it?
And the final question, maybe it's better to use annotation based validation for everything, to validate separate fields and implement type level custom annotations with ConstraintValidator to perform more complicated validation? It's a bit confusing what are the pros and cons of these approaches.
You have to provided an argument to your #InitBinder annotation.
Please refer this question
Above question also answers your other question on registering multiple validators.
I believe the reason this happens is because the #InitBinder method will be called every time a request is being processed and thus for all the methods you have that correspond to HTTP verbs.
The only way I know that you can limit the times the method annotated with #InitBinder gets called is by using the value argument that the annotation takes. I admit that I am also a bit confused on what that value is or how it is interpreted.
Spring boot uses supports to check if a validator can be used every time initBinder() gets called but will throw an exception when it doesn't fit. This happens when initBinder() get called when a Request is processed. So even if you have multiple validators from which one is valid for the request body it will fail
If someone could help with how we can correctly apply validators in Spring boot I would also appreciate it. In C# I know that you can register beans as in middleware and based on the class you register the validator with, the correct validator gets called. (I am not well versed in C# but this is what I remember). Isn't something like this also possible in Java?
I'm developing a REST webservice with Spring MVC and I've implemented a custom annotation in order to annotate controller methods with it. This annotation may include a SpEL expression which I must evaluate considering controller method argument values. So, my idea is to implement a Spring MVC interceptor for this but the parameter HandlerMethod in the preHandle method is just a way to identify the method and does not provide access to controller method argument values. So, the only approach I can think of is to develop a Spring AOP aspect and intercept all the calls to annotated methods. By the way, I need access to the request, so if I go by the AOP way, all the annotated methods should include an argument with the request.
So, my question is: Is there any way to access the method argument values from thr Spring MVC interceptor or should I go the Spring AOP way?.
Thanks in advance.
You cannot use the controller method parameter in the preHandle method of an interceptor, because at the time of calling it, the parameters of the controller method have not been constructed (except for request and response).
So you will have to go the AOP way (do not forget to implement a method in your controllers ...) like explained in JavaBond answer. But thanks to spring framework, you can avoid that all the annotated methods should include an argument with the request. RequestContextHolder.getRequestAttributes() gives you a RequestAttributes object. If you know that your request is a HttpServletRequest, you can cast it to a ServletRequestAttributes and then access the native request via the getRequest() method :
RequestAttributes reqAttr = RequestContextHolder.getRequestAttributes();
HttpServletRequest req = ((ServletRequestAttributes) reqAttr).getRequest();
You should go the AOP way.
Write an Around advice against your custom annotation. The around advice should have a ProceedingJoinPoint argument. Using this you can get the annotated methods arguments values via proceedingJoinPoint.getArgs()
Sample advice shown below
#Around("#annotation(yourCustomAnnotation)")
public Object arooundAdvice(ProceedingJoinPoint joinpoint,
YourCustomAnnotation yourCustomAnnotation) throws Throwable {
Object args[] = joinpoint.getArgs();
// iterate over the args[] array to get the annotated method arguments
return joinpoint.proceed();
}
I have tried to find the answer to this, but I cannot seem to find what I am looking for. So I apologize if this question already exists.
PROBLEM:
I want to be able to access the request type of a request inside of a generic method within my Controller.
DESCRIPTION:
Using Spring ROO and Spring MVC, I have developed a small web service that will respond with certain tidbits from a database when queried. In one of my controller classes, I have some methods that handle some variety of GET, PUT, POST, etc., for the URIs that are mapped within the #RequestMapping parameter.
For example:
#RequestMapping(method = RequestMethod.Get, value = "/foo/bar")
#ResponseBody
public ResponseEntity<String> getFooBar() {
// stuff
}
If a request is made to the web service that it is not currently mapped, a 405 error is returned (which is correct), but I want to return more information along with a 405 response. Maybe respond with something like:
"I know you tried to execute a [some method], but this path only handles [list of proper methods]."
So I wrote a short method that only has the RequestMapping:
#RequestMapping(value = "/foo/bar")
I have found that the method with this mapping will catch all unhandled request types. But I am having trouble accessing the information of the request, specifically the type, from within the method.
QUESTION:
A. How can I access the request type from within the method? OR
B. Is this the right approach? What would be the right approach?
EDIT
ANSWER:
I added a HttpServletRequestobject to the method parameters. I was able to access the method type from that.
I tried using HttpRequest, but it didn't seem to like that much.
Thanks all!
You can add a method parameter of HttpServletRequest, but I think you'd be better off continuing to reply with 405. A client should then make an HTTP OPTIONS call (see How to handle HTTP OPTIONS with Spring MVC?) and you can return the list of allowed methods there.
A. you can access request if you mentioned it as parameter in controller method
public ... getFooBar(HttpRequest request) {
...
}
B. you do not need to add any other description as the 405 status is descriptive.
In answer to "A", just add "HttpRequest req" as an additional argument to your controller methods. Spring will automatically inject a reference to the request, and you can play with headers to your heart's content.
In answer to "B" - "What would be the right approach", how about this?
In order to return that 405, Spring has raised a MethodArgumentNotValidException. You can provide custom handling for this like so:
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public MyMethodArgumentMessage handleMathodArgumentNotValidException(
MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
MyMethodArgumentMessage myMessage =
new MyMethodArgumentMessage(result.getFieldErrors());
return myMessage;
}
You should take a look at the #ExceptionHandler annotation. This lets you add methods such as the following to your controller. You can define your own exceptions and appropriate custom handlers for them. I use it to return well-structured XML and JSON from REST services. Although for it to work, you need to throw specific exceptions from your controller methods.
A good walk-through of using this was provided by Petri Kainulkainen in his blog:
http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/