Why Spring Boot throws different exceptions for Controller method argument validations? - spring-boot

While validating a primitive type or their equivalent (Integer, Boolean) using constraints validation annotations (#Min, #NotNull, ...) Spring Boot throws ConstraintViolationException. But when validating a parameter using the #Valid annotation then MethodArgumentNotValidException is thrown.
I have a class annotated with #ControllerAdvice to handle the exceptions from the Controllers.
The problem is that depending on the spring-boot-starter-parent version, the results are pretty much different.
While using the version 2.0.5.RELEASE I just needed to include a handler for the ConstraintViolationException class.
But there are some others versions that MethodArgumentNotValidException is thrown too.
It already was mentioned on a GitHub issue, but no useful answer...
I'll use the lukasniemeier-zalando's example here. For more detail click on the link above.
#Validated // needed to actually trigger validation
#RestController
class MyController {
#RequestMapping
Response serve(
#RequestParam #Min(2) Integer parameter, // throws ConstraintViolationException
#RequestBody #Valid BodyModel body // throws MethodArgumentNotValidException
) {
return new Response();
}
}
I would expect that both validations throws the same exception, no matter which of them, just to be consistent.
Apearently there's no reason to this to be like it is, at least it was what I understood from this other GitHub issue.
Then I just want a answer why Spring Boot throws 2 types of exception to represent the same problem (argument validation).
Note: as mentioned before, using the version 2.0.5.RELEASE of the spring-boot-starter-parent it doesn't happens.
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
But as reported by the last GitHub issue that I linked, the version 2.0.0.M4 has this behavior, and I also experienced it with the 2.2.0.M3 version.

They are handled by different validation mechanisms. The #Validated on the class is handled by the MethodValidationInterceptor which is a generic purpose validation mechanism for classes. Due to this it throws a ConstraintViolationException. The #Validated is used here simply because the #Valid annotation isn't allowed on types. Hence the only way to enable/trigger the MethodValidationInterceptor is by using the #Validation annotation.
The #Valid on the method argument in a controller is handled by the ModelAttributeMethodProcessor internally and leads to a web specific binding exception, the MethodArgumentNotValidException. The ModelAttributeMethodProcessor is called (indirectly) from the RequestMappingHandlerAdapter when preparing the method invocation. Instead of #Valid you could also use the #Validated annotation on the method argument. Spring MVC supports both (it actually supported #Validated before #Valid even existed!).
The solution/workaround is to create your own exception-handler which handles the ConstraintViolationException the same as a MethodArgumentNotValidException. Which is also suggested in the GitHub issue you link to.

Related

Spring AOP pointcut execution not working

I'm working on a Spring Boot project that uses Spring Cloud (io.awspring.cloud:spring-cloud-aws-dependencies:2.4.2) to produce and consume AWS SQS messages. I have several message producers and several message consumers, and all is working fine from that perspective.
I now have a cross cutting concern where I need to set a header on all messages being produced/sent; and to read that header on all messages being consumed (correlationId), and AOP seems like a good fit.
My aspect for handling (receiving) a message works fine:
#Before("execution(* org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessage(..))")
fun beforeHandleMessage(joinPoint: JoinPoint) {
The class and method that it is targeting is:
package org.springframework.messaging.handler.invocation;
...
public abstract class AbstractMethodMessageHandler<T>
implements MessageHandler, ApplicationContextAware, InitializingBean {
...
#Override
public void handleMessage(Message<?> message) throws MessagingException {
As mentioned, this works great.
However, I can't get my pointcut for sending a message working. This is my aspect:
#Before("execution(* org.springframework.messaging.support.AbstractMessageChannel.send(..))")
// #Before("execution(* io.awspring.cloud.messaging.core.QueueMessageChannel.send(..))")
fun beforeSendMessage(joinPoint: JoinPoint) {
And the class and method that I'm trying to target is this:
package org.springframework.messaging.support;
...
public abstract class AbstractMessageChannel implements MessageChannel, InterceptableChannel, BeanNameAware {
...
#Override
public final boolean send(Message<?> message) {
But it doesn't seem to work. I've also tried writing the pointcut to target the concrete implementation class (as commented out above), but that also does nothing.
I can't see what the difference is between my working pointcut for beforeHandleMessage and beforeSendMethod, other than the pointcut for beforeSendMethod is targeting a final method. Is that relevant?
Any pointers to get this working would be very much appreciated;
Thanks
Spring AOP uses dynamic proxies, i.e. it works by subclassing (CGLIB proxy) or by implementing interfaces (JDK proxies). In your case, you are targeting a class method rather than an interface method. The class method is final, which explains why it cannot work that way, because you cannot override a final method in a CGLIB proxy. What you should do instead is to
target the interface method MessageChannel.send(Message) and
make sure to use JDK proxies, i.e. not the "proxy target class" (CGLIB) mode. In Spring core, JDK proxy mode is the default, in Spring Boot CGLIB mode. So in Boot, you need to manually reconfigure the framework to permit for JDK proxies, which is only possible there via config file, not via annotations (they come too late in the bootstrapping process for Boot).
More specifically, you need this in src/main/resources/application.properties for Spring Boot:
# This works, now we can create JDK interface proxies. The seemingly equivalent alternative
# #EnableAspectJAutoProxy(proxyTargetClass = false)
# where 'false' is even the default, does *not* work in Spring Boot.
spring.aop.proxy-target-class=false
I found the answer from this other SO answer: Spring AOP ignores some methods of Hessian Service
I know that Spring AOP won't intercept local method calls. I.e. the proxy which is applied doesn't intercept the calls if the same object calls its own method, even if it matches the pointcut expression.
The problem was that the send method I was targeting was called by a number of other methods in the class.
Looking at the call stack I found a different method that was the first method called in the class. Changing the pointcut to target that method has worked.

Spring service method validation

I have a service class which is annotated with #Validated.
In this class I have a method with an argument which is annotated with #Valid.
If the method is called from another class instance with an argument that is not valid an exception is thrown.
As expected an error of type ConstraintViolationException is thrown.
If I call this method from another service method (internal call) no validation is performed and an error arises in the body of the method.
This is not what I want. Apparently calls made from within are not validated.
Investigating the problem I found out that the method was not invoked using a Spring proxy bean.
I fixed the problem by retrieving the proxy from the (#Autowired) application context and invoke the method using the proxy:
((T) context.getBean(this.getClass()).myMethod(validatedArgument)
This is an ugly solution.
How can I configure Spring so that method calls made from within are validated?
There is a tricky way to autowire the service to itself.
#Service
public class MyService {
#Autowired
private MyService copy;
private void call() {
//myMethod(validatedArgument);
copy.myMethod(validatedArgument);
}
}
The way Spring handles aspects is such that they are only invoked when one instance sends a message to a different instance. There are some ways to overcome this, but in your case, the fact that you want to do it is probably revealing a design flaw.

#Valid vs #Validated in Spring Boot Rest API #RequestBody [duplicate]

This question already has answers here:
Difference between #Valid and #Validated in Spring
(6 answers)
Closed 5 years ago.
I am coming from a .NET background in developing REST APIs. Now working on java REST project using spring boot.
First my validation at the controller #RequestBody just stop working. While trying to fix it, I saw different ways to implement. So what would be the correct way to annotate the #RequestBody?
#Validated #RequestBody
or
#Valid #RequestBody
There are generally no huge difference between them two, #Valid is from JSR-303 standand, #Validated is spring standard. according to spring document:
Spring provides a Validator interface that can be used for validation in all layers of an application. In Spring MVC you can configure it for use as a global Validator instance, to be used whenever an #Valid or #Validated controller method argument is encountered, and/or as a local Validator within a controller through an #InitBinder method. Global and local validator instances can be combined to provide composite validation.
Reference: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-config-validation
However there are differences, one of them is for example, if you need use group in your validation annotation, you need to use #Validated, it is not supported with #Valid.
#Valid is in the JSR-Specification and #Validated is from spring framework.
When your program should be compatible to EJB/JSR Standard use #Valid otherwise you can use both.

Logging missing REST methods using Aspects

I currently have a class called "AspectLogger" that logs Exceptions occurring in spring #Controllers. I am using native Spring AOP using AspectJ syntax.
#Before("(restController() || controller()) && publicMethod()")
public void logBefore(final JoinPoint joinPoint) throws IOException {
...
When a controller methods is requested from the frontend and there is no #RequestMapping that is able to handle it, I want to log this case too.
Using annotations, how can I achieve this? I guess I will need to create an aspect over one of the native Spring classes to handle this.
As far as I know, the most of Spring methods (which you need to intercept with AspectJ for this purpose) are protected or private, whereas AspectJ is for public methods only. Other work-around is using TRACE log-level on org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping class. Since you just want to log, I wanted let you know this option as well.

#Valid working without spring-mvc?

I'd like to use #Valid annotation to validate my beans in controller method. Unfortunatelly it does not work. I know that in order to make it work I'd have to include spring-mvc into my project and put there mvc:annotation-driven or #EnableMvc... .
But I do not use spring-mvc! I use Wicket framework. How to make #Valid working without incorporating spring-mvc?
Thanks!
#Valid is not specific to spring it is an implementation of JSR 303 bean validation. You can use any other reference implementation or write your own. e.g Apache and Hibernate Validator has reference implementation available. Take a look at this answer Is there an implementation of JSR-303 (bean validation) available?

Resources