[spring-projects/spring-boot]为 Flyway 添加原生镜像支持

2024-06-26 479 views
5

Flyway烟雾测试目前失败,因为 GraalVM 抱怨代理生成,这可能与以下情​​况有关@FlywayDataSource

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dataSource': Unsatisfied dependency expressed through method 'dataSource' parameter 0: Error creating bean with name 'spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties': Proxy class defined by interfaces [interface org.springframework.beans.factory.annotation.Qualifier, interface org.springframework.core.annotation.SynthesizedAnnotation] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options.
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArgument(BeanInstanceSupplier.java:349) ~[na:na]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArguments(BeanInstanceSupplier.java:265) ~[na:na]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:208) ~[na:na]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainInstanceFromSupplier(AbstractAutowireCapableBeanFactory.java:1224) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1209) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1156) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:566) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:526) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:930) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:926) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:592) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) ~[flyway:3.0.0-SNAPSHOT]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:430) ~[flyway:3.0.0-SNAPSHOT]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) ~[flyway:3.0.0-SNAPSHOT]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[flyway:3.0.0-SNAPSHOT]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[flyway:3.0.0-SNAPSHOT]
    at com.example.flyway.FlywayApplication.main(FlywayApplication.java:13) ~[flyway:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties': Proxy class defined by interfaces [interface org.springframework.beans.factory.annotation.Qualifier, interface org.springframework.core.annotation.SynthesizedAnnotation] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options.
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:611) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:526) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1374) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1294) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArgument(BeanInstanceSupplier.java:332) ~[na:na]
    ... 20 common frames omitted
Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface org.springframework.beans.factory.annotation.Qualifier, interface org.springframework.core.annotation.SynthesizedAnnotation] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options.
    at com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:89) ~[na:na]
    at com.oracle.svm.reflect.proxy.DynamicProxySupport.getProxyClass(DynamicProxySupport.java:158) ~[na:na]
    at java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:48) ~[flyway:na]
    at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1037) ~[flyway:na]
    at org.springframework.core.annotation.SynthesizedMergedAnnotationInvocationHandler.createProxy(SynthesizedMergedAnnotationInvocationHandler.java:305) ~[na:na]
    at org.springframework.core.annotation.TypeMappedAnnotation.createSynthesizedAnnotation(TypeMappedAnnotation.java:333) ~[na:na]
    at org.springframework.core.annotation.AbstractMergedAnnotation.synthesize(AbstractMergedAnnotation.java:210) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.core.annotation.AbstractMergedAnnotation.synthesize(AbstractMergedAnnotation.java:200) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.core.annotation.AnnotationUtils.getAnnotation(AnnotationUtils.java:227) ~[na:na]
    at org.springframework.core.annotation.AnnotationUtils.getAnnotation(AnnotationUtils.java:252) ~[na:na]
    at org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils.isQualifierMatch(BeanFactoryAnnotationUtils.java:182) ~[na:na]
    at org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils.qualifiedBeansOfType(BeanFactoryAnnotationUtils.java:68) ~[na:na]
    at org.springframework.boot.context.properties.ConversionServiceDeducer$ConverterBeans.beans(ConversionServiceDeducer.java:99) ~[na:na]
    at org.springframework.boot.context.properties.ConversionServiceDeducer$ConverterBeans.<init>(ConversionServiceDeducer.java:93) ~[na:na]
    at org.springframework.boot.context.properties.ConversionServiceDeducer.getConversionServices(ConversionServiceDeducer.java:65) ~[na:na]
    at org.springframework.boot.context.properties.ConversionServiceDeducer.getConversionServices(ConversionServiceDeducer.java:55) ~[na:na]
    at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.getConversionServices(ConfigurationPropertiesBinder.java:183) ~[flyway:na]
    at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.getBinder(ConfigurationPropertiesBinder.java:168) ~[flyway:na]
    at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bind(ConfigurationPropertiesBinder.java:95) ~[flyway:na]
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:89) ~[flyway:na]
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:78) ~[flyway:na]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:425) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1745) ~[flyway:6.0.0-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:604) ~[flyway:6.0.0-SNAPSHOT]
    ... 29 common frames omitted

回答

6

修复此问题后,我预计会出现更多 Flyway 问题。Flyway 从类路径中枚举迁移文件,而 GraalVM 不支持此问题。

9

Stephane 将会把代理提示添加到 Spring Framework 中,因为这个代理提示不是特定于的,@FlywayDataSource而是针对所有带有元注释的注释@Qualifier

5

我已经为 Flyway 的reachability repo 创建了一个 PR。测试中有一个自定义项ResourceProvider,这是必需的,因为 GraalVM 不支持枚举类路径资源。我想我们必须向 Boot 添加一些内容,以便在 AOT 阶段收集迁移文件,然后生成包含迁移文件列表的代码,或者写入一个文件,然后由自定义项读取该文件ResourceProvider

2

即使合并了 PR,Flyway 仍然会失败,因为它认为没有可用的 SLF4J,并尝试使用 Log4J2 作为日志系统。我在 reachability repo 上创建了另一个 PR来修复 logback 的这个问题。

除此之外,我还尝试在本机映像中查找迁移(因为类路径枚举不起作用 :/),您可以在此处查看代码。我实施了两种方法:

  1. 在 AOT 阶段查找迁移并将其写入索引文件,该文件稍后用于查找迁移
  2. 在 AOT 阶段查找迁移,并为 bean 生成代码,然后该 bean 知道如何查找迁移
8

根据 Stephane 的评价:

