Search code examples
javaspringaop

Why is the property value null when using Spring AOP but the same value works via getter?


I understand that a proxy bean is created when using AOP. I don't understand why I cannot access a property directly in my main method? Do we always have to use method to get a value when using AOP?

package power.sam;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import river.sam.Ganga;

import javax.inject.Inject;
import java.util.stream.Stream;

@Slf4j
@ComponentScan(basePackages = {"power.sam"})
@EnableAspectJAutoProxy
@Data
public class MyTest {

    Employee emp;

    @Autowired
    MyTest(Employee emp){
        this.emp = emp;
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyTest.class);
        context.registerShutdownHook();
        MyTest bean = (MyTest) context.getBean("myTest");
        log.info("Bean is {}", bean);
        log.info("Bean.Employee via getter is {}", bean.getEmp());
        log.info("Bean.Employee via property is {}", bean.emp);

    }
}

output

14:51:12.925 [main] INFO power.sam.MyTest - Bean is MyTest(emp=power.sam.Employee@3700ec9c)

14:51:12.955 [main] INFO power.sam.MyTest - Bean.Employee via get is power.sam.Employee@3700ec9c

14:51:12.955 [main] INFO power.sam.MyTest - Bean.Employee via property is null

update : This seems to only happen if I have a all method advice like

@Before("execution(public * *(..))")

Solution

  • To get clarity on what is happening in the background , add the following to the test code.

    System.out.println("Bean.Employee runtime class is "+ bean.getClass());
    

    Complete main method will be as follows

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyTest.class);
        context.registerShutdownHook();
        MyTest bean = (MyTest) context.getBean("myTest");
        System.out.println("Bean is "+ bean);
        System.out.println("Bean.Employee via getter is "+ bean.getEmp());
        System.out.println("Bean.Employee runtime class is "+ bean.getClass());
        System.out.println("Bean.Employee via property is "+ bean.emp);
    }
    

    Now when run with a Pointcut expression as : @Before("execution(public * *(..))")

    The console log is

    Bean is rg.so.qn64919052.MyTest@662f5666
    Bean.Employee via getter is rg.so.qn64919052.entity.Employee@75ed9710
    Bean.Employee runtime class is class rg.so.qn64919052.MyTest$$EnhancerBySpringCGLIB$$6e53892a
    Bean.Employee via property is null
    

    Note : the bean.getClass() returned the class of the Proxy object created.

    To provide clarity, Employee class is created within the package rg.so.qn64919052.entity and now when run with a Pointcut expression as : @Before("execution(public * rg.so.qn64919052.entity.Employee.*(..))")

    The console log is

    Bean is rg.so.qn64919052.MyTest@acdb094
    Bean.Employee via getter is rg.so.qn64919052.entity.Employee@674bd420
    Bean.Employee runtime class is class rg.so.qn64919052.MyTest
    Bean.Employee via property is rg.so.qn64919052.entity.Employee@674bd420
    

    Note : the bean.getClass() returned the original class.

    Explanation :

    Spring AOP is proxy based . Dynamic proxies only inherit methods and not any instance variables.

    The pointcut expression in the first run had a global scope , which will advice the public (protected and package-scoped , since CGLIB) method executions of all Spring beans. For this reason , a proxy for MyTest got created .

    The pointcut expression in the second run had a limited scope (rg.so.qn64919052.entity.Employee.*) and MyTest was not proxied.

    References :

    https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-introduction-proxies.