We ran into a problem that took us some time to debug and we hope that this could be improved:
- AOT processing fails as it cannot create a DataSource
bean because the properties passed are empty
- AOT processing actually should not create a DataSource
bean
- the DataSource
bean is reference by an AOP bean annotated with @Aspect
that implements the Ordered
interface
- as soon as there is more than one Advisor
(bean providing AOP advice) applicable to a bean, the code attempts to sort the advisors and if one of them implements the Ordered
interface, its needs to be instantiated in order to call the order()
method
- this fails as the DataSource
cannot be created
Once we knew all those details, the fix was trivial: instead of implementing the Ordered
interface, we use the @Order
annotation.
We did not see this mentioned anywhere in the documentation. It could go into the AOT best practices.
Do you see any option to detect this during AOT processing so the error can be handled with a more helpful message?
Last resort would be to just keep this issue around so people can find it.
For reference an example stack trace:
> Task :processAot
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.2)
2024-02-09 10:16:09.432 INFO 5749 [ ] [ ] --- [ main] com.example.ApplicationKt : Starting ApplicationKt using Java 21 with PID 5749 (/Users/xyz/Projects/example/build/classes/kotlin/main started by xyz in /Users/xyz/Projects/example)
2024-02-09 10:16:09.434 INFO 5749 [ ] [ ] --- [ main] com.example.ApplicationKt : The following 1 profile is active: "aot"
2024-02-09 10:16:10.306 INFO 5749 [ ] [ ] --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JDBC repositories in DEFAULT mode.
2024-02-09 10:16:10.394 INFO 5749 [ ] [ ] --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 85 ms. Found 9 JDBC repository interfaces.
2024-02-09 10:16:10.643 INFO 5749 [ ] [ ] --- [ main] o.s.cloud.context.scope.GenericScope : BeanFactory id=899c789d-c52d-342c-b7fe-46da1a60c05c
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'scheduledExecutionCounterInterceptor' defined in com.example.monitoring.TaskExecutionMonitoringConfig: Unsatisfied dependency expressed through method 'scheduledExecutionCounterInterceptor' parameter 0: Error creating bean with name 'taskExecutionStatisticDao' defined in com.example.monitoring.TaskExecutionMonitoringConfigPostgres: Unsatisfied dependency expressed through method 'taskExecutionStatisticDao' parameter 0: Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Unsatisfied dependency expressed through method 'dataSourceScriptDatabaseInitializer' parameter 0: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:798)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:542)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1334)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1164)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory.getOrder(BeanFactoryAspectInstanceFactory.java:138)
at org.springframework.aop.aspectj.annotation.LazySingletonAspectInstanceFactoryDecorator.getOrder(LazySingletonAspectInstanceFactoryDecorator.java:95)
at org.springframework.aop.aspectj.annotation.InstantiationModelAwarePointcutAdvisorImpl.getOrder(InstantiationModelAwarePointcutAdvisorImpl.java:184)
at org.springframework.core.OrderComparator.findOrder(OrderComparator.java:145)
at org.springframework.core.annotation.AnnotationAwareOrderComparator.findOrder(AnnotationAwareOrderComparator.java:64)
at org.springframework.core.OrderComparator.getOrder(OrderComparator.java:128)
at org.springframework.core.OrderComparator.getOrder(OrderComparator.java:116)
at org.springframework.core.OrderComparator.doCompare(OrderComparator.java:86)
at org.springframework.core.OrderComparator.compare(OrderComparator.java:73)
at org.springframework.aop.aspectj.autoproxy.AspectJPrecedenceComparator.compare(AspectJPrecedenceComparator.java:83)
at org.springframework.aop.aspectj.autoproxy.AspectJPrecedenceComparator.compare(AspectJPrecedenceComparator.java:51)
at org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder.compareTo(AspectJAwareAdvisorAutoProxyCreator.java:129)
at org.aspectj.util.PartialOrder$SortObject.addDirectedLinks(PartialOrder.java:71)
at org.aspectj.util.PartialOrder.addNewPartialComparable(PartialOrder.java:92)
at org.aspectj.util.PartialOrder.sort(PartialOrder.java:128)
at org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator.sortAdvisors(AspectJAwareAdvisorAutoProxyCreator.java:75)
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findEligibleAdvisors(AbstractAdvisorAutoProxyCreator.java:100)
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean(AbstractAdvisorAutoProxyCreator.java:78)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.determineBeanType(AbstractAutoProxyCreator.java:249)
at org.springframework.context.support.GenericApplicationContext.preDetermineBeanTypes(GenericApplicationContext.java:436)
at org.springframework.context.support.GenericApplicationContext.refreshForAotProcessing(GenericApplicationContext.java:418)
at org.springframework.context.aot.ApplicationContextAotGenerator.lambda$processAheadOfTime$0(ApplicationContextAotGenerator.java:54)
at org.springframework.context.aot.ApplicationContextAotGenerator.withCglibClassHandler(ApplicationContextAotGenerator.java:67)
at org.springframework.context.aot.ApplicationContextAotGenerator.processAheadOfTime(ApplicationContextAotGenerator.java:53)
at org.springframework.context.aot.ContextAotProcessor.performAotProcessing(ContextAotProcessor.java:106)
at org.springframework.context.aot.ContextAotProcessor.doProcess(ContextAotProcessor.java:84)
at org.springframework.context.aot.ContextAotProcessor.doProcess(ContextAotProcessor.java:49)
at org.springframework.context.aot.AbstractAotProcessor.process(AbstractAotProcessor.java:82)
at org.springframework.boot.SpringApplicationAotProcessor.main(SpringApplicationAotProcessor.java:80)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'taskExecutionStatisticDao' defined in com.example.monitoring.TaskExecutionMonitoringConfigPostgres: Unsatisfied dependency expressed through method 'taskExecutionStatisticDao' parameter 0: Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Unsatisfied dependency expressed through method 'dataSourceScriptDatabaseInitializer' parameter 0: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:798)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:542)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1334)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1164)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1443)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:907)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:785)
... 38 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Unsatisfied dependency expressed through method 'dataSourceScriptDatabaseInitializer' parameter 0: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:798)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:542)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1334)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1164)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:312)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1443)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:907)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:785)
... 52 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:651)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:639)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1334)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1164)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1443)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:907)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:785)
... 68 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:177)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'taskExecutionStatisticDao' defined in com.example.monitoring.TaskExecutionMonitoringConfigPostgres: Unsatisfied dependency expressed through method 'taskExecutionStatisticDao' parameter 0: Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Unsatisfied dependency expressed through method 'dataSourceScriptDatabaseInitializer' parameter 0: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:647)
... 82 more
Caused by: org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Failed to determine a suitable driver class
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Unsatisfied dependency expressed through method 'dataSourceScriptDatabaseInitializer' parameter 0: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class
at org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.determineDriverClassName(DataSourceProperties.java:186)
at org.springframework.boot.autoconfigure.jdbc.PropertiesJdbcConnectionDetails.getDriverClassName(PropertiesJdbcConnectionDetails.java:49)
at org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration.createDataSource(DataSourceConfiguration.java:55)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class
at org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari.dataSource(DataSourceConfiguration.java:117)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
Caused by: org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Failed to determine a suitable driver class
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:140)
... 83 more
> Task :processAot FAILED
Execution failed for task ':processAot'.
> Process 'command '/Users/xyz/.sdkman/candidates/java/21-tem/bin/java'' finished with non-zero exit value 1
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/8.5/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD FAILED in 1m 14s
5 actionable tasks: 2 executed, 3 up-to-date
Comment From: snicoll
Thanks for the report. I 100% agree that AOT should not create such a bean. I'd like to understand your setup a bit more though. Can you please share a small sample we can run ourselves that reproduces the issue? You can attach a zip here or push the code to a GitHub repository.
Comment From: codesimplicity
I've attached a minimal demo. The stack for that differs slightly as I've simplified the code a bit.
The very last @Component
is the one causing the problem as it implements the Ordered
interface.
If @Component
is removed, processAot
works.
Comment From: snicoll
Thanks for the sample.
AbstractAutoProxyCreator#determineBeanType
is invoked at build-time to infer the most precise type of DummyTask
as one of the advisor may change it. Perhaps the sorting is not necessary in that particular case, what do you think @jhoeller?
That being said, an aspect is a lower-level infrastructure bean and injecting a DAO in there is not necessarily a good idea as these are initialized very early on. I've added @Lazy
on the executionStatisticsDao
constructor parameter and this fixed it.
We're going to see what we can do here, but I would apply that best practice on high-level dependencies like that irrespective of this issue. If you do so, you can also use ObjectProvider
to avoid creating a proxy for the @Lazy
part.
Comment From: codesimplicity
While I understand your view on the type of beans and advices regarding that, this requires a detailed understanding of Spring which most people (including me) won't have. Additionally the example is obviously simplified and minified to reproduce the issue. In the actual code, the DAO is injected more indirectly making such things less obvious. But I'll try to remember that beans should be lazily injected into aspects. Anybody migrating existing codebases to native will hopefully find this issue, learn how to solve it and also learn more on the details of Spring.
Comment From: snicoll
@codesimplicity the issue is not closed and we'll try to fix it.
I am just stating a best practice that has nothing do with Native.
Comment From: codesimplicity
@codesimplicity the issue is not closed and we'll try to fix it.
Yes, got that and thanks for taking care.
I am just stating a best practice that has nothing do with Native.
Also got that and just wanted to note that in my experience best practices like that tend to be forgotten if that has no obvious consequences. Retrospectively sounds like a lame excuse 😁
Connected that to Native as it was triggered during AOT processing. So not following the best practice has a consequence in AOT world.
I may be wrong, but I assumed that the DataSourceProperties
were intentionally empty for AOT processing as they should not be needed. Similar code works for MongoDB even though the DB connection fails due to a dummy connection string. No need to actually follow up on this detail as we now know how to properly fix this.
Comment From: jhoeller
Since the use of Ordered
in an aspect can generally have unpleasant initialization side effects, we throw a descriptive AopConfigException
hinting at the Ordered
vs. @Order
problem now if advisor sorting runs into such an unexpected BeanCreationException
. This should make this obvious in an AOT scenario as well as for complex runtime failures elsewhere.