在 liquibase CustomTaskChange class 中使用其他 spring 个 bean
Use other spring beans in liquibase CustomTaskChange class
我需要做一些数据迁移,这太复杂了,无法在 liquibase 变更集中完成。我们使用 spring
这就是为什么我写了一个 class 实现 liquibase.change.custom.CustomTaskChange class 的原因。然后我从变更集中引用它。
至此一切都很好。
我的问题是:
是否可以从这样的 class 中访问其他 spring 个 bean?
当我尝试在此 class 中使用自动装配的 bean 时,它是空的,这让我觉得此时自动装配还没有完成?
我还在其他线程中读到,Liquibase bean 必须在所有其他 bean 之前初始化,对吗?
这是我写的 class 的片段:
@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();
}
...
我遇到异常,在调试时我可以看到 someBean 为空。
这是 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;
}
...
更多配置:
<?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>
这里是来自变更集的调用:
<?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>
您 changeset.xml 中引用的 类 不受 Spring 管理,因此像 DI 这样的酷东西将无法工作。
你可以做的是将 Spring bean 注入到非 Spring 对象中。看到这个答案:
我目前也在运行解决这个问题...经过数小时的挖掘,我找到了 2 个解决方案,不需要 AOP。
Liquibase 版本:4.1.1
解决方案 A
在customChange的官方例子中
https://docs.liquibase.com/change-types/community/custom-change.html
在CustomChange.setFileOpener中,ResourceAccessor其实是一个内在的classSpringLiquibase$SpringResourceOpener,它有一个成员'resourceLoader',确实是一个应用程序上下文。不幸的是,它是私人的,没有 getter 可用。
所以这里出现了一个丑陋的解决方案:使用反射来获取它并调用 getBean
方案B(更优雅)
在开始之前,让我们先了解一下有关 Liquibase 的一些基本事实。将 Liquibase 与 Spring Boot 集成的官方方法是使用:
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration$LiquibaseConfiguration
这是一个条件内部配置 bean,用于创建 SpringLiquibase 仅当 SpringLiquibase.class 丢失时
@Configuration
@ConditionalOnMissingBean(SpringLiquibase.class)
@EnableConfigurationProperties({ DataSourceProperties.class,
LiquibaseProperties.class })
@Import(LiquibaseJpaDependencyConfiguration.class)
public static class LiquibaseConfiguration {...}
因此我们可以通过添加 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;
}
}
在其中我们新建了一个 SpringLiquibase 的扩展 class: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 有一个对上述 ResourceLoader 的静态引用。在Spring Bootstartup时,ResourceLoaderAware接口定义的'setResourceLoader'会在InitializingBean接口定义的'afterPropertiesSet'之前自动调用,因此代码执行如下:
Spring Boot调用setResourceLoader,注入resourceLoader(applicationContext)到BeanAwareSpringLiquibase.
Spring 启动调用afterPropertiesSet,执行包括customChange在内的Liquibase更新,此时你已经完全访问applicationContext
PS:
请记住将您的 Liquibase 配置 bean 包路径添加到 @ComponentScan,否则它仍将使用 LiquibaseAutoConfiguration 而不是我们自己的 LiquibaseConfig。
在 'execute' 之前准备好您在 'setUp' 中需要的所有 bean。
我通过覆盖 Spring Liquibase 配置并在自定义任务上设置静态字段来完成此操作。在配置中设置字段确保在变更集运行之前设置它。
不可能对每个 bean 都执行此操作,因为某些 bean(如 JPA 存储库)依赖于 liquibase bean。 Liquibase 在初始化 SpringLiquibase bean 时运行变更日志,但此时整个 Spring 上下文并未完全加载。如果您尝试自动装配依赖于 liquibase 的 bean,您将在启动时遇到异常。
我还认为这种技术比静态公开整个应用程序上下文更安全。仅将需要的字段传递给任务,之后这些字段不可公开访问。
/**
Task that has a static member that will be set in the LiquibaseConfiguration class.
*/
public class MyCustomTask implements CustomTaskChange {
private static MyBean myBean;
public static void setMyBean(MyBean myBean) {
MyCustomTask.myBean = myBean;
}
@Override
public void execute(Database database) throws CustomChangeException {
try {
JdbcConnection jdbcConnection = (JdbcConnection) database.getConnection();
// do stuff using myBean
} catch (DatabaseException | SQLException e) {
throw new CustomChangeException(e);
}
}
}
/**
Extend SpringBoot Liquibase Auto-Configuration
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseConfiguration
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(SpringLiquibase.class)
@EnableConfigurationProperties({DataSourceProperties.class, LiquibaseProperties.class})
public static class MyLiquibaseConfiguration
extends LiquibaseAutoConfiguration.LiquibaseConfiguration {
/**
* Autowire myBean and set it on {@link MyCustomTask}.
*
* @param properties The {@link LiquibaseProperties} to configure Liquibase.
* @param myBean my bean.
*/
public MigrationLiquibaseConfiguration(LiquibaseProperties properties, MyBean myBean) {
super(properties);
MyCustomTask.setMyBean(myBean);
}
}
我需要做一些数据迁移,这太复杂了,无法在 liquibase 变更集中完成。我们使用 spring
这就是为什么我写了一个 class 实现 liquibase.change.custom.CustomTaskChange class 的原因。然后我从变更集中引用它。
至此一切都很好。
我的问题是: 是否可以从这样的 class 中访问其他 spring 个 bean?
当我尝试在此 class 中使用自动装配的 bean 时,它是空的,这让我觉得此时自动装配还没有完成?
我还在其他线程中读到,Liquibase bean 必须在所有其他 bean 之前初始化,对吗?
这是我写的 class 的片段:
@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();
}
...
我遇到异常,在调试时我可以看到 someBean 为空。
这是 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;
}
...
更多配置:
<?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>
这里是来自变更集的调用:
<?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>
您 changeset.xml 中引用的 类 不受 Spring 管理,因此像 DI 这样的酷东西将无法工作。
你可以做的是将 Spring bean 注入到非 Spring 对象中。看到这个答案:
我目前也在运行解决这个问题...经过数小时的挖掘,我找到了 2 个解决方案,不需要 AOP。
Liquibase 版本:4.1.1
解决方案 A
在customChange的官方例子中
https://docs.liquibase.com/change-types/community/custom-change.html
在CustomChange.setFileOpener中,ResourceAccessor其实是一个内在的classSpringLiquibase$SpringResourceOpener,它有一个成员'resourceLoader',确实是一个应用程序上下文。不幸的是,它是私人的,没有 getter 可用。
所以这里出现了一个丑陋的解决方案:使用反射来获取它并调用 getBean
方案B(更优雅)
在开始之前,让我们先了解一下有关 Liquibase 的一些基本事实。将 Liquibase 与 Spring Boot 集成的官方方法是使用:
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration$LiquibaseConfiguration
这是一个条件内部配置 bean,用于创建 SpringLiquibase 仅当 SpringLiquibase.class 丢失时
@Configuration
@ConditionalOnMissingBean(SpringLiquibase.class)
@EnableConfigurationProperties({ DataSourceProperties.class,
LiquibaseProperties.class })
@Import(LiquibaseJpaDependencyConfiguration.class)
public static class LiquibaseConfiguration {...}
因此我们可以通过添加 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;
}
}
在其中我们新建了一个 SpringLiquibase 的扩展 class: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 有一个对上述 ResourceLoader 的静态引用。在Spring Bootstartup时,ResourceLoaderAware接口定义的'setResourceLoader'会在InitializingBean接口定义的'afterPropertiesSet'之前自动调用,因此代码执行如下:
Spring Boot调用setResourceLoader,注入resourceLoader(applicationContext)到BeanAwareSpringLiquibase.
Spring 启动调用afterPropertiesSet,执行包括customChange在内的Liquibase更新,此时你已经完全访问applicationContext
PS:
请记住将您的 Liquibase 配置 bean 包路径添加到 @ComponentScan,否则它仍将使用 LiquibaseAutoConfiguration 而不是我们自己的 LiquibaseConfig。
在 'execute' 之前准备好您在 'setUp' 中需要的所有 bean。
我通过覆盖 Spring Liquibase 配置并在自定义任务上设置静态字段来完成此操作。在配置中设置字段确保在变更集运行之前设置它。
不可能对每个 bean 都执行此操作,因为某些 bean(如 JPA 存储库)依赖于 liquibase bean。 Liquibase 在初始化 SpringLiquibase bean 时运行变更日志,但此时整个 Spring 上下文并未完全加载。如果您尝试自动装配依赖于 liquibase 的 bean,您将在启动时遇到异常。
我还认为这种技术比静态公开整个应用程序上下文更安全。仅将需要的字段传递给任务,之后这些字段不可公开访问。
/**
Task that has a static member that will be set in the LiquibaseConfiguration class.
*/
public class MyCustomTask implements CustomTaskChange {
private static MyBean myBean;
public static void setMyBean(MyBean myBean) {
MyCustomTask.myBean = myBean;
}
@Override
public void execute(Database database) throws CustomChangeException {
try {
JdbcConnection jdbcConnection = (JdbcConnection) database.getConnection();
// do stuff using myBean
} catch (DatabaseException | SQLException e) {
throw new CustomChangeException(e);
}
}
}
/**
Extend SpringBoot Liquibase Auto-Configuration
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseConfiguration
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(SpringLiquibase.class)
@EnableConfigurationProperties({DataSourceProperties.class, LiquibaseProperties.class})
public static class MyLiquibaseConfiguration
extends LiquibaseAutoConfiguration.LiquibaseConfiguration {
/**
* Autowire myBean and set it on {@link MyCustomTask}.
*
* @param properties The {@link LiquibaseProperties} to configure Liquibase.
* @param myBean my bean.
*/
public MigrationLiquibaseConfiguration(LiquibaseProperties properties, MyBean myBean) {
super(properties);
MyCustomTask.setMyBean(myBean);
}
}