@@ -46,46 +46,65 @@ private static boolean isRoutingDatasource(Object bean) {
4646 }
4747
4848 @ CanIgnoreReturnValue
49- @ Override
50- public Object postProcessAfterInitialization (Object bean , String beanName ) {
51- // Exclude scoped proxy beans to avoid double wrapping
52- if (bean instanceof DataSource
53- && !isRoutingDatasource (bean )
54- && !ScopedProxyUtils .isScopedTarget (beanName )) {
55- DataSource dataSource = (DataSource ) bean ;
56- DataSource wrapped = JdbcTelemetry .builder (openTelemetryProvider .getObject ())
57- .setStatementSanitizationEnabled (
58- InstrumentationConfigUtil .isStatementSanitizationEnabled (
59- configPropertiesProvider .getObject (),
60- "otel.instrumentation.jdbc.statement-sanitizer.enabled" ))
61- .setCaptureQueryParameters (
62- configPropertiesProvider
63- .getObject ()
64- .getBoolean (
65- "otel.instrumentation.jdbc.experimental.capture-query-parameters" , false ))
66- .setTransactionInstrumenterEnabled (
67- configPropertiesProvider
68- .getObject ()
69- .getBoolean ("otel.instrumentation.jdbc.experimental.transaction.enabled" , false ))
70- .build ()
71- .wrap (dataSource );
49+ @ Override
50+ public Object postProcessAfterInitialization (Object bean , String beanName ) {
51+ // Exclude scoped proxy beans to avoid double wrapping
52+ if (bean instanceof DataSource
53+ && !isRoutingDatasource (bean )
54+ && !ScopedProxyUtils .isScopedTarget (beanName )) {
55+ DataSource dataSource = (DataSource ) bean ;
7256
73- ProxyFactory proxyFactory = new ProxyFactory (DataSource .class );
74- proxyFactory .setTarget (bean );
75- proxyFactory .addAdvice (
76- new MethodInterceptor () {
77- @ Nullable
78- @ Override
79- public Object invoke (@ Nonnull MethodInvocation invocation ) throws Throwable {
80- return AopUtils .invokeJoinpointUsingReflection (
81- wrapped , invocation .getMethod (), invocation .getArguments ());
82- }
83- });
57+ // Wrap the original DataSource with OpenTelemetry instrumentation
58+ DataSource wrapped = JdbcTelemetry .builder (openTelemetryProvider .getObject ())
59+ .setStatementSanitizationEnabled (
60+ InstrumentationConfigUtil .isStatementSanitizationEnabled (
61+ configPropertiesProvider .getObject (),
62+ "otel.instrumentation.jdbc.statement-sanitizer.enabled" ))
63+ .setCaptureQueryParameters (
64+ configPropertiesProvider
65+ .getObject ()
66+ .getBoolean (
67+ "otel.instrumentation.jdbc.experimental.capture-query-parameters" , false ))
68+ .setTransactionInstrumenterEnabled (
69+ configPropertiesProvider
70+ .getObject ()
71+ .getBoolean ("otel.instrumentation.jdbc.experimental.transaction.enabled" , false ))
72+ .build ()
73+ .wrap (dataSource );
8474
85- return proxyFactory .getProxy ();
86- }
87- return bean ;
75+ /**
76+ * Spring Boot's configuration binding and rebinding mechanisms (such as those triggered by
77+ * Nacos configuration refresh) may attempt to reconstruct beans using their concrete class
78+ * constructors. If a custom DataSource implementation (such as OpenTelemetryDataSource)
79+ * is returned directly, Spring may not find a suitable constructor during rebinding, resulting
80+ * in errors like "ExistingValue must be an instance of com.zaxxer.hikari.HikariDataSource".
81+ *
82+ * To prevent this, we create a JDK dynamic proxy implementing only the DataSource interface.
83+ * The proxy delegates all method calls to the wrapped (instrumented) DataSource.
84+ * This approach "hides" the actual implementation class and ensures that Spring interacts only
85+ * with the DataSource interface, avoiding issues related to constructor resolution or type casting
86+ * during bean rebinding.
87+ */
88+ ProxyFactory proxyFactory = new ProxyFactory (DataSource .class );
89+ // Set the original bean as the target (important for AOP and bean lifecycle)
90+ proxyFactory .setTarget (bean );
91+ // Delegate all method calls to the wrapped, instrumented DataSource
92+ proxyFactory .addAdvice (
93+ new MethodInterceptor () {
94+ @ Nullable
95+ @ Override
96+ public Object invoke (@ Nonnull MethodInvocation invocation ) throws Throwable {
97+ return AopUtils .invokeJoinpointUsingReflection (
98+ wrapped , invocation .getMethod (), invocation .getArguments ());
99+ }
100+ });
101+
102+ // Return the proxy instead of the instrumented DataSource instance
103+ // This ensures proper interaction with Spring's bean lifecycle and rebinding
104+ return proxyFactory .getProxy ();
88105 }
106+ return bean ;
107+ }
89108
90109 // To be one of the first bean post-processors to be executed
91110 @ Override
0 commit comments