Refactor of Task API
This change removes the Spring Cloud Task dependency upon a CommandLineRunner from boot and moves the handling of the task lifecycle closer to the "edge" of a Spring Boot application. With this change, now a developer simply adds @EnableTask to their configuration somewhere and the task lifecycle will be recorded. Resolves spring-cloud/spring-cloud-task#39
This commit is contained in:
committed by
Glenn Renfro
parent
7be3a88826
commit
61d3cc3641
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation that identifies a class as a task. This annotation will serve as the
|
||||
* main “hook” to activate the various Spring Cloud Task features.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface Task {
|
||||
|
||||
/**
|
||||
* Establishes the name associated with the task. The default is empty.
|
||||
* @return returns name associated with the task or an empty string.
|
||||
*/
|
||||
public String value() default "";
|
||||
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Annotations for spring cloud task.
|
||||
*/
|
||||
package org.springframework.cloud.task.annotation;
|
||||
@@ -20,6 +20,8 @@ import javax.sql.DataSource;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.cloud.task.repository.TaskExplorer;
|
||||
import org.springframework.cloud.task.repository.TaskRepository;
|
||||
import org.springframework.cloud.task.repository.dao.JdbcTaskExecutionDao;
|
||||
@@ -29,6 +31,8 @@ import org.springframework.cloud.task.repository.support.JdbcTaskRepositoryFacto
|
||||
import org.springframework.cloud.task.repository.support.MapTaskExplorerFactoryBean;
|
||||
import org.springframework.cloud.task.repository.support.MapTaskRepositoryFactoryBean;
|
||||
import org.springframework.cloud.task.repository.support.SimpleTaskRepository;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
/**
|
||||
* Default implementation of the TaskConfigurer interface. If no {@link TaskConfigurer}
|
||||
@@ -52,6 +56,8 @@ public class DefaultTaskConfigurer implements TaskConfigurer {
|
||||
|
||||
private TaskExplorer taskExplorer;
|
||||
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
public DefaultTaskConfigurer(){
|
||||
initialize();
|
||||
}
|
||||
@@ -61,14 +67,21 @@ public class DefaultTaskConfigurer implements TaskConfigurer {
|
||||
initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskRepository getTaskRepository() {
|
||||
return taskRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskExplorer getTaskExplorer() {
|
||||
return taskExplorer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlatformTransactionManager getTransactionManager() {
|
||||
return this.transactionManager;
|
||||
}
|
||||
|
||||
private void initialize(){
|
||||
logger.debug("Initializing TaskRepository");
|
||||
if (dataSource == null) {
|
||||
@@ -78,6 +91,7 @@ public class DefaultTaskConfigurer implements TaskConfigurer {
|
||||
MapTaskExplorerFactoryBean mapTaskExplorerFactoryBean =
|
||||
new MapTaskExplorerFactoryBean();
|
||||
taskExplorer = mapTaskExplorerFactoryBean.getObject();
|
||||
transactionManager = new ResourcelessTransactionManager();
|
||||
|
||||
}
|
||||
else {
|
||||
@@ -87,6 +101,7 @@ public class DefaultTaskConfigurer implements TaskConfigurer {
|
||||
JdbcTaskExplorerFactoryBean jdbcTaskExplorerFactoryBean =
|
||||
new JdbcTaskExplorerFactoryBean(dataSource);
|
||||
taskExplorer = jdbcTaskExplorerFactoryBean.getObject();
|
||||
transactionManager = new DataSourceTransactionManager(dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.task.annotation;
|
||||
package org.springframework.cloud.task.configuration;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
@@ -24,16 +24,12 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.cloud.task.configuration.DefaultTaskConfigurer;
|
||||
import org.springframework.cloud.task.configuration.SimpleTaskConfiguration;
|
||||
import org.springframework.cloud.task.configuration.TaskConfigurer;
|
||||
import org.springframework.cloud.task.repository.TaskRepository;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Enable Spring Task features and provide a base configuration for setting up
|
||||
* {@link Task} objects in an @Configuration class.
|
||||
* Enable Spring Task features.
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Configuration
|
||||
@@ -23,15 +23,17 @@ import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.task.listener.TaskLifecycleListener;
|
||||
import org.springframework.cloud.task.repository.TaskRepository;
|
||||
import org.springframework.cloud.task.repository.support.TaskDatabaseInitializer;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
@@ -41,13 +43,12 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
@EnableTransactionManagement
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
public class SimpleTaskConfiguration {
|
||||
|
||||
protected static final Log logger = LogFactory.getLog(SimpleTaskConfiguration.class);
|
||||
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
|
||||
@@ -66,17 +67,23 @@ public class SimpleTaskConfiguration {
|
||||
|
||||
private TaskConfigurer configurer;
|
||||
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
public TaskHandler taskHandler() {
|
||||
return new TaskHandler();
|
||||
}
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
@Bean
|
||||
public TaskRepository taskRepository(){
|
||||
return taskRepository;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TaskLifecycleListener taskLifecycleListener() {
|
||||
return new TaskLifecycleListener(taskRepository());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager transactionManager() {
|
||||
return this.transactionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the basic components by extracting them from the {@link TaskConfigurer}, defaulting to some
|
||||
* sensible values as long as a unique DataSource is available.
|
||||
@@ -93,6 +100,7 @@ public class SimpleTaskConfiguration {
|
||||
logger.debug(String.format("Using %s TaskConfigurer",
|
||||
configurer.getClass().getName()));
|
||||
taskRepository = configurer.getTaskRepository();
|
||||
transactionManager = configurer.getTransactionManager();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
package org.springframework.cloud.task.configuration;
|
||||
|
||||
import org.springframework.cloud.task.repository.TaskExplorer;
|
||||
import org.springframework.cloud.task.repository.TaskRepository;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
/**
|
||||
* Provides a strategy interface for providing configuration
|
||||
@@ -33,4 +35,7 @@ public interface TaskConfigurer {
|
||||
*/
|
||||
public TaskRepository getTaskRepository();
|
||||
|
||||
public PlatformTransactionManager getTransactionManager();
|
||||
|
||||
public TaskExplorer getTaskExplorer();
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.task.configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.ExitCodeGenerator;
|
||||
import org.springframework.cloud.task.annotation.Task;
|
||||
import org.springframework.cloud.task.repository.TaskExecution;
|
||||
import org.springframework.cloud.task.repository.TaskRepository;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* Offers the advice on how to record tasks to the repository for both
|
||||
* {@link org.springframework.boot.ApplicationRunner} and
|
||||
* {@link org.springframework.boot.CommandLineRunner} spring boot applications.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
@Aspect
|
||||
public class TaskHandler {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private TaskRepository repository;
|
||||
|
||||
private String taskName;
|
||||
|
||||
private String executionId;
|
||||
|
||||
private TaskExecution taskExecution;
|
||||
|
||||
/**
|
||||
* Looks for any {@link CommandLineRunner}.run method with its class annotated with @Task
|
||||
* and calls the repository implementation to store the start of the task in the repo
|
||||
* before the run starts.
|
||||
*
|
||||
* @param joinPoint the point where the run method in a {@link CommandLineRunner} or
|
||||
* {@link ApplicationRunner} is executed
|
||||
*/
|
||||
@Before("within( @org.springframework.cloud.task.annotation.Task *) && (execution(* org.springframework.boot.CommandLineRunner.run(..)) || execution(* org.springframework.boot.ApplicationRunner.run(..)))")
|
||||
public void beforeCommandLineRunner(JoinPoint joinPoint) {
|
||||
executionId = UUID.randomUUID().toString();
|
||||
taskExecution = new TaskExecution();
|
||||
|
||||
Task a = joinPoint.getTarget().getClass().getAnnotation(Task.class);
|
||||
taskName = a.value();
|
||||
if (taskName == null || taskName.length() == 0) {
|
||||
taskName = joinPoint.getTarget().getClass().getName();
|
||||
}
|
||||
|
||||
taskExecution.setTaskName(taskName);
|
||||
taskExecution.setStartTime(new Date());
|
||||
taskExecution.setExecutionId(executionId);
|
||||
repository.createTaskExecution(taskExecution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for any {@link CommandLineRunner}.run method with its class annotated with @Task
|
||||
* and calls repository implementation to store the exit of the task in the repo after
|
||||
* run returns result.
|
||||
*
|
||||
* @param joinPoint the point where the run method in a {@link CommandLineRunner} or
|
||||
* {@link ApplicationRunner} is executed
|
||||
*/
|
||||
@AfterReturning("within( @org.springframework.cloud.task.annotation.Task *) && (execution(* org.springframework.boot.CommandLineRunner.run(..)) || execution(* org.springframework.boot.ApplicationRunner.run(..)))")
|
||||
public void afterReturnCommandLineRunner(JoinPoint joinPoint) {
|
||||
int result = 0;
|
||||
List<ExitCodeGenerator> generators = new ArrayList<ExitCodeGenerator>();
|
||||
generators
|
||||
.addAll(context.getBeansOfType(ExitCodeGenerator.class).values());
|
||||
for (ExitCodeGenerator generator : generators) {
|
||||
result = generator.getExitCode();
|
||||
}
|
||||
taskExecution.setEndTime(new Date());
|
||||
taskExecution.setExitCode(result);
|
||||
repository.update(taskExecution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for any {@link CommandLineRunner}.run method with its class annotated with @Task
|
||||
* and calls the repository implementation to store the exitCode of 1
|
||||
* for the task in the repo in the case of an exception.
|
||||
*
|
||||
* @param joinPoint the point where the run method in a {@link CommandLineRunner} or
|
||||
* {@link ApplicationRunner} is executed
|
||||
*/
|
||||
@AfterThrowing("within( @org.springframework.cloud.task.annotation.Task *) && (execution(* org.springframework.boot.CommandLineRunner.run(..)) || execution(* org.springframework.boot.ApplicationRunner.run(..)))")
|
||||
public void logExceptionCommandLineRunner(JoinPoint joinPoint) {
|
||||
taskExecution.setEndTime(new Date());
|
||||
taskExecution.setExitCode(1);
|
||||
repository.update(taskExecution);
|
||||
}
|
||||
|
||||
public TaskExecution getTaskExecution() {
|
||||
return taskExecution;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.task.listener;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.context.event.ApplicationFailedEvent;
|
||||
import org.springframework.cloud.task.repository.TaskExecution;
|
||||
import org.springframework.cloud.task.repository.TaskRepository;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextClosedEvent;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
|
||||
/**
|
||||
* Monitors the lifecycle of a task. This listener will record both the start and end of
|
||||
* a task in the registered {@link TaskRepository}.
|
||||
*
|
||||
* The following events are used to identify the start and end of a task:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link ContextRefreshedEvent} - Used to identify the start of a task. A task
|
||||
* is expected to contain a single application context.</li>
|
||||
* <li>{@link ContextClosedEvent} - Used to identify the successful end of a task.</li>
|
||||
* <li>{@link ApplicationFailedEvent} - Used to identify the failure of a task.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <b>NOTE:</b> Multiple contexts (including parent/child relationships) will result in
|
||||
* only the first context refresh that contains this instance being recorded.
|
||||
*
|
||||
* @author Michael Minella
|
||||
*/
|
||||
public class TaskLifecycleListener implements ApplicationListener<ApplicationEvent>,
|
||||
ApplicationContextAware {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(TaskLifecycleListener.class);
|
||||
|
||||
private final TaskRepository taskRepository;
|
||||
|
||||
private TaskExecution taskExecution;
|
||||
|
||||
private boolean started = false;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
/**
|
||||
* @param taskRepository The repository to record executions in.
|
||||
*/
|
||||
public TaskLifecycleListener(TaskRepository taskRepository) {
|
||||
this.taskRepository = taskRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utilizes {@link ApplicationEvent}s to determine the start, end, and failure of a
|
||||
* task. Specifically:
|
||||
* <ul>
|
||||
* <li>{@link ContextRefreshedEvent} - Start of a task</li>
|
||||
* <li>{@link ContextClosedEvent} - Successful end of a task</li>
|
||||
* <li>{@link ApplicationFailedEvent} - Failure of a task</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param applicationEvent The application being listened for.
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent applicationEvent) {
|
||||
if(applicationEvent instanceof ContextRefreshedEvent) {
|
||||
doTaskStart();
|
||||
started = true;
|
||||
}
|
||||
else if(applicationEvent instanceof ContextClosedEvent) {
|
||||
doTaskEnd();
|
||||
}
|
||||
else if(applicationEvent instanceof ApplicationFailedEvent) {
|
||||
doTaskFailed(((ApplicationFailedEvent) applicationEvent).getException());
|
||||
}
|
||||
}
|
||||
|
||||
private void doTaskFailed(Throwable exception) {
|
||||
|
||||
if(started) {
|
||||
this.taskExecution.setEndTime(new Date());
|
||||
//TODO: get exit code from new event thrown by Boot.
|
||||
this.taskExecution.setExitCode(1);
|
||||
this.taskExecution.setExitMessage(stackTraceToString(exception));
|
||||
|
||||
taskRepository.update(taskExecution);
|
||||
}
|
||||
else {
|
||||
logger.error("An event to fail a task has been received for a task that has " +
|
||||
"not yet started.");
|
||||
}
|
||||
}
|
||||
|
||||
private String stackTraceToString(Throwable exception) {
|
||||
StringWriter writer = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(writer);
|
||||
|
||||
exception.printStackTrace(printWriter);
|
||||
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
private void doTaskEnd() {
|
||||
if(started) {
|
||||
this.taskExecution.setEndTime(new Date());
|
||||
//TODO: get exit code from new event thrown by Boot.
|
||||
this.taskExecution.setExitCode(0);
|
||||
|
||||
taskRepository.update(taskExecution);
|
||||
}
|
||||
else {
|
||||
logger.error("An event to end a task has been received for a task that has " +
|
||||
"not yet started.");
|
||||
}
|
||||
}
|
||||
|
||||
private void doTaskStart() {
|
||||
|
||||
if(!started) {
|
||||
|
||||
String executionId = UUID.randomUUID().toString();
|
||||
this.taskExecution = new TaskExecution();
|
||||
|
||||
this.taskExecution.setTaskName(applicationContext.getId());
|
||||
this.taskExecution.setStartTime(new Date());
|
||||
this.taskExecution.setExecutionId(executionId);
|
||||
this.taskRepository.createTaskExecution(this.taskExecution);
|
||||
}
|
||||
else {
|
||||
logger.error("Multiple start events have been received. The first one was " +
|
||||
"recorded.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for testing purposes
|
||||
* @return the current {@link TaskExecution}
|
||||
*/
|
||||
TaskExecution getTaskExecution() {
|
||||
return this.taskExecution;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user