You can use the Spring AOP module. And i think that a clean way (managing response without break the RestController endpoints flow) can be the following.
First of all add the spring-boot-starter-aop dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Let's see the code.
This is our ExampleRequest
import lombok.Data;
@Data
public class ExampleRequest {
String name;
}
We assume that we want to validate our request -> if name == "anonymous" we want to return an HTTP status BAD_REQUEST.
This is our TestController
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
@Anonymous
@GetMapping("")
public ResponseEntity<String> test(ExampleRequest exampleRequest, BindingResult bindingResult) {
if (bindingResult.hasErrors()) return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
else return new ResponseEntity<>(HttpStatus.OK);
}
}
We pass as arguments the ExampleRequest
that is our request and BindingResult
that we will use later. Anyway if bindingResult has errors our validation logic is broken so we need to return the error we want!
The @Anonymous is our custom annotation that we will use to tell to our Advice(s) which methods, or better - which classes (an Aspect is an entire class "proxied"), we want to proxy
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Anonymous {
}
And our TestAspect
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
@Aspect
@Component
public class TestAspect {
@Before(value = "@annotation(Anonymous) && args(exampleRequest, bindingResult,..)", argNames = "joinPoint,exampleRequest,bindingResult")
public Object logExecutionTime(JoinPoint joinPoint, ExampleRequest exampleRequest, BindingResult bindingResult) throws Throwable {
if (exampleRequest.getName().equals("anonymous")) bindingResult.rejectValue("name", "incorrect");
return joinPoint;
}
}
where we are saying: BEFORE the execution of EVERY METHODS annotated with our custom ANONYMOUS annotation with these ARGUMENTS execute this logic!
Obviously you will have your customized validation logic so implement it.
As I was saying in comments above i think you could return errors directly from the proxied class but at the same time I think that this example is a cleaner way to work with AOP (using BindingResult the flow of endpoints is respected and this technique does't break other validations of your model classes. You can use tha javax validation for example mixing in the advices and all will work perfectly!), personally I love this Spring AOP module but don't abuse it!
For reference: https://docs.spring.io/spring/docs/2.5.x/reference/aop.html