PoC 的问题:它使用BeanFactoryInitializationAotProcessor而不是BeanRegistrationAotProcessor。使用BeanRegistrationAotProcessor会允许对默认 Flyway bean 的存在做出反应ResourceProvider,然后用代码替换该 bean,该代码new会为 提供ResourceProvider一个固定的迁移列表。

另一个问题是代码重新实现了 Flyway 如何查找迁移(使用类路径枚举)。如果 Flyway 提供一个 API,我们可以在 AOT 阶段调用该 API 来获取迁移列表,然后将其输入到我们的自定义中,那就更好了ResourceProvider。现在我们冒着 Flyway 做一些细微不同的事情的风险,我们不包括所有迁移,或者包括太多迁移,等等。

一般来说,解决方案 2(代码生成)是可行的,解决方案 1(索引文件)可以放弃。

新方法的问题:

  • Flyway 中的默认值ResourceProvider似乎是Scanner,这确实很难new。我们可以通过引入另一种信号类型来解决这个问题,就像PersistenceManagedTypes在 JPA 支持中所做的那样。

我将研究如何重构 PoC 以适应这些变化。

8

我已经打开/评论了 Flyway 和 GraalVM 问题,以使 Flyway 正常运行,而无需我们编写一堆解决方法。

4

即使 GraalVM 团队/Flyway 团队不想自己支持它,我也找到了一个比在 AOT 阶段寻找迁移更好的解决方法:实现一个 Flyway 自定义,ResourceProvider使用resource:GraalVM 中的方案来查找迁移。PoC 在这里:https ://github.com/mhalbritter/flyway-native-image

剩下的ResourceProvider就是资源提示db/migration/*.sql(或用户配置的任何内容)。

该方法的缺点是,当自ResourceProvider定义到位时,它会被调用用于所有迁移位置,甚至可能是带有filesystem:s3:前缀的位置。将其委托回默认位置会很好ResourceProvider,但似乎没有简单的方法可以做到这一点。

8

我将暂时搁置此问题,直到 Flyway 或 GraalVM 对此问题做出反应。

3

到目前为止,我还没有收到 Flyway 团队关于 GraalVM 的回应。

我目前正在本机映像中使用 Flyway,并且可能已经找到了一种方法来使用它们Scanner来获取对 AWS、文件等的支持,并且只有在本机映像中运行并读取位置时才会回到我们自己的实现classpath:,使用 PathMatchingResourcePatternResolver 中的新功能列出本机映像中的资源。

3

我在这里有一些工作:https://github.com/mhalbritter/sb3-native-flyway-poc-2

有一个ResourceProviderCustomizer,当未在 AOT / 本机映像中运行时使用。它是一个,在 Flyway 设置时FlywayConfigurationCustomizer由 Spring Boot 调用。这是一个 noop 实现,不执行任何操作。它的唯一目的是在 AOT 模式下运行时向FlywayBeanRegistrationAotProcessor发出信号。这将 no-op 替换为NativeImageResourceProviderCustomizer。当 Spring Boot 执行此定制器时,它会检查用户是否提供了自己的,如果没有,则安装NativeImageResourceProviderFlywayAutoconfigurationResourceProviderCustomizerFlywayBeanRegistrationAotProcessorResourceProviderCustomizerResourceProvider

使用NativeImageResourceProvider默认 FlywayScanner支持 S3、Google Cloud 等,并在原生镜像中运行时使用PathMatchingResourcePatternResolver查找迁移文件。在 JVM 上以 AOT 模式运行时,它基本上将所有工作委托给 Flyway Scanner

4

一个警告:Flyway 记录了以下内容:

2022-10-17T12:04:43.155+02:00  WARN 182256 --- [           main] o.f.core.internal.util.FeatureDetector   : Unable to scan location: /db/migration (unsupported protocol: resource)
2022-10-17T12:04:43.155+02:00  WARN 182256 --- [           main] o.f.core.internal.util.FeatureDetector   : Unable to scan location: /db/migration2 (unsupported protocol: resource)
2022-10-17T12:04:43.155+02:00  WARN 182256 --- [           main] o.f.c.i.s.classpath.ClassPathScanner     : Unable to scan location: /db/migration (unsupported protocol: resource)
2022-10-17T12:04:43.155+02:00  WARN 182256 --- [           main] o.f.c.i.s.classpath.ClassPathScanner     : Unable to scan location: /db/migration2 (unsupported protocol: resource)

这是因为 将NativeImageResourceProviderCustomizer所有位置都传递给 Flyways Scanner。我们可以在本机映像中运行时从传递给 的位置中删除类路径位置,Scanner以消除这些警告。

5

除了 WARN 日志之外,这看起来确实是 Flyway 支持的可行解决方案。我想知道是否FlywayBeanRegistrationAotProcessor需要此步骤,以及始终添加NativeImageResourceProviderCustomizer本机图像检查是否可行 - 我猜这样做有我目前没有注意到的缺点?Ping @snicoll 以了解我们是否有更好的模式可用于此。

2

我不能 100% 确信NativeImageResourceProviderCustomizer创建的扫描仪的行为与 Flyway 中的默认扫描仪完全相同。这就是我犹豫是否始终NativeImageResourceProviderCustomizer在 JVM 和本机映像上使用的原因。

1

我们可以从在本机图像中运行时传递给的位置中删除类路径位置,Scanner以消除这些警告。

不幸的是,这并不能消除来自的警告ClassPathScanner,只能消除来自的警告FeatureDetector

8

虽然所有迁移都已扫描并成功应用,但还是出现了此警告。这只是一个警告,还是会在后续阶段引发问题?