0

我有一个Spring项目,我需要配置Flyway。使用默认的 FlywayAutoConfiguration,Flyway 迁移在其他一切(缓存、PostConstruct 注释、服务)之前立即执行。这是我预期的行为(在术语或启动工作流程中)

不幸的是,我需要覆盖默认的 FlywayAutoConfiguration,因为我使用自定义 Flyway 实现,但这不是我的主要问题,我的问题实际上与配置和初始化序列的 Spring Priority 有关。

所以请使用我自己的 flyway,我首先将 FlywayAutoConfiguration 复制到我的 maven 模块中,将其命名为 CustomFlywayAutoConfiguration 并调整导入。我还将属性“spring.flyway.enabled”更改为 false 并创建另一个“spring.flywaycustom.enabled”,以便能够激活我的而不是默认的。

这样做会完全改变启动顺序。现在,flyway 在启动序列结束时执行(在缓存和我项目中的其他 @PostConstruct 之后)

CustomFlywayAutoConfiguration 中定义的以下 bean 现在仅在启动序列结束时创建。使用默认的 FlywayAutoConfiguration ,一开始就很好地创建了。

@Bean
    @ConditionalOnMissingBean
    public FlywayMigrationInitializer flywayInitializer(Flyway flyway,
            ObjectProvider<FlywayMigrationStrategy> migrationStrategy) {
        return new FlywayMigrationInitializer(flyway, migrationStrategy.getIfAvailable());
    }

我试着玩了很多订购(HIGHEST_PRECEDENCE 和 LOWEST_PRECEDENCE)

  • 配置类上的@AutoConfigureOrder
  • @Order 组件
  • 尝试 Autowire FlywayMigrationInitializer 以更早地强制 bean 初始化

它没有改变任何东西,看起来像 Spring 忽略 @Order 和 @AutoConfigureOrder

知道为什么当配置在 spring-boot-autoconfigure 依赖项中时,它作为第一优先级启动,而当相同的配置代码在我的项目中时,我没有相同的顺序?

非常感谢你的帮助。

4

2 回答 2

0

您描述的所有注释都不会影响运行时语义:

  • @AutoConfigureOrder仅适用于自动配置(因此,如果您已将自动配置复制为用户配置,则甚至不会考虑)。这用于订购如何解析自动配置(典型用例:确保自动配置在另一个检查 bean X 是否可用之前贡献类型 X 的 bean 定义)。
  • @Order订购相同类型的组件。对“何时”发生某事没有任何影响

使用注入点强制初始化是一个好主意,但它只有在您注入它的组件在正确的时间自行初始化时才会起作用。

自动配置有一堆后处理器,它们在使用DataSourceFlywayMigrationInitializer. 例如,FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor确保它FlywayMigrationIntializer是实体管理器的依赖项,以便在EntityManager其他组件可用之前迁移数据库。该链接确保 Flyway 在正确的时间执行。我不知道为什么这不适用于您的副本,我们可以自己运行的示例在 GitHub 上共享可以帮助我们弄清楚。

尽管如此,请不要在您自己的项目中复制自动配置。我建议更详细地描述您的用例并打开一个问题,以便我们考虑改进自动配置。

于 2021-01-15T08:40:16.663 回答
0

感谢您的回答,我关注 FlywayMigrationInitializerEntityManagerFactoryDe​​pendsOnPostProcessor 帮助我解决问题。

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@EnableConfigurationProperties({ DataSourceProperties.class, FlywayProperties.class })
@Import({ FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor.class }) // Very important to force flyway before
public class CustomFlywayConfiguration {

    @Bean
    public Flyway flyway(FlywayProperties properties, DataSourceProperties dataSourceProperties,
            ResourceLoader resourceLoader, ObjectProvider<DataSource> dataSource,
            @FlywayDataSource ObjectProvider<DataSource> flywayDataSource,
            ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
            ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks) {
        FluentConfiguration configuration = new FluentConfiguration(resourceLoader.getClassLoader());
        DataSource dataSourceToMigrate = configureDataSource(configuration, properties, dataSourceProperties, flywayDataSource.getIfAvailable(), dataSource.getIfUnique());
        checkLocationExists(dataSourceToMigrate, properties, resourceLoader);
        configureProperties(configuration, properties);
        List<Callback> orderedCallbacks = callbacks.orderedStream().collect(Collectors.toList());
        configureCallbacks(configuration, orderedCallbacks);
        fluentConfigurationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configuration));
        configureFlywayCallbacks(configuration, orderedCallbacks);
        List<JavaMigration> migrations = javaMigrations.stream().collect(Collectors.toList());
        configureJavaMigrations(configuration, migrations);
        return new CustomFlyway(configuration);
    }


    /**
     * FlywayAutoConfiguration.FlywayConfiguration is conditioned to the missing flyway bean. @Import annotation are not executed in this case.
     * 
     * So if we declare our own Flyway bean, we also need to create this bean to trigger the flyway migration.
     * 
     * The main issue is now that bean is create after the @PostConstruct init method in MyInitComponent.
     * 
     */
    @Bean
    public FlywayMigrationInitializer flywayInitializer(Flyway flyway,
            ObjectProvider<FlywayMigrationStrategy> migrationStrategy) {
        return new FlywayMigrationInitializer(flyway, migrationStrategy.getIfAvailable());
    }

    /**
     * Thanks to that, it's now working, because make sure it's required before to create EntityManager
     */
    // @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
    // @ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
    static class FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor
            extends EntityManagerFactoryDependsOnPostProcessor {

        FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor() {
            super(FlywayMigrationInitializer.class);
        }

    }

...

这样确认我们不能轻易地覆盖 Flyway bean,而不从 FlywayAutoConfiguration 复制一些逻辑。

我用我找到的解决方案创建了一个小项目来重现这个错误。 https://github.com/w3blogfr/flyway-issue-demo

不知道spring自动配置项目中是否需要修复?

我检查了历史以找回提交 https://github.com/spring-projects/spring-boot/commit/795303d6676c163af899e87364846d9763055cf8

票就是这张。 https://github.com/spring-projects/spring-boot/issues/18382

这个变化看起来很技术性,可能他错过了@ConditionalOnClass

于 2021-01-15T11:50:54.873 回答