Skip to content

Commit c14a7cc

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

File tree

14 files changed

+501
-121
lines changed

14 files changed

+501
-121
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 & 5 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,8 @@ 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
140+
// invokerMap.
139141
if (count == listenerInterfaces.size()) {
140142
return delegate;
141143
}
@@ -229,7 +231,8 @@ 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,
235+
metaData.hasGenericParameter()) != null) {
233236
return true;
234237
}
235238
}

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

Lines changed: 6 additions & 0 deletions
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
*/
@@ -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: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,35 +53,41 @@
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,
63+
StepExecution.class),
64+
AFTER_STEP("afterStep", "after-step-method", AfterStep.class, StepExecutionListener.class, false,
65+
StepExecution.class),
66+
BEFORE_CHUNK("beforeChunk", "before-chunk-method", BeforeChunk.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,
71-
Object.class),
72-
AFTER_PROCESS("afterProcess", "after-process-method", AfterProcess.class, ItemProcessListener.class, Object.class,
68+
AFTER_CHUNK("afterChunk", "after-chunk-method", AfterChunk.class, ChunkListener.class, false, ChunkContext.class),
69+
AFTER_CHUNK_ERROR("afterChunkError", "after-chunk-error-method", AfterChunkError.class, ChunkListener.class, false,
70+
ChunkContext.class),
71+
BEFORE_READ("beforeRead", "before-read-method", BeforeRead.class, ItemReadListener.class, false),
72+
AFTER_READ("afterRead", "after-read-method", AfterRead.class, ItemReadListener.class, true, Object.class),
73+
ON_READ_ERROR("onReadError", "on-read-error-method", OnReadError.class, ItemReadListener.class, false,
74+
Exception.class),
75+
BEFORE_PROCESS("beforeProcess", "before-process-method", BeforeProcess.class, ItemProcessListener.class, true,
7376
Object.class),
74-
ON_PROCESS_ERROR("onProcessError", "on-process-error-method", OnProcessError.class, ItemProcessListener.class,
77+
AFTER_PROCESS("afterProcess", "after-process-method", AfterProcess.class, ItemProcessListener.class, true,
78+
Object.class, Object.class),
79+
ON_PROCESS_ERROR("onProcessError", "on-process-error-method", OnProcessError.class, ItemProcessListener.class, true,
7580
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,
81+
BEFORE_WRITE("beforeWrite", "before-write-method", BeforeWrite.class, ItemWriteListener.class, true, Chunk.class),
82+
AFTER_WRITE("afterWrite", "after-write-method", AfterWrite.class, ItemWriteListener.class, true, Chunk.class),
83+
ON_WRITE_ERROR("onWriteError", "on-write-error-method", OnWriteError.class, ItemWriteListener.class, true,
7984
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,
85+
ON_SKIP_IN_READ("onSkipInRead", "on-skip-in-read-method", OnSkipInRead.class, SkipListener.class, false,
86+
Throwable.class),
87+
ON_SKIP_IN_PROCESS("onSkipInProcess", "on-skip-in-process-method", OnSkipInProcess.class, SkipListener.class, true,
8288
Object.class, Throwable.class),
83-
ON_SKIP_IN_WRITE("onSkipInWrite", "on-skip-in-write-method", OnSkipInWrite.class, SkipListener.class, Object.class,
84-
Throwable.class);
89+
ON_SKIP_IN_WRITE("onSkipInWrite", "on-skip-in-write-method", OnSkipInWrite.class, SkipListener.class, true,
90+
Object.class, Throwable.class);
8591

8692
private final String methodName;
8793

@@ -91,16 +97,19 @@ public enum StepListenerMetaData implements ListenerMetaData {
9197

9298
private final Class<? extends StepListener> listenerInterface;
9399

100+
private final boolean hasGenericParameter;
101+
94102
private final Class<?>[] paramTypes;
95103

96104
private static final Map<String, StepListenerMetaData> propertyMap;
97105

98106
StepListenerMetaData(String methodName, String propertyName, Class<? extends Annotation> annotation,
99-
Class<? extends StepListener> listenerInterface, Class<?>... paramTypes) {
107+
Class<? extends StepListener> listenerInterface, boolean hasGenericParameter, Class<?>... paramTypes) {
100108
this.methodName = methodName;
101109
this.propertyName = propertyName;
102110
this.annotation = annotation;
103111
this.listenerInterface = listenerInterface;
112+
this.hasGenericParameter = hasGenericParameter;
104113
this.paramTypes = paramTypes;
105114
}
106115

@@ -136,6 +145,11 @@ public String getPropertyName() {
136145
return propertyName;
137146
}
138147

148+
@Override
149+
public boolean hasGenericParameter() {
150+
return hasGenericParameter;
151+
}
152+
139153
/**
140154
* Return the relevant meta data for the provided property name.
141155
* @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(BeforeChunk.class, AfterChunk.class,
180+
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(OnSkipInRead.class, OnSkipInProcess.class,
199+
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;

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

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,12 @@
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;
20-
import java.util.HashSet;
2120
import java.util.LinkedHashSet;
21+
import java.util.List;
2222
import java.util.Set;
2323

24-
import io.micrometer.core.instrument.MeterRegistry;
25-
import io.micrometer.core.instrument.Metrics;
26-
2724
import org.springframework.batch.core.ChunkListener;
2825
import org.springframework.batch.core.ItemProcessListener;
2926
import org.springframework.batch.core.ItemReadListener;
@@ -56,6 +53,9 @@
5653
import org.springframework.batch.support.ReflectionUtils;
5754
import org.springframework.util.Assert;
5855

56+
import io.micrometer.core.instrument.MeterRegistry;
57+
import io.micrometer.core.instrument.Metrics;
58+
5959
/**
6060
* Step builder for simple item processing (chunk oriented) steps. Items are read and
6161
* cached in chunks, and then processed (transformed) and written (optionally the
@@ -65,6 +65,7 @@
6565
* @author Dave Syer
6666
* @author Mahmoud Ben Hassine
6767
* @author Parikshit Dutta
68+
* @author Seonkyo Ok
6869
* @since 2.2
6970
*/
7071
public class SimpleStepBuilder<I, O> extends AbstractTaskletStepBuilder<SimpleStepBuilder<I, O>> {
@@ -258,21 +259,13 @@ public SimpleStepBuilder<I, O> readerIsTransactionalQueue() {
258259
public SimpleStepBuilder<I, O> listener(Object listener) {
259260
super.listener(listener);
260261

261-
Set<Method> itemListenerMethods = new HashSet<>();
262-
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeRead.class));
263-
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterRead.class));
264-
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeProcess.class));
265-
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterProcess.class));
266-
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeWrite.class));
267-
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterWrite.class));
268-
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnReadError.class));
269-
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnProcessError.class));
270-
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnWriteError.class));
271-
272-
if (!itemListenerMethods.isEmpty()) {
273-
StepListenerFactoryBean factory = new StepListenerFactoryBean();
274-
factory.setDelegate(listener);
275-
itemListeners.add((StepListener) factory.getObject());
262+
final List<Class<? extends Annotation>> targetAnnotations = List.of(BeforeRead.class, AfterRead.class,
263+
OnReadError.class, BeforeProcess.class, AfterProcess.class, OnProcessError.class, BeforeWrite.class,
264+
AfterWrite.class, OnWriteError.class);
265+
if (listener instanceof ItemReadListener<?> || listener instanceof ItemProcessListener<?, ?>
266+
|| listener instanceof ItemWriteListener<?>
267+
|| ReflectionUtils.hasMethodWithAnyAnnotation(listener.getClass(), targetAnnotations)) {
268+
itemListeners.add(StepListenerFactoryBean.getListener(listener));
276269
}
277270

278271
return this;

0 commit comments

Comments
 (0)