Search code examples
springspring-mvcspring-aopspring-java-configspring-aspects

Spring MVC and AOP: @Pointcuts for @Controllers only works in Testing and not for Production


I am working with Spring Framework 4.3.3 in a Web Environment:

I have two contexts:

  • RootApplicationContext
  • ServletApplicationContext

I know the ServletApplicationContext contains all the beans about the web side, for example @Controller. Furthermore ServletApplicationContext is able to access all the context or beans from the RootApplicationContext for example @Service, @Repository etc. Until here I am fine.

Note it applies for @Configuration classes too. (Infrastructure)

Therefore with the previous introduction we can think in the following way:

  • ServletApplicationContext --> RootApplicationContext

Something important to take in consideration is that the inverse is not possible.

Therefore

  • RootApplicationContext --> ServletApplicationContext

is not possible. It has sense and is Ok. Server side should not access the Web side

About AspectJ. I have the following:

@Configuration
@EnableAspectJAutoProxy
public class AopConfig {

}

Here one important point:

  • This AopConfig is scanned by RootApplicationContext
    • I trust that ServletApplicationContext can refer that @Configuration through the access of RootApplicationContext

Ok, when I run my @Test methods.

When I execute a Test class from the server side I use

  • @ContextConfiguration(classes={RootApplicationContext.class} )
    • Only RootApplicationContext

And AOP works fine. I can confirm through AOP + logging the following process:

  • @Service -> @Repository

When I execute a Test class from the Web side I use:

  • @ContextConfiguration(classes={RootApplicationContext.class, ServletApplicationContext.class})
    • RootApplicationContext and ServletApplicationContext

And AOP works fine. I can confirm through AOP + logging the following process:

  • @Controller -> @Service -> @Repository

Now for production I have:

public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{RootApplicationContext.class};
    }

   @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{ServletApplicationContext.class};
    }

But when I export the project for a .war file and through an URL/URI a Controller is executed the expected behaviour or process works fine. But about AOP through AOP + logging the following process happens:

  • @Service -> @Repository

@Controller does not appear for the output. The expected flow process should be:

  • @Controller -> @Service -> @Repository

So why works in Testing and not in production?

I already did a research and I have found these two posts:

Practically they say that the @Configuration class with @EnableAspectJAutoProxy should be scanned through ServletApplicationContext and not through RootApplicationContext

Even when it is true (according with new experiments), consider that Server side should be tested without a Web Environment.

For other @Beans about infrastructure through @Configuration the relation already explained about ServletApplicationContext --> RootApplicationContext works how is expected. Just with AOP has this situation.

Question 01: So why this behaviour?

Question 02: How keep the AopConfig scanned by RootApplicationContext and get the expected behaviour for production?

Note if AopConfig is scanned by ServletApplicationContext. The following about testing is valid and mandatory for the server side @ContextConfiguration(classes={RootApplicationContext.class, AopConfig.class} ). See the addition of AopConfig.class but I think AopConfig should be scanned by RootApplicationContext.


Solution

  • The answer is that @ContextConfiguration(classes={RootApplicationContext.class, ServletApplicationContext.class}) in test environment and context inheritance in production is not the same things. In test environment you include RootApplicationContext and ServletApplicationContext as parts of your test application context. In production inheritance is used instead of a simple inclusion, as you described in your question.

    Seems that BeanFactoryPostProcessor (which is @EnableAspectJAutoProxy in your case) from parent context not applied to child contexts. To make it work in production you must explicitly define @EnableAspectJAutoProxy in child context too.

    In this case Spring context definition should be about as code below:

    @Configuration
    @Import(AopConfig.class)
    public class RootApplicationContext {
        ...
    }
    

    @Configuration
    @Import(AopConfig.class)
    public class ServletApplicationContext {
        ...
    }
    

    Or

    @Configuration
    @ComponentScan(basePackageClasses={AopConfig.Class, ...})
    public class RootApplicationContext {
        ...
    }
    

    @Configuration
    @ComponentScan(basePackageClasses={AopConfig.Class, ...})
    public class ServletApplicationContext {
        ...
    }
    

    Related Task