springspring-bootaop

Why are field values null when calling final methods on a proxy instance?


I have my own Spring Boot application and want to create my annotation, which will check incoming data to null.

When I created the @Aspect annotated class, all other beans became null, what should I do?

@Aspect class code:

@Aspect
@Component
public class EnableRestCallLogAspect {
    @Around("@annotation(EnableRestCallLogs)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {

        long initTime = System.currentTimeMillis();
        Object proceed = joinPoint.proceed();
        long executionTime = System.currentTimeMillis() - initTime;
        System.out.println("============================================================================================================");
        System.out.println("Method Signature is : "+joinPoint.getSignature() );
        System.out.println("Method executed in : " + executionTime + "ms");
        System.out.println("Input Request: " + joinPoint.getArgs()[0]);
        System.out.println("Output Response : " + proceed);
        return proceed;
    }
}

Annotation EnableRestCallLogs is annotated only in methods and in PostMapping methods:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableRestCallLogs {

}

Solution

  • Preface

    Actually, the question is quite interesting, so I want to explain the situation with a demo. Before talking about this problem, please first read and try to understand my answer to "Why does self-invocation not work for Spring proxies (e.g. with AOP)?". The main take-away is the fact that Spring proxies use a delegation pattern.

    Next, let us talk about auto-wiring: Spring injects the values into the original instance, i.e. the proxy's corresponding field values remain null, because the proxy's sole purpose is to intercept methods, possibly

    Spring example

    An entity and its auto-wired dependency:

    package de.scrum_master.spring.q77134178;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyEntity {
      @Autowired
      private MyDependency myDependency;
    
      public void canOverridePublicNonFinal() {
        System.out.println("canOverridePublicNonFinal");
        System.out.println("  class      = " + this.getClass().getName());
        System.out.println("  dependency = " + myDependency);
        cannotOverridePrivate();
      }
    
      private void cannotOverridePrivate() {
        System.out.println("cannotOverridePrivate");
        System.out.println("  class      = " + this.getClass().getName());
        System.out.println("  dependency = " + myDependency);
      }
    
      public final void cannotOverrideFinal() {
        System.out.println("cannotOverrideFinal");
        System.out.println("  class      = " + this.getClass().getName());
        System.out.println("  dependency = " + myDependency);
      }
    }
    
    package de.scrum_master.spring.q77134178;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyDependency {}
    

    An aspect intercepting entity methods:

    Why do we need an aspect? Because

    package de.scrum_master.spring.q77134178;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class MyAspect {
      @Before("execution(* MyEntity.*(..))")
      public void myAdvice(JoinPoint joinPoint) {
        System.out.println(joinPoint);
      }
    }
    

    A driver application:

    package de.scrum_master.spring.q77134178;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @SpringBootApplication
    @Configuration
    @EnableAspectJAutoProxy
    public class DemoApplication {
      public static void main(String[] args) {
        try (ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args)) {
          MyEntity myEntity = context.getBean(MyEntity.class);
          myEntity.canOverridePublicNonFinal();
          myEntity.cannotOverrideFinal();
        }
      }
    }
    

    Console log:

    execution(void de.scrum_master.spring.q77134178.MyEntity.canOverridePublicNonFinal())
    canOverridePublicNonFinal
      class      = de.scrum_master.spring.q77134178.MyEntity
      dependency = de.scrum_master.spring.q77134178.MyDependency@6c24f61d
    cannotOverridePrivate
      class      = de.scrum_master.spring.q77134178.MyEntity
      dependency = de.scrum_master.spring.q77134178.MyDependency@6c24f61d
    cannotOverrideFinal
      class      = de.scrum_master.spring.q77134178.MyEntity$$EnhancerBySpringCGLIB$$e1109d0e
      dependency = null
    

    Please note:

    POJO example emulating Spring's dynamic proxies

    Here is a plain Java version (no Spring or dynamic proxies), emulating the behaviour in POJO fashion in order to hopefully make it even clearer what goes on behind the curtains in Spring AOP.

    Entity and injectable dependency:

    You see, the classes are identical to the Spring example. I just removed @Component and @Autowired and added a setter method to enable dependency injection.

    package de.scrum_master.app;
    
    public class MyEntity {
      private MyDependency myDependency;
    
      public void setMyDependency(MyDependency myDependency) {
        this.myDependency = myDependency;
      }
    
      public void canOverridePublicNonFinal() {
        System.out.println("canOverridePublicNonFinal");
        System.out.println("  class      = " + this.getClass().getName());
        System.out.println("  dependency = " + myDependency);
        cannotOverridePrivate();
      }
    
      private void cannotOverridePrivate() {
        System.out.println("cannotOverridePrivate");
        System.out.println("  class      = " + this.getClass().getName());
        System.out.println("  dependency = " + myDependency);
      }
    
      public final void cannotOverrideFinal() {
        System.out.println("cannotOverrideFinal");
        System.out.println("  class      = " + this.getClass().getName());
        System.out.println("  dependency = " + myDependency);
      }
    }
    
    package de.scrum_master.app;
    
    public class MyDependency {}
    

    Pseudo proxy class:

    Here we see how delegation works in Spring AOP.

    package de.scrum_master.app;
    
    public class MyEntityProxy extends MyEntity {
      private MyEntity delegate;
    
      public MyEntityProxy(MyEntity delegate) {
        this.delegate = delegate;
      }
    
      @Override
      public void canOverridePublicNonFinal() {
        System.out.println("MyEntityProxy.canOverridePublicNonFinal delegating to original method (emulating aspect or interceptor)");
        delegate.canOverridePublicNonFinal();
      }
    }
    

    Driver application:

    Here, we emulate dependency injection by simply wiring the dependency into the entity manually via setter call.

    package de.scrum_master.app;
    
    public class DemoApplication {
      public static void main(String[] args) {
        // Create entity
        MyEntity myEntity = new MyEntity();
        // Pseudo "auto-wire" a value into the entity
        myEntity.setMyDependency(new MyDependency());
        // Create pseudo "dynamic proxy" for entity
        MyEntityProxy myEntityProxy = new MyEntityProxy(myEntity);
    
        // Call proxy method overriding and delegating to original entity parent method
        myEntityProxy.canOverridePublicNonFinal();
        // Call parent method (final -> cannot be overridden and therefore not intercepted by the proxy)
        myEntityProxy.cannotOverrideFinal();
      }
    }
    

    Console log:

    Unsurprisingly, the console log looks just like for the Spring AOP example, only the proxy class has a less cryptic name, because we manually created a pseudo proxy instead of relying on a CGLIB proxy.

    MyEntityProxy.canOverridePublicNonFinal delegating to original method (emulating aspect or interceptor)
    canOverridePublicNonFinal
      class      = de.scrum_master.app.MyEntity
      dependency = de.scrum_master.app.MyDependency@4554617c
    cannotOverridePrivate
      class      = de.scrum_master.app.MyEntity
      dependency = de.scrum_master.app.MyDependency@4554617c
    cannotOverrideFinal
      class      = de.scrum_master.app.MyEntityProxy
      dependency = null