Support multiple task listener beans of same type
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2019 the original author or authors.
|
||||
* Copyright 2018-2021 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.
|
||||
@@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -45,6 +46,7 @@ import org.springframework.core.annotation.AnnotationUtils;
|
||||
* Initializes TaskListenerExecutor for a task.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @author Isik Erhan
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public class TaskListenerExecutorObjectFactory
|
||||
@@ -57,11 +59,11 @@ public class TaskListenerExecutorObjectFactory
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
private Map<Method, Object> beforeTaskInstances;
|
||||
private Map<Method, Set<Object>> beforeTaskInstances;
|
||||
|
||||
private Map<Method, Object> afterTaskInstances;
|
||||
private Map<Method, Set<Object>> afterTaskInstances;
|
||||
|
||||
private Map<Method, Object> failedTaskInstances;
|
||||
private Map<Method, Set<Object>> failedTaskInstances;
|
||||
|
||||
public TaskListenerExecutorObjectFactory(ConfigurableApplicationContext context) {
|
||||
this.context = context;
|
||||
@@ -141,20 +143,23 @@ public class TaskListenerExecutorObjectFactory
|
||||
}
|
||||
if (!beforeTaskMethods.isEmpty()) {
|
||||
for (Method beforeTaskMethod : beforeTaskMethods.keySet()) {
|
||||
this.beforeTaskInstances.put(beforeTaskMethod,
|
||||
this.context.getBean(beanName));
|
||||
this.beforeTaskInstances
|
||||
.computeIfAbsent(beforeTaskMethod, k -> new LinkedHashSet<>())
|
||||
.add(this.context.getBean(beanName));
|
||||
}
|
||||
}
|
||||
if (!afterTaskMethods.isEmpty()) {
|
||||
for (Method afterTaskMethod : afterTaskMethods.keySet()) {
|
||||
this.afterTaskInstances.put(afterTaskMethod,
|
||||
this.context.getBean(beanName));
|
||||
this.afterTaskInstances
|
||||
.computeIfAbsent(afterTaskMethod, k -> new LinkedHashSet<>())
|
||||
.add(this.context.getBean(beanName));
|
||||
}
|
||||
}
|
||||
if (!failedTaskMethods.isEmpty()) {
|
||||
for (Method failedTaskMethod : failedTaskMethods.keySet()) {
|
||||
this.failedTaskInstances.put(failedTaskMethod,
|
||||
this.context.getBean(beanName));
|
||||
this.failedTaskInstances
|
||||
.computeIfAbsent(failedTaskMethod, k -> new LinkedHashSet<>())
|
||||
.add(this.context.getBean(beanName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
* Copyright 2016-2021 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.
|
||||
@@ -31,18 +31,19 @@ import org.springframework.cloud.task.repository.TaskExecution;
|
||||
* appropriate time.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @author Isik Erhan
|
||||
*/
|
||||
public class TaskListenerExecutor implements TaskExecutionListener {
|
||||
|
||||
private Map<Method, Object> beforeTaskInstances;
|
||||
private Map<Method, Set<Object>> beforeTaskInstances;
|
||||
|
||||
private Map<Method, Object> afterTaskInstances;
|
||||
private Map<Method, Set<Object>> afterTaskInstances;
|
||||
|
||||
private Map<Method, Object> failedTaskInstances;
|
||||
private Map<Method, Set<Object>> failedTaskInstances;
|
||||
|
||||
public TaskListenerExecutor(Map<Method, Object> beforeTaskInstances,
|
||||
Map<Method, Object> afterTaskInstances,
|
||||
Map<Method, Object> failedTaskInstances) {
|
||||
public TaskListenerExecutor(Map<Method, Set<Object>> beforeTaskInstances,
|
||||
Map<Method, Set<Object>> afterTaskInstances,
|
||||
Map<Method, Set<Object>> failedTaskInstances) {
|
||||
|
||||
this.beforeTaskInstances = beforeTaskInstances;
|
||||
this.afterTaskInstances = afterTaskInstances;
|
||||
@@ -81,48 +82,55 @@ public class TaskListenerExecutor implements TaskExecutionListener {
|
||||
}
|
||||
|
||||
private void executeTaskListener(TaskExecution taskExecution, Set<Method> methods,
|
||||
Map<Method, Object> instances) {
|
||||
Map<Method, Set<Object>> instances) {
|
||||
for (Method method : methods) {
|
||||
try {
|
||||
method.invoke(instances.get(method), taskExecution);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
throw new TaskExecutionException(
|
||||
"@BeforeTask and @AfterTask annotated methods must be public.",
|
||||
e);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw new TaskExecutionException(String.format(
|
||||
"Failed to process @BeforeTask or @AfterTask"
|
||||
+ " annotation because: %s",
|
||||
e.getTargetException().getMessage()), e);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new TaskExecutionException("taskExecution parameter "
|
||||
+ "is required for @BeforeTask and @AfterTask annotated methods",
|
||||
e);
|
||||
for (Object instance : instances.get(method)) {
|
||||
try {
|
||||
method.invoke(instance, taskExecution);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
throw new TaskExecutionException(
|
||||
"@BeforeTask and @AfterTask annotated methods must be public.",
|
||||
e);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw new TaskExecutionException(String.format(
|
||||
"Failed to process @BeforeTask or @AfterTask"
|
||||
+ " annotation because: %s",
|
||||
e.getTargetException().getMessage()), e);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new TaskExecutionException("taskExecution parameter "
|
||||
+ "is required for @BeforeTask and @AfterTask annotated methods",
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void executeTaskListenerWithThrowable(TaskExecution taskExecution,
|
||||
Throwable throwable, Set<Method> methods, Map<Method, Object> instances) {
|
||||
Throwable throwable, Set<Method> methods,
|
||||
Map<Method, Set<Object>> instances) {
|
||||
for (Method method : methods) {
|
||||
try {
|
||||
method.invoke(instances.get(method), taskExecution, throwable);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
throw new TaskExecutionException(
|
||||
"@FailedTask annotated methods must be public.", e);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw new TaskExecutionException(String.format(
|
||||
"Failed to process @FailedTask " + "annotation because: %s",
|
||||
e.getTargetException().getMessage()), e);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new TaskExecutionException("taskExecution and throwable parameters "
|
||||
+ "are required for @FailedTask annotated methods", e);
|
||||
for (Object instance : instances.get(method)) {
|
||||
try {
|
||||
method.invoke(instance, taskExecution, throwable);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
throw new TaskExecutionException(
|
||||
"@FailedTask annotated methods must be public.", e);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw new TaskExecutionException(String.format(
|
||||
"Failed to process @FailedTask " + "annotation because: %s",
|
||||
e.getTargetException().getMessage()), e);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new TaskExecutionException(
|
||||
"taskExecution and throwable parameters "
|
||||
+ "are required for @FailedTask annotated methods",
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2019 the original author or authors.
|
||||
* Copyright 2018-2021 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.
|
||||
@@ -19,11 +19,10 @@ package org.springframework.cloud.task.listener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.task.listener.annotation.AfterTask;
|
||||
import org.springframework.cloud.task.listener.annotation.BeforeTask;
|
||||
import org.springframework.cloud.task.listener.annotation.FailedTask;
|
||||
@@ -43,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* {@link TaskListenerExecutor}.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
* @author Isik Erhan
|
||||
* @since 2.1.0
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@@ -71,55 +71,161 @@ public class TaskListenerExecutorObjectFactoryTests {
|
||||
*/
|
||||
public static List<TaskExecution> taskExecutionListenerResults = new ArrayList<>(3);
|
||||
|
||||
@Autowired
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
private TaskListenerExecutor taskListenerExecutor;
|
||||
|
||||
private TaskListenerExecutorObjectFactory taskListenerExecutorObjectFactory;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
public void setup(ConfigurableApplicationContext context) {
|
||||
taskExecutionListenerResults.clear();
|
||||
this.taskListenerExecutorObjectFactory = new TaskListenerExecutorObjectFactory(
|
||||
this.context);
|
||||
context);
|
||||
this.taskListenerExecutor = this.taskListenerExecutorObjectFactory.getObject();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyTaskStartupListener() {
|
||||
this.taskListenerExecutor
|
||||
.onTaskStartup(createSampleTaskExecution(BEFORE_LISTENER));
|
||||
validateSingleEntry(BEFORE_LISTENER);
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(TaskExecutionListenerConfiguration.class);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
setup(context);
|
||||
|
||||
this.taskListenerExecutor
|
||||
.onTaskStartup(createSampleTaskExecution(BEFORE_LISTENER));
|
||||
validateSingleEntry(BEFORE_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyTaskFailedListener() {
|
||||
this.taskListenerExecutor.onTaskFailed(createSampleTaskExecution(FAIL_LISTENER),
|
||||
new IllegalStateException("oops"));
|
||||
validateSingleEntry(FAIL_LISTENER);
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(TaskExecutionListenerConfiguration.class);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
setup(context);
|
||||
|
||||
this.taskListenerExecutor.onTaskFailed(
|
||||
createSampleTaskExecution(FAIL_LISTENER),
|
||||
new IllegalStateException("oops"));
|
||||
validateSingleEntry(FAIL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyTaskEndListener() {
|
||||
this.taskListenerExecutor.onTaskEnd(createSampleTaskExecution(AFTER_LISTENER));
|
||||
validateSingleEntry(AFTER_LISTENER);
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(TaskExecutionListenerConfiguration.class);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
setup(context);
|
||||
|
||||
this.taskListenerExecutor
|
||||
.onTaskEnd(createSampleTaskExecution(AFTER_LISTENER));
|
||||
validateSingleEntry(AFTER_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyAllListener() {
|
||||
this.taskListenerExecutor
|
||||
.onTaskStartup(createSampleTaskExecution(BEFORE_LISTENER));
|
||||
this.taskListenerExecutor.onTaskFailed(createSampleTaskExecution(FAIL_LISTENER),
|
||||
new IllegalStateException("oops"));
|
||||
this.taskListenerExecutor.onTaskEnd(createSampleTaskExecution(AFTER_LISTENER));
|
||||
assertThat(taskExecutionListenerResults.size()).isEqualTo(3);
|
||||
assertThat(taskExecutionListenerResults.get(0).getTaskName())
|
||||
.isEqualTo(BEFORE_LISTENER);
|
||||
assertThat(taskExecutionListenerResults.get(1).getTaskName())
|
||||
.isEqualTo(FAIL_LISTENER);
|
||||
assertThat(taskExecutionListenerResults.get(2).getTaskName())
|
||||
.isEqualTo(AFTER_LISTENER);
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(TaskExecutionListenerConfiguration.class);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
setup(context);
|
||||
|
||||
this.taskListenerExecutor
|
||||
.onTaskStartup(createSampleTaskExecution(BEFORE_LISTENER));
|
||||
this.taskListenerExecutor.onTaskFailed(
|
||||
createSampleTaskExecution(FAIL_LISTENER),
|
||||
new IllegalStateException("oops"));
|
||||
this.taskListenerExecutor
|
||||
.onTaskEnd(createSampleTaskExecution(AFTER_LISTENER));
|
||||
assertThat(taskExecutionListenerResults.size()).isEqualTo(3);
|
||||
assertThat(taskExecutionListenerResults.get(0).getTaskName())
|
||||
.isEqualTo(BEFORE_LISTENER);
|
||||
assertThat(taskExecutionListenerResults.get(1).getTaskName())
|
||||
.isEqualTo(FAIL_LISTENER);
|
||||
assertThat(taskExecutionListenerResults.get(2).getTaskName())
|
||||
.isEqualTo(AFTER_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyTaskStartupListenerWithMultipleInstances() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(
|
||||
TaskExecutionListenerMultipleInstanceConfiguration.class);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
setup(context);
|
||||
|
||||
this.taskListenerExecutor
|
||||
.onTaskStartup(createSampleTaskExecution(BEFORE_LISTENER));
|
||||
validateSingleEventWithMultipleInstances(BEFORE_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyTaskFailedListenerWithMultipleInstances() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(
|
||||
TaskExecutionListenerMultipleInstanceConfiguration.class);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
setup(context);
|
||||
|
||||
this.taskListenerExecutor.onTaskFailed(
|
||||
createSampleTaskExecution(FAIL_LISTENER),
|
||||
new IllegalStateException("oops"));
|
||||
validateSingleEventWithMultipleInstances(FAIL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyTaskEndListenerWithMultipleInstances() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(
|
||||
TaskExecutionListenerMultipleInstanceConfiguration.class);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
setup(context);
|
||||
|
||||
this.taskListenerExecutor
|
||||
.onTaskEnd(createSampleTaskExecution(AFTER_LISTENER));
|
||||
validateSingleEventWithMultipleInstances(AFTER_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyAllListenerWithMultipleInstances() {
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(
|
||||
TaskExecutionListenerMultipleInstanceConfiguration.class);
|
||||
|
||||
applicationContextRunner.run((context) -> {
|
||||
setup(context);
|
||||
|
||||
this.taskListenerExecutor
|
||||
.onTaskStartup(createSampleTaskExecution(BEFORE_LISTENER));
|
||||
this.taskListenerExecutor.onTaskFailed(
|
||||
createSampleTaskExecution(FAIL_LISTENER),
|
||||
new IllegalStateException("oops"));
|
||||
this.taskListenerExecutor
|
||||
.onTaskEnd(createSampleTaskExecution(AFTER_LISTENER));
|
||||
assertThat(taskExecutionListenerResults.size()).isEqualTo(6);
|
||||
assertThat(taskExecutionListenerResults.get(0).getTaskName())
|
||||
.isEqualTo(BEFORE_LISTENER);
|
||||
assertThat(taskExecutionListenerResults.get(1).getTaskName())
|
||||
.isEqualTo(BEFORE_LISTENER);
|
||||
assertThat(taskExecutionListenerResults.get(2).getTaskName())
|
||||
.isEqualTo(FAIL_LISTENER);
|
||||
assertThat(taskExecutionListenerResults.get(3).getTaskName())
|
||||
.isEqualTo(FAIL_LISTENER);
|
||||
assertThat(taskExecutionListenerResults.get(4).getTaskName())
|
||||
.isEqualTo(AFTER_LISTENER);
|
||||
assertThat(taskExecutionListenerResults.get(5).getTaskName())
|
||||
.isEqualTo(AFTER_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
private TaskExecution createSampleTaskExecution(String taskName) {
|
||||
@@ -133,6 +239,12 @@ public class TaskListenerExecutorObjectFactoryTests {
|
||||
assertThat(taskExecutionListenerResults.get(0).getTaskName()).isEqualTo(event);
|
||||
}
|
||||
|
||||
private void validateSingleEventWithMultipleInstances(String event) {
|
||||
assertThat(taskExecutionListenerResults.size()).isEqualTo(2);
|
||||
assertThat(taskExecutionListenerResults)
|
||||
.allSatisfy(task -> assertThat(task.getTaskName()).isEqualTo(event));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class TaskExecutionListenerConfiguration {
|
||||
|
||||
@@ -143,6 +255,20 @@ public class TaskListenerExecutorObjectFactoryTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class TaskExecutionListenerMultipleInstanceConfiguration {
|
||||
|
||||
@Bean
|
||||
public TaskRunComponent taskRunComponent() {
|
||||
return new TaskRunComponent();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TaskRunComponent otherTaskRunComponent() {
|
||||
return new TaskRunComponent();
|
||||
}
|
||||
}
|
||||
|
||||
public static class TaskRunComponent {
|
||||
|
||||
@BeforeTask
|
||||
|
||||
Reference in New Issue
Block a user