I am working with
I have two @Controllers
one for mvc
and other for rest
. Each one uses how a dependency a @Service
.
I have the following Rest method
package com.manuel.jordan.controller.persona;
@Controller
@RequestMapping(value="/personas")
public class PersonaRestController {
@PutMapping(value="/{id}", consumes={MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE})
public ResponseEntity<Void> updateOne(@PathVariable String id, @Validated @RequestBody Persona persona){
persona = personaService.updateOne(persona);
return ResponseEntity.noContent().build();
}
Observe for the second parameter I use @Validated
I have the following Pointcut:
@Pointcut(value=
"execution(* com.manuel.jordan.controller.persona.PersonaRestController.updateOne(..))")
public void updateOnePointcut(){}
And the following Around Advice:
@Around(value="PersonaRestControllerPointcut.updateOnePointcut()")
@Transactional(noRollbackFor={Some exceptions})
public Object aroundAdviceUpdateOne(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
logger.info("Beginning aroundAdviceUpdateOne - Class: {}, Method: {}",
proceedingJoinPoint.getTarget().getClass().getSimpleName(),
proceedingJoinPoint.getSignature().getName());
....
}
Through Spring MVC Test
When I send valid data:
Such as:
resultActions = mockMvc.perform(put(uri).contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.header("Accept-Language", locale.toString())
.content(JsonTransformerSupport.objectToJson(personaValid))).andDo(print());
or by
RequestEntity<Persona> requestEntity = RequestEntity.put(uri).contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.header("Accept-Language", locale.toString())
.body(personaValid);
Through Gradle
reports I can confirm that the @Around
advice works how is expected. It just watching the Beginning aroundAdviceUpdateOne - Class ....
text in the report and the rest of the advice body works how is expected.
The problem is when I send invalid data to be tested through @Validated
Using the two ways shown above but just changing personaValid
by personaInvalid
(null fields, breaking the min and max boundaries, etc) I did realize the @Around
advice always is ignored. It confirming that Beginning aroundAdviceUpdateOne - Class ....
never appears.
Note: even when the @Around
advice does not work, the validation process happens. I mean, there are none exception. The @Test
method pass.
Here some considerations.
@Before
advice.PersonaRestController.updateOne
method the @Around
(even @Before
) advice must do its work. It without matter the data is valid or not. That method is:
@PutMapping(value="/update/{id}",
consumes=MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces=MediaType.TEXT_HTML_VALUE)
public String updateOne(@PathVariable String id,
@Validated @ModelAttribute Persona persona,
BindingResult result,
RedirectAttributes redirectAttributes){
The same @Around
advice works, just with the following variation (note the ||
):
@Around(value="PersonaRestControllerPointcut.updateOnePointcut() || PersonaControllerPointcut.updateOnePointcut()")
@Transactional(noRollbackFor={some exceptions })
public Object aroundAdviceUpdateOne(JoinPoint proceedingJoinPoint) throws Throwable {
Update 01
Therefore 4 scenarios where the @Around
advice should work:
@Around
does not execute)Again: the @Validated
works how is expected for rest and non rest methods. The problem is about the @Around
advice, it does not work when the Rest
method is executed just when invalid data is sent. What is wrong? or missing?
Update 02
@Validated
is:
Variant of JSR-303's Valid, supporting the specification of validation groups. Designed for convenient use with Spring's JSR-303 support but not JSR-303 specific
It from its API
Seems is not an AOP
annotation. Is just a Spring annotation. My @Pointcut
does not care or check about annotations. It uses updateOne(..)
Update 03
Even adding
@Aspect
@Component
@Transactional
@Order(0)
class PersonaRestControllerAspect {
Does not work how is expected.
Update 04
I have
@EnableWebMvc
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
...
@Override
public Validator getValidator() {
return validatorConfig.localValidatorFactoryBean();
}
...
Where the Validator
comes from:
@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean(){
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.setValidationMessageSource(rrbms);
return localValidatorFactoryBean;
}
That rrbms
is a ReloadableResourceBundleMessageSource
instance that load many .properties
files, one of them is "classpath:/com/manuel/jordan/validation/validation"
.
That's the expected behavior. If you use @Validated
without an accompanying BindingResult
parameter, the method is never called. You get an exception if the data is invalid. After all that's the whole point of validation. And if the method is not called, then your advice will not be executed either.