简体   繁体   English

在 liquibase CustomTaskChange 类中使用其他 spring bean

[英]Use other spring beans in liquibase CustomTaskChange class

I need to do some data migration, which is too complex to do it in a liquibase changeset.我需要做一些数据迁移,这太复杂了,无法在 liquibase 变更集中进行。 We use spring我们用弹簧

That's why I wrote a class implementing the liquibase.change.custom.CustomTaskChange class.这就是为什么我编写了一个实现liquibase.change.custom.CustomTaskChange类的类。 I then reference it from within a changeset.然后我从变更集中引用它。

All is fine to this point.一切都很好。

My question is: Is it possible to get access to the other spring beans from within such a class?我的问题是:是否可以从这样的类中访问其他 spring bean?

When I try to use an autowired bean in this class, it's null, which makes me think that the autowiring is simply not done at this point?当我尝试在此类中使用自动装配的 bean 时,它为 null,这让我认为此时根本没有完成自动装配?

I've also read in some other thread, that the Liquibase bean must be initialized before all other beans, is that correct?我还在其他一些线程中读到过,Liquibase bean 必须在所有其他 bean 之前初始化,对吗?

Here is a snippet of the class I wrote:这是我写的类的片段:

@Component
public class UpdateJob2 implements CustomTaskChange {

private String param1;

@Autowired
private SomeBean someBean;

@Override
public void execute(Database database) throws CustomChangeException {
    try {
        List<SomeObject> titleTypes = someBean.getSomeObjects(
                param1
        );
    } catch (Exception e) {         
        throw new CustomChangeException();
    }
...

I get an exception and when debugging I can see that someBean is null.我得到一个异常,在调试时我可以看到 someBean 为空。

Here is the config for the SpringLiquibase:这是 SpringLiquibase 的配置:

@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
@ComponentScan({
"xxx.xxx.."})
public class DatabaseConfiguration {

@Bean
public SpringLiquibase springLiquibase() {
    SpringLiquibase liquibase = new SpringLiquibase();
    liquibase.setDataSource(dataSource());
    liquibase.setChangeLog("classpath:liquibase-changelog.xml");
    return liquibase;
}
...

Some more config:还有一些配置:

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
     http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">

    <includeAll path="dbschema"/>

</databaseChangeLog>

And here the call from the changeset:这里是来自变更集的调用:

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
     http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">

<changeSet id="201509281536" author="sr">
        <customChange class="xxx.xxx.xxx.UpdateJob2">
            <param name="param1" value="2" />
        </customChange>
</changeSet>

I'm currently running through this problem as well...After hours of digging, I found 2 solutions, no AOP is needed.我目前也在解决这个问题......经过数小时的挖掘,我找到了 2 个解决方案,不需要 AOP。

Liquibase version: 4.1.1 Liquibase 版本:4.1.1


Solution A方案一

In the official example of customChange在customChange的官方例子中

https://docs.liquibase.com/change-types/community/custom-change.html https://docs.liquibase.com/change-types/community/custom-change.html

In CustomChange.setFileOpener, ResourceAccessor actually is an inner class SpringLiquibase$SpringResourceOpener, and it has a member 'resourceLoader', which is indeed an ApplicationContext.在CustomChange.setFileOpener中,ResourceAccessor实际上是一个内部类SpringLiquibase$SpringResourceOpener,它有一个成员'resourceLoader',它确实是一个ApplicationContext。 Unfortunately, it's private and no getter is available.不幸的是,它是私有的,没有可用的吸气剂。

So here comes an ugly solution: USE REFLECTION TO GET IT AND INVOKE getBean所以这里有一个丑陋的解决方案:使用反射来获取它并调用 getBean


Solution B (More elegant)方案B (更优雅)

Before we get started, let's see some basic facts about Liquibase.在开始之前,让我们先了解一些关于 Liquibase 的基本事实。 The official way of integrating Liquibase with Spring Boot is by using:将 Liquibase 与 Spring Boot 集成的官方方法是使用:

org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration$LiquibaseConfiguration org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration$LiquibaseConfiguration

This is a conditional inner config bean for creating SpringLiquibase ONLY WHEN SpringLiquibase.class IS MISSING这是一个有条件的内部配置 bean,用于仅在 SpringLiquibase.class 丢失时创建 SpringLiquibase

@Configuration
@ConditionalOnMissingBean(SpringLiquibase.class)
@EnableConfigurationProperties({ DataSourceProperties.class,
        LiquibaseProperties.class })
@Import(LiquibaseJpaDependencyConfiguration.class)
public static class LiquibaseConfiguration {...}

So we can create our own SpringLiquibase by adding a liquibase config bean所以我们可以通过添加一个 liquibase 配置 bean 来创建我们自己的 SpringLiquibase

@Getter
@Configuration
@EnableConfigurationProperties(LiquibaseProperties.class)
public class LiquibaseConfig {

    private DataSource dataSource;

    private LiquibaseProperties properties;

    public LiquibaseConfig(DataSource dataSource, LiquibaseProperties properties) {
        this.dataSource = dataSource;
        this.properties = properties;
    }

    @Bean
    public SpringLiquibase liquibase() {
        SpringLiquibase liquibase = new BeanAwareSpringLiquibase();
        liquibase.setDataSource(dataSource);
        liquibase.setChangeLog(this.properties.getChangeLog());
        liquibase.setContexts(this.properties.getContexts());
        liquibase.setDefaultSchema(this.properties.getDefaultSchema());
        liquibase.setDropFirst(this.properties.isDropFirst());
        liquibase.setShouldRun(this.properties.isEnabled());
        liquibase.setLabels(this.properties.getLabels());
        liquibase.setChangeLogParameters(this.properties.getParameters());
        liquibase.setRollbackFile(this.properties.getRollbackFile());
        return liquibase;
   }
}

inside which we new an extended class of SpringLiquibase: BeanAwareSpringLiquibase在其中我们新建了一个 SpringLiquibase 的扩展类: BeanAwareSpringLiquibase

public class BeanAwareSpringLiquibase extends SpringLiquibase {
private static ResourceLoader applicationContext;

public BeanAwareSpringLiquibase() {
}

public static final <T> T getBean(Class<T> beanClass) throws Exception {
    if (ApplicationContext.class.isInstance(applicationContext)) {
        return ((ApplicationContext)applicationContext).getBean(beanClass);
    } else {
        throw new Exception("Resource loader is not an instance of ApplicationContext");
    }
}

public static final <T> T getBean(String beanName) throws Exception {
    if (ApplicationContext.class.isInstance(applicationContext)) {
        return ((ApplicationContext)applicationContext).getBean(beanName);
    } else {
        throw new Exception("Resource loader is not an instance of ApplicationContext");
    }
}

@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
    super.setResourceLoader(resourceLoader);
    applicationContext = resourceLoader;
}}

BeanAwareSpringLiquibase has a static reference to ResourceLoader aforementioned. BeanAwareSpringLiquibase 有一个对 ResourceLoader 的静态引用。 On Spring Bootstartup, 'setResourceLoader' defined by ResourceLoaderAware interface will be invoked automatically before 'afterPropertiesSet' defined by InitializingBean interface, thus the code execution will be like this: Spring Bootstartup时,ResourceLoaderAware接口定义的'setResourceLoader'会在InitializingBean接口定义的'afterPropertiesSet'之前自动调用,因此代码执行如下:

  1. Spring Boot invokes setResourceLoader, injecting resourceLoader(applicationContext) to BeanAwareSpringLiquibase. Spring Boot 调用 setResourceLoader,将 resourceLoader(applicationContext) 注入到 BeanAwareSpringLiquibase。

  2. Spring Boot invokes afterPropertiesSet, performing Liquibase update including customChange, by now you already have full access to applicationContext Spring Boot 调用 afterPropertiesSet,执行包括 customChange 在内的 Liquibase 更新,现在您已经拥有对 applicationContext 的完全访问权限

PS: PS:

  1. Remember adding your Liquibase config bean package path to @ComponentScan or it will still use LiquibaseAutoConfiguration instead of our own LiquibaseConfig.请记住将您的 Liquibase 配置 bean 包路径添加到 @ComponentScan,否则它仍将使用 LiquibaseAutoConfiguration 而不是我们自己的 LiquibaseConfig。

  2. Prepare all beans you need in 'setUp' before 'execute' would be a better convention.在“执行”之前在“setUp”中准备您需要的所有 bean 将是一个更好的约定。

The classes referenced in your changeset.xml are not managed by Spring, so the cool stuff like DI will not work.在您的 changeset.xml 中引用的类不是由 Spring 管理的,所以像 DI 这样很酷的东西将不起作用。

What you can do is to inject Spring beans into Non-Spring objects.您可以做的是将 Spring bean 注入到非 Spring 对象中。 See this answer: https://stackoverflow.com/a/1377740/4365460看到这个答案: https : //stackoverflow.com/a/1377740/4365460

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM