Update TaskLifecycleListener to use SmartLifecycle

This commit changes the starting point of a task from the point when the
ApplicationContext issues the ContextRefreshedEvent to
SmartLifecycle#start.  This is a more accurate point of start for a task
in that all beans should now be available.  It also allows us to clean
up many ApplicationContext hacks that were present to get around the
fact that many beans were not ready when a Task was attempting to begin.

Resolves spring-cloud/spring-cloud-task#107
This commit is contained in:
Michael Minella
2016-03-11 14:17:02 -06:00
committed by Glenn Renfro
parent 7c8fc5f50e
commit f35f8ef52d
17 changed files with 384 additions and 310 deletions

View File

@@ -15,14 +15,19 @@
*/
package org.springframework.cloud.task.batch.configuration;
import java.util.Collection;
import javax.sql.DataSource;
import org.springframework.batch.core.Job;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.task.batch.listener.TaskBatchExecutionListener;
import org.springframework.cloud.task.configuration.EnableTask;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.cloud.task.repository.TaskExplorer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
/**
* Provides auto configuration for the {@link TaskBatchExecutionListener}.
@@ -39,9 +44,24 @@ public class TaskBatchAutoConfiguration {
return new TaskBatchExecutionListenerBeanPostProcessor();
}
@Bean
@ConditionalOnMissingBean
public TaskBatchExecutionListenerFactoryBean taskBatchExecutionListener(ConfigurableApplicationContext context) {
return new TaskBatchExecutionListenerFactoryBean(context);
@Configuration
@ConditionalOnMissingBean(name = "taskBatchExecutionListener")
public static class TaskBatchExecutionListenerAutoconfiguration {
@Autowired(required = false)
private Collection<DataSource> dataSources;
@Bean
public TaskBatchExecutionListenerFactoryBean taskBatchExecutionListener(TaskExplorer taskExplorer) {
if(!CollectionUtils.isEmpty(dataSources) && dataSources.size() == 1) {
return new TaskBatchExecutionListenerFactoryBean(dataSources.iterator().next(), taskExplorer);
}
else if(CollectionUtils.isEmpty(dataSources)) {
return new TaskBatchExecutionListenerFactoryBean(null, taskExplorer);
}
else {
throw new IllegalStateException("Expected one datasource and found " + dataSources.size());
}
}
}
}

View File

@@ -27,10 +27,7 @@ import org.springframework.cloud.task.batch.listener.support.MapTaskBatchDao;
import org.springframework.cloud.task.repository.TaskExplorer;
import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao;
import org.springframework.cloud.task.repository.support.SimpleTaskExplorer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* {@link FactoryBean} for a {@link TaskBatchExecutionListener}. Provides a jdbc based
@@ -41,19 +38,15 @@ import org.springframework.util.StringUtils;
*/
public class TaskBatchExecutionListenerFactoryBean implements FactoryBean<TaskBatchExecutionListener> {
private ConfigurableApplicationContext context;
private TaskBatchExecutionListener listener;
private String dataSourceName;
private DataSource dataSource;
/**
* @param context the current application context
*/
public TaskBatchExecutionListenerFactoryBean(ConfigurableApplicationContext context) {
Assert.notNull(context, "A ConfigurableApplicationContext is required");
private TaskExplorer taskExplorer;
this.context = context;
public TaskBatchExecutionListenerFactoryBean(DataSource dataSource, TaskExplorer taskExplorer) {
this.dataSource = dataSource;
this.taskExplorer = taskExplorer;
}
@Override
@@ -61,24 +54,11 @@ public class TaskBatchExecutionListenerFactoryBean implements FactoryBean<TaskBa
if(listener != null){
return listener;
}
if(this.context.getBeanNamesForType(DataSource.class).length == 0) {
if(this.dataSource == null) {
this.listener = new TaskBatchExecutionListener(getMapTaskBatchDao());
}
else {
DataSource dataSource;
if(StringUtils.hasText(this.dataSourceName)) {
dataSource = (DataSource) this.context.getBean(this.dataSourceName);
}
else {
if(this.context.getBeanNamesForType(DataSource.class).length == 1) {
dataSource = this.context.getBean(DataSource.class);
}
else {
throw new IllegalStateException("Unable to determine what DataSource to use");
}
}
this.listener = new TaskBatchExecutionListener(new JdbcTaskBatchDao(dataSource));
this.listener = new TaskBatchExecutionListener(new JdbcTaskBatchDao(this.dataSource));
}
return listener;
@@ -94,27 +74,21 @@ public class TaskBatchExecutionListenerFactoryBean implements FactoryBean<TaskBa
return true;
}
public void setDataSourceName(String dataSourceName) {
this.dataSourceName = dataSourceName;
}
private MapTaskBatchDao getMapTaskBatchDao() throws Exception {
Field taskExecutionDaoField = ReflectionUtils.findField(SimpleTaskExplorer.class, "taskExecutionDao");
taskExecutionDaoField.setAccessible(true);
MapTaskExecutionDao taskExecutionDao;
TaskExplorer taskExplorer = this.context.getBean(TaskExplorer.class);
if(AopUtils.isJdkDynamicProxy(taskExplorer)) {
SimpleTaskExplorer dereferencedTaskRepository = (SimpleTaskExplorer) ((Advised) taskExplorer).getTargetSource().getTarget();
if(AopUtils.isJdkDynamicProxy(this.taskExplorer)) {
SimpleTaskExplorer dereferencedTaskRepository = (SimpleTaskExplorer) ((Advised) this.taskExplorer).getTargetSource().getTarget();
taskExecutionDao =
(MapTaskExecutionDao) ReflectionUtils.getField(taskExecutionDaoField, dereferencedTaskRepository);
}
else {
taskExecutionDao =
(MapTaskExecutionDao) ReflectionUtils.getField(taskExecutionDaoField, taskExplorer);
(MapTaskExecutionDao) ReflectionUtils.getField(taskExecutionDaoField, this.taskExplorer);
}
return new MapTaskBatchDao(taskExecutionDao.getBatchJobAssociations());

View File

@@ -18,11 +18,14 @@ package org.springframework.cloud.task.batch.listener;
import java.util.Iterator;
import java.util.Set;
import javax.sql.DataSource;
import org.junit.After;
import org.junit.Test;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
@@ -35,21 +38,28 @@ import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfigurati
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.cloud.task.batch.configuration.TaskBatchAutoConfiguration;
import org.springframework.cloud.task.batch.configuration.TaskBatchExecutionListenerFactoryBean;
import org.springframework.cloud.task.configuration.DefaultTaskConfigurer;
import org.springframework.cloud.task.configuration.EnableTask;
import org.springframework.cloud.task.configuration.TaskConfigurer;
import org.springframework.cloud.task.repository.TaskExecution;
import org.springframework.cloud.task.repository.TaskExplorer;
import org.springframework.cloud.task.repository.support.TaskRepositoryInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import static org.junit.Assert.assertEquals;
/**
* @author Michael Minella
*/
public class BatchTaskExecutionListenerTests {
public class TaskBatchExecutionListenerTests {
private ConfigurableApplicationContext applicationContext;
@@ -62,7 +72,29 @@ public class BatchTaskExecutionListenerTests {
@Test
public void testAutobuiltDataSource() {
this.applicationContext = SpringApplication.run(new Object[] {JobConfiguration.class, PropertyPlaceholderAutoConfiguration.class, EmbeddedDataSourceConfiguration.class, TaskBatchAutoConfiguration.class, BatchAutoConfiguration.class, TaskBatchAutoConfiguration.class}, new String[0]);
this.applicationContext = SpringApplication.run(new Object[] {JobConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
EmbeddedDataSourceConfiguration.class,
BatchAutoConfiguration.class,
TaskBatchAutoConfiguration.class}, new String[0]);
TaskExplorer taskExplorer = this.applicationContext.getBean(TaskExplorer.class);
Page<TaskExecution> page = taskExplorer.findTaskExecutionsByName("application", new PageRequest(0, 1));
Set<Long> jobExecutionIds = taskExplorer.getJobExecutionIdsByTaskExecutionId(page.iterator().next().getExecutionId());
assertEquals(1, jobExecutionIds.size());
assertEquals(1, taskExplorer.getTaskExecution(jobExecutionIds.iterator().next()).getExecutionId());
}
@Test
public void testMultipleDataSources() {
this.applicationContext = SpringApplication.run(new Object[] {JobConfigurationMultipleDataSources.class,
PropertyPlaceholderAutoConfiguration.class,
EmbeddedDataSourceConfiguration.class,
BatchAutoConfiguration.class,
TaskBatchAutoConfiguration.class}, new String[0]);
TaskExplorer taskExplorer = this.applicationContext.getBean(TaskExplorer.class);
@@ -76,7 +108,11 @@ public class BatchTaskExecutionListenerTests {
@Test
public void testAutobuiltDataSourceNoJob() {
this.applicationContext = SpringApplication.run(new Object[] {NoJobConfiguration.class, PropertyPlaceholderAutoConfiguration.class, EmbeddedDataSourceConfiguration.class, TaskBatchAutoConfiguration.class, BatchAutoConfiguration.class, TaskBatchAutoConfiguration.class}, new String[0]);
this.applicationContext = SpringApplication.run(new Object[] {NoJobConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
EmbeddedDataSourceConfiguration.class,
BatchAutoConfiguration.class,
TaskBatchAutoConfiguration.class}, new String[0]);
TaskExplorer taskExplorer = this.applicationContext.getBean(TaskExplorer.class);
@@ -89,7 +125,10 @@ public class BatchTaskExecutionListenerTests {
@Test
public void testMapBased() {
this.applicationContext = SpringApplication.run(new Object[] {JobConfiguration.class, PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class, TaskBatchAutoConfiguration.class}, new String[0]);
this.applicationContext = SpringApplication.run(new Object[] {JobConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
BatchAutoConfiguration.class,
TaskBatchAutoConfiguration.class}, new String[0]);
TaskExplorer taskExplorer = this.applicationContext.getBean(TaskExplorer.class);
@@ -103,7 +142,10 @@ public class BatchTaskExecutionListenerTests {
@Test
public void testMultipleJobs() {
this.applicationContext = SpringApplication.run(new Object[] {MultipleJobConfiguration.class, PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class, TaskBatchAutoConfiguration.class}, new String[0]);
this.applicationContext = SpringApplication.run(new Object[] {MultipleJobConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
BatchAutoConfiguration.class,
TaskBatchAutoConfiguration.class}, new String[0]);
TaskExplorer taskExplorer = this.applicationContext.getBean(TaskExplorer.class);
@@ -149,6 +191,72 @@ public class BatchTaskExecutionListenerTests {
}
}
@Configuration
@EnableBatchProcessing
@EnableTask
public static class JobConfigurationMultipleDataSources {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Job job() {
return jobBuilderFactory.get("job")
.start(stepBuilderFactory.get("step1").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("Executed");
return RepeatStatus.FINISHED;
}
}).build())
.build();
}
@Bean
@Primary
public DataSource myDataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.setName("myDataSource");
return builder.build();
}
@Bean
public DataSource incorrectDataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.setName("incorrectDataSource");
return builder.build();
}
@Bean
public TaskBatchExecutionListenerFactoryBean taskBatchExecutionListener(TaskExplorer taskExplorer) {
return new TaskBatchExecutionListenerFactoryBean(myDataSource(), taskExplorer);
}
@Bean
public TaskConfigurer taskConfigurer() {
return new DefaultTaskConfigurer(myDataSource());
}
@Bean
public TaskRepositoryInitializer taskRepositoryInitializer() {
TaskRepositoryInitializer taskRepositoryInitializer = new TaskRepositoryInitializer();
taskRepositoryInitializer.setDataSource(myDataSource());
return taskRepositoryInitializer;
}
@Bean
public DefaultBatchConfigurer batchConfigurer() {
return new DefaultBatchConfigurer(myDataSource());
}
}
@Configuration
@EnableBatchProcessing
@EnableTask