Skip to content

Commit f4f042a

Browse files
committed
Support interface based listener registration
1 parent 425134c commit f4f042a

File tree

14 files changed

+500
-123
lines changed

14 files changed

+500
-123
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ protected JobRepository getJobRepository() {
261261
* @throws JobExecutionException to signal a fatal batch framework error (not a
262262
* business or validation exception)
263263
*/
264-
abstract protected void doExecute(JobExecution execution) throws JobExecutionException;
264+
protected abstract void doExecute(JobExecution execution) throws JobExecutionException;
265265

266266
/**
267267
* Run the specified job, handling all listener and repository calls, and delegating

spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilderHelper.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,14 @@
1515
*/
1616
package org.springframework.batch.core.job.builder;
1717

18-
import java.lang.reflect.Method;
18+
import java.lang.annotation.Annotation;
1919
import java.util.ArrayList;
20-
import java.util.HashSet;
2120
import java.util.LinkedHashSet;
2221
import java.util.List;
2322
import java.util.Set;
2423

25-
import io.micrometer.core.instrument.MeterRegistry;
26-
import io.micrometer.observation.ObservationRegistry;
2724
import org.apache.commons.logging.Log;
2825
import org.apache.commons.logging.LogFactory;
29-
3026
import org.springframework.batch.core.JobExecutionListener;
3127
import org.springframework.batch.core.JobParametersIncrementer;
3228
import org.springframework.batch.core.JobParametersValidator;
@@ -39,13 +35,17 @@
3935
import org.springframework.batch.core.repository.JobRepository;
4036
import org.springframework.batch.support.ReflectionUtils;
4137

38+
import io.micrometer.core.instrument.MeterRegistry;
39+
import io.micrometer.observation.ObservationRegistry;
40+
4241
/**
4342
* A base class and utility for other job builders providing access to common properties
4443
* like job repository.
4544
*
4645
* @author Dave Syer
4746
* @author Mahmoud Ben Hassine
4847
* @author Taeik Lim
48+
* @author Seonkyo Ok
4949
* @since 2.2
5050
*/
5151
public abstract class JobBuilderHelper<B extends JobBuilderHelper<B>> {
@@ -167,14 +167,10 @@ public B meterRegistry(MeterRegistry meterRegistry) {
167167
* @return this for fluent chaining
168168
*/
169169
public B listener(Object listener) {
170-
Set<Method> jobExecutionListenerMethods = new HashSet<>();
171-
jobExecutionListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeJob.class));
172-
jobExecutionListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterJob.class));
173-
174-
if (jobExecutionListenerMethods.size() > 0) {
175-
JobListenerFactoryBean factory = new JobListenerFactoryBean();
176-
factory.setDelegate(listener);
177-
properties.addJobExecutionListener((JobExecutionListener) factory.getObject());
170+
final List<Class<? extends Annotation>> targetAnnotations = List.of(BeforeJob.class, AfterJob.class);
171+
if (listener instanceof JobExecutionListener ||
172+
ReflectionUtils.hasMethodWithAnyAnnotation(listener.getClass(), targetAnnotations)) {
173+
properties.addJobExecutionListener(JobListenerFactoryBean.getListener(listener));
178174
}
179175

180176
@SuppressWarnings("unchecked")

spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
import org.apache.commons.logging.Log;
2525
import org.apache.commons.logging.LogFactory;
26-
2726
import org.springframework.aop.TargetSource;
2827
import org.springframework.aop.framework.Advised;
2928
import org.springframework.aop.framework.ProxyFactory;
@@ -59,6 +58,7 @@
5958
* @author Lucas Ward
6059
* @author Dan Garrette
6160
* @author Taeik Lim
61+
* @author Seonkyo Ok
6262
* @since 2.0
6363
* @see ListenerMetaData
6464
*/
@@ -103,15 +103,15 @@ public Object getObject() {
103103
}
104104

105105
invoker = getMethodInvokerByName(entry.getValue(), delegate, metaData.getParamTypes());
106-
if (invoker != null) {
106+
if (invoker != null && !invokers.contains(invoker)) {
107107
invokers.add(invoker);
108108
synthetic = true;
109109
}
110110

111111
if (metaData.getAnnotation() != null) {
112112
invoker = MethodInvokerUtils.getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate,
113-
metaData.getParamTypes());
114-
if (invoker != null) {
113+
metaData.hasGenericParameter(), metaData.getParamTypes());
114+
if (invoker != null && !invokers.contains(invoker)) {
115115
invokers.add(invoker);
116116
synthetic = true;
117117
}
@@ -136,6 +136,7 @@ public Object getObject() {
136136
}
137137
}
138138
// All listeners can be supplied by the delegate itself
139+
// No other methods except overriding methods of these interfaces are in invokerMap.
139140
if (count == listenerInterfaces.size()) {
140141
return delegate;
141142
}
@@ -173,7 +174,8 @@ public Object getObject() {
173174

174175
protected MethodInvoker getMethodInvokerByName(String methodName, Object candidate, Class<?>... params) {
175176
if (methodName != null) {
176-
return MethodInvokerUtils.getMethodInvokerByName(candidate, methodName, false, params);
177+
return MethodInvokerUtils.getMethodInvokerByName(
178+
candidate, methodName, false, params);
177179
}
178180
else {
179181
return null;
@@ -229,7 +231,7 @@ public static boolean isListener(Object target, Class<?> listenerType, ListenerM
229231
}
230232
}
231233
for (ListenerMetaData metaData : metaDataValues) {
232-
if (MethodInvokerUtils.getMethodInvokerByAnnotation(metaData.getAnnotation(), target) != null) {
234+
if (MethodInvokerUtils.getMethodInvokerByAnnotation(metaData.getAnnotation(), target, metaData.hasGenericParameter()) != null) {
233235
return true;
234236
}
235237
}

spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerMetaData.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
*
3232
* @author Lucas Ward
3333
* @author Mahmoud Ben Hassine
34+
* @author Seonkyo Ok
3435
* @since 2.0
3536
* @see JobListenerFactoryBean
3637
*/
@@ -51,7 +52,7 @@ public enum JobListenerMetaData implements ListenerMetaData {
5152
this.methodName = methodName;
5253
this.propertyName = propertyName;
5354
this.annotation = annotation;
54-
}
55+
}
5556

5657
static {
5758
propertyMap = new HashMap<>();
@@ -85,6 +86,11 @@ public Class<?>[] getParamTypes() {
8586
return new Class<?>[] { JobExecution.class };
8687
}
8788

89+
@Override
90+
public boolean hasGenericParameter() {
91+
return false;
92+
}
93+
8894
/**
8995
* Return the relevant meta data for the provided property name.
9096
* @param propertyName name of the property to retrieve.

spring-batch-core/src/main/java/org/springframework/batch/core/listener/ListenerMetaData.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
*
2323
* @author Dan Garrette
2424
* @author Mahmoud Ben Hassine
25+
* @author Seonkyo Ok
2526
* @since 2.0
2627
* @see JobListenerMetaData
2728
* @see StepListenerMetaData
@@ -38,4 +39,6 @@ public interface ListenerMetaData {
3839

3940
Class<?>[] getParamTypes();
4041

42+
boolean hasGenericParameter();
43+
4144
}

spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -53,34 +53,35 @@
5353
* methods, their interfaces, annotation, and expected arguments.
5454
*
5555
* @author Lucas Ward
56+
* @author Seonkyo Ok
5657
* @since 2.0
5758
* @see StepListenerFactoryBean
5859
*/
5960
public enum StepListenerMetaData implements ListenerMetaData {
6061

61-
BEFORE_STEP("beforeStep", "before-step-method", BeforeStep.class, StepExecutionListener.class, StepExecution.class),
62-
AFTER_STEP("afterStep", "after-step-method", AfterStep.class, StepExecutionListener.class, StepExecution.class),
63-
BEFORE_CHUNK("beforeChunk", "before-chunk-method", BeforeChunk.class, ChunkListener.class, ChunkContext.class),
64-
AFTER_CHUNK("afterChunk", "after-chunk-method", AfterChunk.class, ChunkListener.class, ChunkContext.class),
65-
AFTER_CHUNK_ERROR("afterChunkError", "after-chunk-error-method", AfterChunkError.class, ChunkListener.class,
62+
BEFORE_STEP("beforeStep", "before-step-method", BeforeStep.class, StepExecutionListener.class, false, StepExecution.class),
63+
AFTER_STEP("afterStep", "after-step-method", AfterStep.class, StepExecutionListener.class, false, StepExecution.class),
64+
BEFORE_CHUNK("beforeChunk", "before-chunk-method", BeforeChunk.class, ChunkListener.class, false, ChunkContext.class),
65+
AFTER_CHUNK("afterChunk", "after-chunk-method", AfterChunk.class, ChunkListener.class, false, ChunkContext.class),
66+
AFTER_CHUNK_ERROR("afterChunkError", "after-chunk-error-method", AfterChunkError.class, ChunkListener.class, false,
6667
ChunkContext.class),
67-
BEFORE_READ("beforeRead", "before-read-method", BeforeRead.class, ItemReadListener.class),
68-
AFTER_READ("afterRead", "after-read-method", AfterRead.class, ItemReadListener.class, Object.class),
69-
ON_READ_ERROR("onReadError", "on-read-error-method", OnReadError.class, ItemReadListener.class, Exception.class),
70-
BEFORE_PROCESS("beforeProcess", "before-process-method", BeforeProcess.class, ItemProcessListener.class,
68+
BEFORE_READ("beforeRead", "before-read-method", BeforeRead.class, ItemReadListener.class, false),
69+
AFTER_READ("afterRead", "after-read-method", AfterRead.class, ItemReadListener.class, true, Object.class),
70+
ON_READ_ERROR("onReadError", "on-read-error-method", OnReadError.class, ItemReadListener.class, false, Exception.class),
71+
BEFORE_PROCESS("beforeProcess", "before-process-method", BeforeProcess.class, ItemProcessListener.class, true,
7172
Object.class),
72-
AFTER_PROCESS("afterProcess", "after-process-method", AfterProcess.class, ItemProcessListener.class, Object.class,
73+
AFTER_PROCESS("afterProcess", "after-process-method", AfterProcess.class, ItemProcessListener.class, true, Object.class,
7374
Object.class),
74-
ON_PROCESS_ERROR("onProcessError", "on-process-error-method", OnProcessError.class, ItemProcessListener.class,
75+
ON_PROCESS_ERROR("onProcessError", "on-process-error-method", OnProcessError.class, ItemProcessListener.class, true,
7576
Object.class, Exception.class),
76-
BEFORE_WRITE("beforeWrite", "before-write-method", BeforeWrite.class, ItemWriteListener.class, Chunk.class),
77-
AFTER_WRITE("afterWrite", "after-write-method", AfterWrite.class, ItemWriteListener.class, Chunk.class),
78-
ON_WRITE_ERROR("onWriteError", "on-write-error-method", OnWriteError.class, ItemWriteListener.class,
77+
BEFORE_WRITE("beforeWrite", "before-write-method", BeforeWrite.class, ItemWriteListener.class, true, Chunk.class),
78+
AFTER_WRITE("afterWrite", "after-write-method", AfterWrite.class, ItemWriteListener.class, true, Chunk.class),
79+
ON_WRITE_ERROR("onWriteError", "on-write-error-method", OnWriteError.class, ItemWriteListener.class, true,
7980
Exception.class, Chunk.class),
80-
ON_SKIP_IN_READ("onSkipInRead", "on-skip-in-read-method", OnSkipInRead.class, SkipListener.class, Throwable.class),
81-
ON_SKIP_IN_PROCESS("onSkipInProcess", "on-skip-in-process-method", OnSkipInProcess.class, SkipListener.class,
81+
ON_SKIP_IN_READ("onSkipInRead", "on-skip-in-read-method", OnSkipInRead.class, SkipListener.class, false, Throwable.class),
82+
ON_SKIP_IN_PROCESS("onSkipInProcess", "on-skip-in-process-method", OnSkipInProcess.class, SkipListener.class, true,
8283
Object.class, Throwable.class),
83-
ON_SKIP_IN_WRITE("onSkipInWrite", "on-skip-in-write-method", OnSkipInWrite.class, SkipListener.class, Object.class,
84+
ON_SKIP_IN_WRITE("onSkipInWrite", "on-skip-in-write-method", OnSkipInWrite.class, SkipListener.class, true, Object.class,
8485
Throwable.class);
8586

8687
private final String methodName;
@@ -91,16 +92,20 @@ public enum StepListenerMetaData implements ListenerMetaData {
9192

9293
private final Class<? extends StepListener> listenerInterface;
9394

95+
private final boolean hasGenericParameter;
96+
9497
private final Class<?>[] paramTypes;
9598

9699
private static final Map<String, StepListenerMetaData> propertyMap;
97100

98101
StepListenerMetaData(String methodName, String propertyName, Class<? extends Annotation> annotation,
99-
Class<? extends StepListener> listenerInterface, Class<?>... paramTypes) {
102+
Class<? extends StepListener> listenerInterface, boolean hasGenericParameter,
103+
Class<?>... paramTypes) {
100104
this.methodName = methodName;
101105
this.propertyName = propertyName;
102106
this.annotation = annotation;
103107
this.listenerInterface = listenerInterface;
108+
this.hasGenericParameter = hasGenericParameter;
104109
this.paramTypes = paramTypes;
105110
}
106111

@@ -136,6 +141,11 @@ public String getPropertyName() {
136141
return propertyName;
137142
}
138143

144+
@Override
145+
public boolean hasGenericParameter() {
146+
return hasGenericParameter;
147+
}
148+
139149
/**
140150
* Return the relevant meta data for the provided property name.
141151
* @param propertyName property name to retrieve data for.

spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
*/
1616
package org.springframework.batch.core.step.builder;
1717

18-
import java.lang.reflect.Method;
19-
import java.util.HashSet;
18+
import java.lang.annotation.Annotation;
2019
import java.util.LinkedHashSet;
20+
import java.util.List;
2121
import java.util.Set;
2222

2323
import org.springframework.batch.core.ChunkListener;
@@ -50,6 +50,7 @@
5050
* @author Michael Minella
5151
* @author Mahmoud Ben Hassine
5252
* @author Ilpyo Yang
53+
* @author Seonkyo Ok
5354
* @since 2.2
5455
* @param <B> the type of builder represented
5556
*/
@@ -175,15 +176,11 @@ public B listener(ChunkListener listener) {
175176
public B listener(Object listener) {
176177
super.listener(listener);
177178

178-
Set<Method> chunkListenerMethods = new HashSet<>();
179-
chunkListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeChunk.class));
180-
chunkListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterChunk.class));
181-
chunkListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterChunkError.class));
182-
183-
if (!chunkListenerMethods.isEmpty()) {
184-
StepListenerFactoryBean factory = new StepListenerFactoryBean();
185-
factory.setDelegate(listener);
186-
this.listener((ChunkListener) factory.getObject());
179+
final List<Class<? extends Annotation>> targetAnnotations = List.of(
180+
BeforeChunk.class, AfterChunk.class, AfterChunkError.class);
181+
if (listener instanceof ChunkListener ||
182+
ReflectionUtils.hasMethodWithAnyAnnotation(listener.getClass(), targetAnnotations)) {
183+
this.listener((ChunkListener) StepListenerFactoryBean.getListener(listener));
187184
}
188185

189186
return self();

spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package org.springframework.batch.core.step.builder;
1717

18-
import java.lang.reflect.Method;
18+
import java.lang.annotation.Annotation;
1919
import java.util.ArrayList;
2020
import java.util.Collection;
2121
import java.util.HashMap;
@@ -90,6 +90,7 @@
9090
* @author Chris Schaefer
9191
* @author Michael Minella
9292
* @author Mahmoud Ben Hassine
93+
* @author Seonkyo Ok
9394
* @since 2.2
9495
*/
9596
public class FaultTolerantStepBuilder<I, O> extends SimpleStepBuilder<I, O> {
@@ -194,15 +195,11 @@ protected Tasklet createTasklet() {
194195
public FaultTolerantStepBuilder<I, O> listener(Object listener) {
195196
super.listener(listener);
196197

197-
Set<Method> skipListenerMethods = new HashSet<>();
198-
skipListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnSkipInRead.class));
199-
skipListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnSkipInProcess.class));
200-
skipListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnSkipInWrite.class));
201-
202-
if (!skipListenerMethods.isEmpty()) {
203-
StepListenerFactoryBean factory = new StepListenerFactoryBean();
204-
factory.setDelegate(listener);
205-
skipListeners.add((SkipListener<I, O>) factory.getObject());
198+
final List<Class<? extends Annotation>> targetAnnotations = List.of(
199+
OnSkipInRead.class, OnSkipInProcess.class, OnSkipInWrite.class);
200+
if (listener instanceof SkipListener<?,?> ||
201+
ReflectionUtils.hasMethodWithAnyAnnotation(listener.getClass(), targetAnnotations)) {
202+
skipListeners.add((SkipListener<I, O>) StepListenerFactoryBean.getListener(listener));
206203
}
207204

208205
return this;

0 commit comments

Comments
 (0)