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:
committed by
Glenn Renfro
parent
7c8fc5f50e
commit
f35f8ef52d
@@ -26,7 +26,6 @@ import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao;
|
||||
import org.springframework.cloud.task.repository.support.SimpleTaskExplorer;
|
||||
import org.springframework.cloud.task.repository.support.SimpleTaskRepository;
|
||||
import org.springframework.cloud.task.repository.support.TaskExecutionDaoFactoryBean;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
@@ -51,14 +50,25 @@ public class DefaultTaskConfigurer implements TaskConfigurer {
|
||||
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
private TaskExecutionDaoFactoryBean taskExecutionDaoFactoryBean;
|
||||
|
||||
public DefaultTaskConfigurer(ConfigurableApplicationContext context) {
|
||||
this.context = context;
|
||||
private DataSource dataSource;
|
||||
|
||||
/**
|
||||
* @param dataSource references the {@link DataSource} to be used as the Task
|
||||
* repository. If none is provided, a Map will be used (not recommended for
|
||||
* production use.
|
||||
*/
|
||||
public DefaultTaskConfigurer(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
|
||||
if(this.dataSource != null) {
|
||||
this.taskExecutionDaoFactoryBean = new TaskExecutionDaoFactoryBean(this.dataSource);
|
||||
}
|
||||
else {
|
||||
this.taskExecutionDaoFactoryBean = new TaskExecutionDaoFactoryBean();
|
||||
}
|
||||
|
||||
this.taskExecutionDaoFactoryBean = new TaskExecutionDaoFactoryBean(this.context);
|
||||
this.taskRepository = new SimpleTaskRepository(this.taskExecutionDaoFactoryBean);
|
||||
this.taskExplorer = new SimpleTaskExplorer(this.taskExecutionDaoFactoryBean);
|
||||
}
|
||||
@@ -77,7 +87,7 @@ public class DefaultTaskConfigurer implements TaskConfigurer {
|
||||
public PlatformTransactionManager getTransactionManager() {
|
||||
if(this.transactionManager == null) {
|
||||
if(isDataSourceAvailable()) {
|
||||
this.transactionManager = new DataSourceTransactionManager(this.context.getBean(DataSource.class));
|
||||
this.transactionManager = new DataSourceTransactionManager(this.dataSource);
|
||||
}
|
||||
else {
|
||||
this.transactionManager = new ResourcelessTransactionManager();
|
||||
@@ -88,6 +98,6 @@ public class DefaultTaskConfigurer implements TaskConfigurer {
|
||||
}
|
||||
|
||||
private boolean isDataSourceAvailable() {
|
||||
return this.context.getBeanNamesForType(DataSource.class).length == 1;
|
||||
return this.dataSource != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.cloud.task.configuration;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -27,8 +26,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.cloud.task.listener.TaskLifecycleListener;
|
||||
import org.springframework.cloud.task.listener.annotation.TaskListenerExecutor;
|
||||
import org.springframework.cloud.task.listener.annotation.TaskListenerExecutorFactory;
|
||||
import org.springframework.cloud.task.listener.annotation.TaskListenerExecutorFactoryBean;
|
||||
import org.springframework.cloud.task.repository.TaskExplorer;
|
||||
import org.springframework.cloud.task.repository.TaskNameResolver;
|
||||
import org.springframework.cloud.task.repository.TaskRepository;
|
||||
@@ -39,6 +37,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Base {@code Configuration} class providing common structure for enabling and using
|
||||
@@ -54,6 +53,9 @@ public class SimpleTaskConfiguration {
|
||||
|
||||
protected static final Log logger = LogFactory.getLog(SimpleTaskConfiguration.class);
|
||||
|
||||
@Autowired(required = false)
|
||||
private Collection<DataSource> dataSources;
|
||||
|
||||
@Autowired
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@@ -62,34 +64,40 @@ public class SimpleTaskConfiguration {
|
||||
|
||||
private boolean initialized = false;
|
||||
|
||||
private TaskConfigurer configurer;
|
||||
private TaskRepository taskRepository;
|
||||
|
||||
private TaskLifecycleListener taskLifecycleListener;
|
||||
|
||||
private TaskListenerExecutorFactoryBean taskListenerExecutorFactoryBean;
|
||||
|
||||
private PlatformTransactionManager platformTransactionManager;
|
||||
|
||||
private TaskExplorer taskExplorer;
|
||||
|
||||
@Bean
|
||||
public TaskRepository taskRepository(){
|
||||
return this.configurer.getTaskRepository();
|
||||
return this.taskRepository;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TaskLifecycleListener taskLifecycleListener() {
|
||||
return new TaskLifecycleListener(taskRepository(), taskNameResolver(), this.applicationArguments);
|
||||
return this.taskLifecycleListener;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TaskListenerExecutor taskListenerExecutor(ConfigurableApplicationContext context) throws Exception
|
||||
{
|
||||
TaskListenerExecutorFactory taskListenerExecutorFactory =
|
||||
new TaskListenerExecutorFactory(context);
|
||||
return taskListenerExecutorFactory.getObject();
|
||||
public TaskListenerExecutorFactoryBean taskListenerExecutor()
|
||||
throws Exception {
|
||||
return this.taskListenerExecutorFactoryBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager transactionManager() {
|
||||
return this.configurer.getTransactionManager();
|
||||
return this.platformTransactionManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TaskExplorer taskExplorer() {
|
||||
return this.configurer.getTaskExplorer();
|
||||
return this.taskExplorer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -101,8 +109,9 @@ public class SimpleTaskConfiguration {
|
||||
public TaskRepositoryInitializer taskRepositoryInitializer() {
|
||||
TaskRepositoryInitializer taskRepositoryInitializer = new TaskRepositoryInitializer();
|
||||
|
||||
if(this.context.getBeanNamesForType(DataSource.class).length == 1) {
|
||||
taskRepositoryInitializer.setDataSource(context.getBean(DataSource.class));
|
||||
if(!CollectionUtils.isEmpty(this.dataSources) && this.dataSources.size() == 1) {
|
||||
DataSource next = this.dataSources.iterator().next();
|
||||
taskRepositoryInitializer.setDataSource(next);
|
||||
}
|
||||
|
||||
return taskRepositoryInitializer;
|
||||
@@ -112,40 +121,56 @@ public class SimpleTaskConfiguration {
|
||||
* Determines the {@link TaskConfigurer} to use.
|
||||
*/
|
||||
@PostConstruct
|
||||
protected void initialize() {
|
||||
protected void initialize() throws Exception {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
logger.debug("Getting Task Configurer");
|
||||
if (configurer == null) {
|
||||
configurer = getDefaultConfigurer(context.getBeansOfType(TaskConfigurer.class).values());
|
||||
}
|
||||
|
||||
TaskConfigurer taskConfigurer = getDefaultConfigurer();
|
||||
|
||||
logger.debug(String.format("Using %s TaskConfigurer",
|
||||
configurer.getClass().getName()));
|
||||
taskConfigurer.getClass().getName()));
|
||||
|
||||
this.taskRepository = taskConfigurer.getTaskRepository();
|
||||
this.taskListenerExecutorFactoryBean = new TaskListenerExecutorFactoryBean(context);
|
||||
this.platformTransactionManager = taskConfigurer.getTransactionManager();
|
||||
this.taskExplorer = taskConfigurer.getTaskExplorer();
|
||||
|
||||
this.taskLifecycleListener = new TaskLifecycleListener(this.taskRepository, taskNameResolver(), this.applicationArguments);
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private TaskConfigurer getDefaultConfigurer(Collection<TaskConfigurer> configurers) {
|
||||
verifyEnvironment(configurers);
|
||||
if (configurers == null || configurers.isEmpty()) {
|
||||
this.configurer = new DefaultTaskConfigurer(this.context);
|
||||
return this.configurer;
|
||||
private TaskConfigurer getDefaultConfigurer() {
|
||||
verifyEnvironment();
|
||||
|
||||
int configurers = this.context.getBeanNamesForType(TaskConfigurer.class).length;
|
||||
|
||||
if (configurers < 1) {
|
||||
if(!CollectionUtils.isEmpty(this.dataSources) && this.dataSources.size() == 1) {
|
||||
return new DefaultTaskConfigurer(this.dataSources.iterator().next());
|
||||
}
|
||||
else {
|
||||
return new DefaultTaskConfigurer(null);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(configurers == 1) {
|
||||
return this.context.getBean(TaskConfigurer.class);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Expected one TaskConfigurer but found " + configurers);
|
||||
}
|
||||
}
|
||||
this.configurer = configurers.iterator().next();
|
||||
return this.configurer;
|
||||
}
|
||||
|
||||
private void verifyEnvironment(Collection configurers){
|
||||
private void verifyEnvironment(){
|
||||
int configurers = this.context.getBeanNamesForType(TaskConfigurer.class).length;
|
||||
int dataSources = this.context.getBeanNamesForType(DataSource.class).length;
|
||||
|
||||
if (dataSources > 1) {
|
||||
if(configurers == 0 && dataSources > 1) {
|
||||
throw new IllegalStateException("To use the default TaskConfigurer the context must contain no more than" +
|
||||
"one DataSource, found " + dataSources);
|
||||
}
|
||||
if (configurers.size() > 1) {
|
||||
throw new IllegalStateException(
|
||||
"To use a custom TaskConfigurer the context must contain precisely one, found "
|
||||
+ configurers.size());
|
||||
" one DataSource, found " + dataSources);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -36,6 +36,7 @@ import org.springframework.cloud.task.repository.TaskNameResolver;
|
||||
import org.springframework.cloud.task.repository.TaskRepository;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.SmartLifecycle;
|
||||
import org.springframework.context.event.ContextClosedEvent;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -58,7 +59,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Michael Minella
|
||||
*/
|
||||
public class TaskLifecycleListener implements ApplicationListener<ApplicationEvent>{
|
||||
public class TaskLifecycleListener implements ApplicationListener<ApplicationEvent>, SmartLifecycle {
|
||||
|
||||
@Autowired(required = false)
|
||||
private Collection<TaskExecutionListener> taskExecutionListeners;
|
||||
@@ -106,11 +107,7 @@ public class TaskLifecycleListener implements ApplicationListener<ApplicationEve
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent applicationEvent) {
|
||||
if(applicationEvent instanceof ContextRefreshedEvent) {
|
||||
doTaskStart();
|
||||
started = true;
|
||||
}
|
||||
else if(applicationEvent instanceof ApplicationFailedEvent) {
|
||||
if(applicationEvent instanceof ApplicationFailedEvent) {
|
||||
this.applicationFailedEvent = (ApplicationFailedEvent) applicationEvent;
|
||||
}
|
||||
else if(applicationEvent instanceof ExitCodeEvent){
|
||||
@@ -221,4 +218,37 @@ public class TaskLifecycleListener implements ApplicationListener<ApplicationEve
|
||||
endTime,taskExecution.getExitMessage(),
|
||||
Collections.unmodifiableList(taskExecution.getParameters()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutoStartup() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(Runnable callback) {
|
||||
Assert.notNull(callback, "A callback is required");
|
||||
|
||||
callback.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
doTaskStart();
|
||||
this.started = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return this.started;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPhase() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.springframework.aop.scope.ScopedProxyUtils;
|
||||
import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.cloud.task.listener.TaskExecutionListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.MethodIntrospector;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
@@ -40,7 +41,7 @@ import org.springframework.core.annotation.AnnotationUtils;
|
||||
/**
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public class TaskListenerExecutorFactory implements FactoryBean<TaskListenerExecutor> {
|
||||
public class TaskListenerExecutorFactoryBean implements FactoryBean<TaskExecutionListener> {
|
||||
|
||||
private final static Log logger = LogFactory.getLog(TaskListenerExecutor.class);
|
||||
|
||||
@@ -55,7 +56,7 @@ public class TaskListenerExecutorFactory implements FactoryBean<TaskListenerExec
|
||||
|
||||
private Map<Method, Object> failedTaskInstances;
|
||||
|
||||
public TaskListenerExecutorFactory(ConfigurableApplicationContext context){
|
||||
public TaskListenerExecutorFactoryBean(ConfigurableApplicationContext context){
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@@ -161,5 +162,4 @@ public class TaskListenerExecutorFactory implements FactoryBean<TaskListenerExec
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -76,7 +76,7 @@ public class SimpleTaskRepository implements TaskRepository {
|
||||
validateCreateInformation(startTime, taskName);
|
||||
TaskExecution taskExecution =
|
||||
taskExecutionDao.createTaskExecution(taskName, startTime, parameters);
|
||||
logger.info("Creating: " + taskExecution.toString());
|
||||
logger.debug("Creating: " + taskExecution.toString());
|
||||
return taskExecution;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,8 @@ import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.cloud.task.repository.dao.JdbcTaskExecutionDao;
|
||||
import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao;
|
||||
import org.springframework.cloud.task.repository.dao.TaskExecutionDao;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.jdbc.support.MetaDataAccessException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link FactoryBean} implementation that creates the appropriate
|
||||
@@ -38,12 +36,10 @@ public class TaskExecutionDaoFactoryBean implements FactoryBean<TaskExecutionDao
|
||||
|
||||
public static final String DEFAULT_TABLE_PREFIX = "TASK_";
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
private DataSource dataSource;
|
||||
|
||||
private TaskExecutionDao dao = null;
|
||||
|
||||
private String dataSourceName;
|
||||
|
||||
private String tablePrefix = DEFAULT_TABLE_PREFIX;
|
||||
|
||||
/**
|
||||
@@ -54,37 +50,21 @@ public class TaskExecutionDaoFactoryBean implements FactoryBean<TaskExecutionDao
|
||||
}
|
||||
|
||||
/**
|
||||
* ApplicationContext provided will be used to obtain the appropriate
|
||||
* {@link DataSource}.
|
||||
* {@link DataSource} to be used.
|
||||
*
|
||||
* @param context context for this application
|
||||
* @param dataSource {@link DataSource} to be used.
|
||||
*/
|
||||
public TaskExecutionDaoFactoryBean(ConfigurableApplicationContext context) {
|
||||
Assert.notNull(context, "An ApplicationContext is required");
|
||||
public TaskExecutionDaoFactoryBean(DataSource dataSource) {
|
||||
Assert.notNull(dataSource, "A DataSource is required");
|
||||
|
||||
this.context = context;
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskExecutionDao getObject() throws Exception {
|
||||
if(this.dao == null) {
|
||||
if(this.context != null) {
|
||||
if (StringUtils.hasText(this.dataSourceName)) {
|
||||
if(!this.context.containsBean(this.dataSourceName)) {
|
||||
throw new IllegalArgumentException("The configured dataSourceName is not available in the current context");
|
||||
}
|
||||
|
||||
DataSource dataSource = (DataSource) this.context.getBean(this.dataSourceName);
|
||||
buildTaskExecutionDao(dataSource);
|
||||
}
|
||||
else if (this.context.getBeanNamesForType(DataSource.class).length == 1) {
|
||||
DataSource dataSource = this.context.getBean(DataSource.class);
|
||||
buildTaskExecutionDao(dataSource);
|
||||
|
||||
}
|
||||
else {
|
||||
this.dao = new MapTaskExecutionDao();
|
||||
}
|
||||
if (this.dataSource != null) {
|
||||
buildTaskExecutionDao(this.dataSource);
|
||||
}
|
||||
else {
|
||||
this.dao = new MapTaskExecutionDao();
|
||||
@@ -104,17 +84,6 @@ public class TaskExecutionDaoFactoryBean implements FactoryBean<TaskExecutionDao
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the {@link DataSource} to be used if one is to be used. By default, the
|
||||
* name is not specified and it is assumed that only one DataSource exists within the
|
||||
* context.
|
||||
*
|
||||
* @param dataSourceName bean id for the DataSource to be used.
|
||||
*/
|
||||
public void setDataSourceName(String dataSourceName) {
|
||||
this.dataSourceName = dataSourceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates a prefix for all of the task repository's tables if the jdbc option is
|
||||
* used.
|
||||
|
||||
Reference in New Issue
Block a user