Toparvion opened SPR-17381 and commented

I have a Spring Boot application where certain beans get wrapped into 2 proxies: 1. The inner one is CGLIB proxy and is created by Spring Cloud Config by means of ScopedProxyFactoryBean 2. The outer one is JDK proxy and is created by DefaultAdvisorAutoProxyCreator provided by JavaMelody monitoring tool (in my case).

This particular proxy combination concerns dataSource bean which is of type com.zaxxer.hikari.HikariDataSource by default (for Spring Boot 2).

The problem arises when Spring Boot calls dataSource.unwrap() on the bean during startup:

15:19:01.203 ERROR o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.jdbc.DataSourceJmxConfiguration$Hikari': Invocation of init method failed; nested exception is java.lang.ClassCastException: com.sun.proxy.$Proxy76 cannot be cast to com.zaxxer.hikari.HikariDataSource
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:139) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:416) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1691) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:573) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:398) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:330) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1258) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
    at tech.toparvion.sample.joker18.schedule.HikariJavamelodyDemoApplication.main(HikariJavamelodyDemoApplication.java:10) [classes/:na]
Caused by: java.lang.ClassCastException: com.sun.proxy.$Proxy76 cannot be cast to com.zaxxer.hikari.HikariDataSource
    at com.zaxxer.hikari.HikariDataSource$$FastClassBySpringCGLIB$$eeb1ae86.invoke(<generated>) ~[HikariCP-2.7.9.jar:na]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:136) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:124) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at com.zaxxer.hikari.HikariDataSource$$EnhancerBySpringCGLIB$$21cdbad2.unwrap(<generated>) ~[HikariCP-2.7.9.jar:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_92]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_92]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_92]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_92]
    at net.bull.javamelody.JdbcWrapper$3.invoke(JdbcWrapper.java:781) ~[javamelody-core-1.72.0.jar:1.72.0]
    at net.bull.javamelody.JdbcWrapper$DelegatingInvocationHandler.invoke(JdbcWrapper.java:294) ~[javamelody-core-1.72.0.jar:1.72.0]
    at com.sun.proxy.$Proxy75.unwrap(Unknown Source) ~[na:na]
    at org.springframework.boot.autoconfigure.jdbc.DataSourceJmxConfiguration$Hikari.unwrapHikariDataSource(DataSourceJmxConfiguration.java:74) ~[spring-boot-autoconfigure-2.0.4.RELEASE.jar:2.0.4.RELEASE]
    at org.springframework.boot.autoconfigure.jdbc.DataSourceJmxConfiguration$Hikari.validateMBeans(DataSourceJmxConfiguration.java:65) ~[spring-boot-autoconfigure-2.0.4.RELEASE.jar:2.0.4.RELEASE]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_92]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_92]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_92]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_92]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:366) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:309) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:136) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    ... 18 common frames omitted

The debugging and the stacktrace above both show that outer proxy (JDK) correctly passes the call through. But when the inner proxy (CGLIB) gets applied it performs the following (probably incorrect) steps: 1. CglibAopProxy class gets the target object from the targetSource 2. SimpleBeanTargetSource's getTarget() method is invoked which fetches the target object from the Spring context once again. 3. It receives the original bean (which is wrapped into JDK proxy on the outmost layer) and then CglibAopProxy tries to proceed with the invocation by means of CglibMethodInvocation. 4. The last one fails a few steps later due to ClassCastException because CGLIB API is being applied to JDK proxied object.

It seems that the behavior of SimpleBeanTargetSource's is incorrect in this particular case because it re-fetches the target object thus spoiling the context from which it was called.

A sample project to reproduce the issue can be found here: https://github.com/Toparvion/javamelody-issue-472 (it was initially addressed to JavaMelody authors mistakenly).

Additional details can be provided if needed.


Affects: 5.0.5

Reference URL: https://github.com/Toparvion/javamelody-issue-472

1 votes, 3 watchers

Comment From: snicoll

I couldn't reproduce the problem upgrading the sample to Spring Boot 2.7, but I can see that the bean is eagerly post-processed which goes away if I remove Spring Cloud.

Chatting with @jhoeller, SimpleBeanTargetSource is doing the right thing but there might be something going on around the unwrap call. Unfortunately, I can't ask for feedback as the issue was created from Jira. If someone manages to reproduce the problem with a supported version, we can reopen of course.

Comment From: Toparvion

@snicoll , thank you for coming to this issue and for the new observations! I'll provide new details if I manage to reproduce the problem on the newer versions.