From 744d1834fe313204f06c0bcd0eedd472ab4af6be Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 21 Aug 2020 10:31:03 +0200 Subject: [PATCH] Add support for annotation based listeners in JobBuilder Resolves #817 --- .../core/job/builder/JobBuilderHelper.java | 31 ++++- .../core/job/builder/JobBuilderTests.java | 111 ++++++++++++++++++ 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/JobBuilderTests.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilderHelper.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilderHelper.java index 4c98cdbf1..b1ed7c2ac 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilderHelper.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilderHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2011 the original author or authors. + * Copyright 2006-2020 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. @@ -15,7 +15,9 @@ */ package org.springframework.batch.core.job.builder; +import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -26,13 +28,18 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.JobParametersIncrementer; import org.springframework.batch.core.JobParametersValidator; +import org.springframework.batch.core.annotation.AfterJob; +import org.springframework.batch.core.annotation.BeforeJob; import org.springframework.batch.core.job.AbstractJob; +import org.springframework.batch.core.listener.JobListenerFactoryBean; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.support.ReflectionUtils; /** * A base class and utility for other job builders providing access to common properties like job repository. * * @author Dave Syer + * @author Mahmoud Ben Hassine * * @since 2.2 */ @@ -95,6 +102,28 @@ public abstract class JobBuilderHelper> { return result; } + /** + * Registers objects using the annotation based listener configuration. + * + * @param listener the object that has a method configured with listener annotation + * @return this for fluent chaining + */ + public B listener(Object listener) { + Set jobExecutionListenerMethods = new HashSet<>(); + jobExecutionListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeJob.class)); + jobExecutionListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterJob.class)); + + if(jobExecutionListenerMethods.size() > 0) { + JobListenerFactoryBean factory = new JobListenerFactoryBean(); + factory.setDelegate(listener); + properties.addJobExecutionListener((JobExecutionListener) factory.getObject()); + } + + @SuppressWarnings("unchecked") + B result = (B) this; + return result; + } + /** * Register a job execution listener. * diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/JobBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/JobBuilderTests.java new file mode 100644 index 000000000..9bbd3e8aa --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/JobBuilderTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2020 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 + * + * https://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.batch.core.job.builder; + +import org.junit.Assert; +import org.junit.Test; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobExecutionListener; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.annotation.AfterJob; +import org.springframework.batch.core.annotation.BeforeJob; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.junit.Assert.assertEquals; + +/** + * @author Mahmoud Ben Hassine + */ +public class JobBuilderTests { + + @Test + public void testListeners() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(MyJobConfiguration.class); + JobLauncher jobLauncher = context.getBean(JobLauncher.class); + Job job = context.getBean(Job.class); + + // when + JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + + // then + Assert.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + assertEquals(1, AnnotationBasedJobExecutionListener.beforeJobCount); + assertEquals(1, AnnotationBasedJobExecutionListener.afterJobCount); + assertEquals(1, InterfaceBasedJobExecutionListener.beforeJobCount); + assertEquals(1, InterfaceBasedJobExecutionListener.afterJobCount); + + } + + @Configuration + @EnableBatchProcessing + static class MyJobConfiguration { + @Bean + public Job job(JobBuilderFactory jobs, StepBuilderFactory steps) { + return jobs.get("job") + .listener(new InterfaceBasedJobExecutionListener()) + .listener(new AnnotationBasedJobExecutionListener()) + .start(steps.get("step") + .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED) + .build()) + .build(); + } + } + + static class InterfaceBasedJobExecutionListener implements JobExecutionListener { + + public static int beforeJobCount = 0; + public static int afterJobCount = 0; + + @Override + public void beforeJob(JobExecution jobExecution) { + beforeJobCount++; + } + + @Override + public void afterJob(JobExecution jobExecution) { + afterJobCount++; + } + } + + static class AnnotationBasedJobExecutionListener { + + public static int beforeJobCount = 0; + public static int afterJobCount = 0; + + @BeforeJob + public void beforeJob(JobExecution jobExecution) { + beforeJobCount++; + } + + @AfterJob + public void afterJob(JobExecution jobExecution) { + afterJobCount++; + } + } + +} \ No newline at end of file