Search code examples
spring-mvcaopaspectjspring-aopspring-mvc-test

Spring MVC Test & AOP: Around advice is not executed how is expected for rest controller (just for invalid data)


I am working with

  • Spring Framework 4.3.3
  • AspectJ 1.8.9

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.

  • I have the same behaviour with @Before advice.
  • I am assuming before the execution of the PersonaRestController.updateOne method the @Around (even @Before) advice must do its work. It without matter the data is valid or not.
  • I have this scenario totally valid when I send data either valid or invalid to a non- rest controller

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:

  • non rest method with valid data (works)
  • non rest method with invalid data (works)
  • rest method with valid data (works)
  • rest method with invalid data (fail or ignored - @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".


Solution

  • 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.