Search code examples
javaspringcglib

Spring CGLIB proxies intercept only public method calls


Spring documentation state that:

CGLIB proxies intercept only public method calls! Do not call non-public methods on such a proxy. They are not delegated to the actual scoped target object.

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-scopes-other

But after observation, I think either my experiment (code below) is not good or this behaviour has changed over time.

I Observed that only final or private methods are being bypassed.

Here's The experiment: (spring version: 5.1.3)

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

@Autowired
private StudentService studentService;

public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
}

@Bean
StudentService studentService() {
    ProxyFactory proxyFactory = new ProxyFactory(new StudentService());
    proxyFactory.setProxyTargetClass(true);
    proxyFactory.addAdvice(new MethodInterceptor() {
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("method " + invocation.getMethod() + " is called on " +
                    invocation.getThis() + " with args " + invocation.getArguments());
            Object ret = invocation.proceed();
            System.out.println("method " + invocation.getMethod() + " returns " + ret);
            return ret;
        }
    });
    return (StudentService) proxyFactory.getProxy();
}

@Override
public void run(String... args) throws Exception {
    studentService.doIt();
}

class StudentService {

    void doIt() {
        System.out.println("doIt");
    }
}

Output:

method void com.example.demo.DemoApplication$StudentService.doIt() is called on com.example.demo.DemoApplication$StudentService@127a7a2e with args [Ljava.lang.Object;@14008db3
doIt
method void com.example.demo.DemoApplication$StudentService.doIt() returns null

Also - by experiment - CGLIB the library (without spring, using the Enhancer class) also allows proxying package-level methods.

UPDATE

I've another Observation (that is opposite to the above). In a typical jdbc application:

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

    @Autowired
    private ExampleService exampleService;
    @Autowired
    private XExampleService xExampleService;
    @Autowired
    private XXExampleService xxExampleService;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        System.out.println(exampleService.getClass());
        System.out.println(xExampleService.getClass());
        System.out.println(xxExampleService.getClass());
    }


    @Service
    class ExampleService {

        void tranx() {

        }
    }

    @Service
    class XExampleService {

        @org.springframework.transaction.annotation.Transactional
        void tranx() {

        }
    }

    @Service
    class XXExampleService {

        @Transactional
        public void tranx() {

        }
    }
}

Output:

class com.example.demo.DemoApplication$ExampleService
class com.example.demo.DemoApplication$XExampleService
class com.example.demo.DemoApplication$XXExampleService$$EnhancerBySpringCGLIB$$2b1603e8

Which means, In case of spring creating the proxy in behave of me - as in the TransactionInterceptor - the CGLIB proxy is only created for the public method.

UPDATE2

I think I found where this behaviour of accept public method only happens. It happens at AnnotationTransactionAttributeSource which is used by the PointCut (TransactionAttributeSourcePointcut).

From code:

/**
 * Create a custom AnnotationTransactionAttributeSource, supporting
 * public methods that carry the {@code Transactional} annotation
 * or the EJB3 {@link javax.ejb.TransactionAttribute} annotation.
 * @param publicMethodsOnly whether to support public methods that carry
 * the {@code Transactional} annotation only (typically for use
 * with proxy-based AOP), or protected/private methods as well
 * (typically used with AspectJ class weaving)
 */
public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {

Solution

  • Possible Answer is Since Java-Based proxies cannot work with package-private methods (as Java implementation classes cannot assign weaker access privileges to interface-implemented methods).

    So this might stop spring team from operating on package-private methods even if, CGLib can do proxy them.