spring-projects / spring-modulith

Modular applications with Spring Boot
https://spring.io/projects/spring-modulith
Apache License 2.0
811 stars 139 forks source link

Support using observability features in GraalVM #646

Open dmakariev opened 5 months ago

dmakariev commented 5 months ago

I've created a simple application using

https://start.spring.io/#!type=maven-project&language=java&platformVersion=3.3.0&packaging=jar&jvmVersion=21&groupId=com.example&artifactId=demo-simple-obeservability&name=demo-simple-obeservability&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.demo-simple-obeservability&dependencies=actuator,web,modulith,native

I had to manually change

<spring-modulith.version>1.2.0-RC1</spring-modulith.version>

to

<spring-modulith.version>1.2.0</spring-modulith.version>

after executing

./mvnw clean install -Pnative native:compile
./target/demo-simple-obeservability

I got the following

2024-05-27T16:14:13.260+03:00  WARN 22326 --- [demo-simple-obeservability] [           main] [                                                 ] w.s.c.ServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Unable to start web server
Application run failed
org.springframework.context.ApplicationContextException: Unable to start web server
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:165)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:618)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352)
    at com.example.demo_simple_obeservability.DemoSimpleObeservabilityApplication.main(DemoSimpleObeservabilityApplication.java:10)
    at java.base@22.0.1/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat': java.lang.ExceptionInInitializerError
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:225)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1323)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1284)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:486)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:341)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:334)
    at org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration__BeanDefinitions$EmbeddedTomcat.lambda$getTomcatServletWebServerFactoryInstanceSupplier$0(ServletWebServerFactoryConfiguration__BeanDefinitions.java:35)
    at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:68)
    at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:54)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$get$2(BeanInstanceSupplier.java:206)
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58)
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.invokeBeanSupplier(BeanInstanceSupplier.java:218)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:206)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:949)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1219)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1162)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getWebServerFactory(ServletWebServerApplicationContext.java:223)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:186)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:162)
    ... 9 more
Caused by: java.lang.RuntimeException: java.lang.ExceptionInInitializerError
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:64)
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46)
    at org.springframework.util.function.SingletonSupplier.get(SingletonSupplier.java:106)
    at org.springframework.modulith.runtime.ApplicationModulesRuntime.isApplicationClass(ApplicationModulesRuntime.java:69)
    at org.springframework.modulith.observability.ModuleTracingBeanPostProcessor.postProcessAfterInitialization(ModuleTracingBeanPostProcessor.java:82)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:438)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1791)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600)
    ... 39 more
Caused by: java.util.concurrent.ExecutionException: java.lang.ExceptionInInitializerError
    at java.base@22.0.1/java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.base@22.0.1/java.util.concurrent.FutureTask.get(FutureTask.java:191)
    at org.springframework.modulith.runtime.autoconfigure.SpringModulithRuntimeAutoConfiguration.lambda$modulesRuntime$1(SpringModulithRuntimeAutoConfiguration.java:71)
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58)
    ... 46 more
Caused by: java.lang.ExceptionInInitializerError
    at com.tngtech.archunit.core.importer.ClassFileImporter.lambda$importPackages$1(ClassFileImporter.java:212)
    at java.base@22.0.1/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:288)
    at java.base@22.0.1/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:722)
    at java.base@22.0.1/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:556)
    at java.base@22.0.1/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:546)
    at java.base@22.0.1/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
    at java.base@22.0.1/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
    at java.base@22.0.1/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:702)
    at com.tngtech.archunit.core.importer.ClassFileImporter.importPackages(ClassFileImporter.java:213)
    at org.springframework.modulith.core.ApplicationModules.<init>(ApplicationModules.java:147)
    at org.springframework.modulith.core.ApplicationModules.<init>(ApplicationModules.java:120)
    at org.springframework.modulith.core.ApplicationModules.lambda$of$17(ApplicationModules.java:622)
    at java.base@22.0.1/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1710)
    at org.springframework.modulith.core.ApplicationModules.of(ApplicationModules.java:618)
    at org.springframework.modulith.core.ApplicationModules.of(ApplicationModules.java:238)
    at org.springframework.modulith.core.ApplicationModules.of(ApplicationModules.java:220)
    at org.springframework.modulith.runtime.autoconfigure.SpringModulithRuntimeAutoConfiguration$ApplicationModulesBootstrap.initializeApplicationModules(SpringModulithRuntimeAutoConfiguration.java:157)
    at org.springframework.modulith.runtime.autoconfigure.SpringModulithRuntimeAutoConfiguration.lambda$modulesRuntime$0(SpringModulithRuntimeAutoConfiguration.java:70)
    at java.base@22.0.1/java.util.concurrent.FutureTask.run(FutureTask.java:317)
    at java.base@22.0.1/java.lang.Thread.runWith(Thread.java:1583)
    at java.base@22.0.1/java.lang.Thread.run(Thread.java:1570)
    at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:853)
    at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:829)
Caused by: com.tngtech.archunit.core.PluginLoader$PluginLoadingFailedException: Couldn't load plugin of type com.tngtech.archunit.core.importer.ModuleImportPlugin
    at com.tngtech.archunit.core.PluginLoader$PluginSupplier.create(PluginLoader.java:88)
    at com.tngtech.archunit.core.PluginLoader$PluginSupplier.get(PluginLoader.java:73)
    at com.tngtech.archunit.thirdparty.com.google.common.base.Suppliers$NonSerializableMemoizingSupplier.get(Suppliers.java:186)
    at com.tngtech.archunit.core.PluginLoader.load(PluginLoader.java:50)
    at com.tngtech.archunit.core.importer.ImportPlugin$Loader.loadForCurrentPlatform(ImportPlugin.java:40)
    at com.tngtech.archunit.core.importer.Locations.<clinit>(Locations.java:43)
    ... 23 more
Caused by: java.lang.ClassNotFoundException: com.tngtech.archunit.core.importer.ModuleImportPlugin
    at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:143)
    at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:106)
    at java.base@22.0.1/java.lang.Class.forName(DynamicHub.java:1387)
    at java.base@22.0.1/java.lang.Class.forName(DynamicHub.java:1352)
    at java.base@22.0.1/java.lang.Class.forName(DynamicHub.java:1346)
    at com.tngtech.archunit.core.PluginLoader$PluginSupplier.create(PluginLoader.java:84)
    ... 28 more

then I've tried mitigating the issue as far as I could by adding

import com.tngtech.archunit.core.importer.ModuleImportPlugin;

@SpringBootApplication
@RegisterReflectionForBinding({ModuleImportPlugin.class})
public class DemoApplication {

and in pom.xml

        <dependency>
            <groupId>org.springframework.modulith</groupId>
            <artifactId>spring-modulith-observability</artifactId>
<!--            <scope>runtime</scope> -->
        </dependency>
.....
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
                                 <configuration>
                                       <buildArgs>
                                             <buildArg>--enable-url-protocols=jrt</buildArg>
                                       </buildArgs>
                                 </configuration>
            </plugin>

now the stacktrace is

2024-05-27T15:57:17.280+03:00  WARN 20051 --- [demo] [           main] [                                                 ] w.s.c.ServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Unable to start web server
Application run failed
org.springframework.context.ApplicationContextException: Unable to start web server
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:165)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:618)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352)
    at com.example.demo.DemoApplication.main(DemoApplication.java:13)
    at java.base@22.0.1/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat': com.tngtech.archunit.thirdparty.com.google.common.util.concurrent.ExecutionError: com.oracle.svm.core.jdk.UnsupportedFeatureError: JRT file system is disabled
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:225)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1323)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1284)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:486)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:341)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:334)
    at org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration__BeanDefinitions$EmbeddedTomcat.lambda$getTomcatServletWebServerFactoryInstanceSupplier$0(ServletWebServerFactoryConfiguration__BeanDefinitions.java:35)
    at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:68)
    at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:54)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$get$2(BeanInstanceSupplier.java:206)
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58)
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.invokeBeanSupplier(BeanInstanceSupplier.java:218)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:206)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:949)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1219)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1162)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getWebServerFactory(ServletWebServerApplicationContext.java:223)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:186)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:162)
    ... 9 more
Caused by: java.lang.RuntimeException: com.tngtech.archunit.thirdparty.com.google.common.util.concurrent.ExecutionError: com.oracle.svm.core.jdk.UnsupportedFeatureError: JRT file system is disabled
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:64)
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46)
    at org.springframework.util.function.SingletonSupplier.get(SingletonSupplier.java:106)
    at org.springframework.modulith.runtime.ApplicationModulesRuntime.isApplicationClass(ApplicationModulesRuntime.java:69)
    at org.springframework.modulith.observability.ModuleTracingBeanPostProcessor.postProcessAfterInitialization(ModuleTracingBeanPostProcessor.java:82)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:438)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1791)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600)
    ... 39 more

As convenience I'm attaching the modified project as zip demo-modified.zip

Thanks in advance !

odrotbohm commented 5 months ago

That's a current limitation, unfortunately, as, right now, the bean decoration requires a full ApplicationModules instance present. That in turn requires us to run ArchUnit which needs to read the class files, which is problematic on GraalVM.

We can probably investigate creating a list of bean types to be decorated with the observability interceptors during the AOT phase and rather use that at runtime.