In the previous blog post entry, we introduced Spring Batch with a simple exposition of its features, main concepts both for configuring and running Batch Jobs.
We also saw a sample application and two ways of running it: by invoking a JobLauncher bean or by using CommandLineJobRunner from the command line.
In this blog entry, we’ll see two additional ways to run a Spring Batch job:
- Using JobOperator, in order to have control of the batch process, from start a job to monitoring tasks such as stopping, restarting, or summarizing a Job. We’ll only pay attention to the start operation, but once a JobOperator is configured, you can use for the remaining monitoring tasks.
- Using Spring Boot, the new convention-over-configuration centric framework from the Spring team, that allows you to create with a few lines of code applications that “just run”, because Spring Boot provides a lot of features based on what you have in your classpath.
As usual, all the source code is available at GitHub.
Running the sample: JobOperator
JobOperator is an interface that provides operations for inspecting and controlling jobs, mainly for a command-line client or a remote launcher like a JMX console.
The implementation that Spring Batch provides, SimpleJobOperator, uses JobLauncher, JobRepository, JobExplorer, and JobRegistry for performing its operations. They are created by the @EnableBatchProcessing annotation, so we can create an additional batch configuration file with these dependencies autowired and later import it in the batch configuration file (not without issues, due to the way Spring loads the application context, we’ll see this shortly):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@Configuration public class AdditionalBatchConfiguration { @Autowired JobRepository jobRepository; @Autowired JobRegistry jobRegistry; @Autowired JobLauncher jobLauncher; @Autowired JobExplorer jobExplorer; @Bean public JobOperator jobOperator() { SimpleJobOperator jobOperator = new SimpleJobOperator(); jobOperator.setJobExplorer(jobExplorer); jobOperator.setJobLauncher(jobLauncher); jobOperator.setJobRegistry(jobRegistry); jobOperator.setJobRepository(jobRepository); return jobOperator; } } |
And the @Import:
1 2 3 4 5 6 7 8 |
@Configuration @EnableBatchProcessing @Import(AdditionalBatchConfiguration.class) public class BatchConfiguration { // Omitted } |
Now it seems easy to run the job with a main class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@Component public class MainJobOperator { @Autowired JobOperator jobOperator; @Autowired Job importUserJob; public static void main(String... args) throws JobParametersInvalidException, JobInstanceAlreadyExistsException, NoSuchJobException, DuplicateJobException, NoSuchJobExecutionException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfiguration.class); MainJobOperator main = context.getBean(MainJobOperator.class); long executionId = main.jobOperator.start(main.importUserJob.getName(), null); MainHelper.reportResults(main.jobOperator, executionId); MainHelper.reportPeople(context.getBean(JdbcTemplate.class)); context.close(); System.out.printf("\nFIN %s", main.getClass().getName()); } } |
But there’s a little problem… it doesn’t work:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
Exception in thread "main" org.springframework.batch.core.launch.NoSuchJobException: No job configuration with the name [importUserJob] was registered at org.springframework.batch.core.configuration.support.MapJobRegistry.getJob(MapJobRegistry.java:66) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:483) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$PassthruAdvice.invoke(SimpleBatchConfiguration.java:127) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) at com.sun.proxy.$Proxy14.getJob(Unknown Source) at org.springframework.batch.core.launch.support.SimpleJobOperator.start(SimpleJobOperator.java:310) at com.malsolo.springframework.batch.sample.MainJobOperator.main(MainJobOperator.java:15) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:483) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134) Process finished with exit code 1 |
The problem here, No job configuration with the name [importUserJob] was registered, is due to the way that JobOperator.start(String jobName, String parameters) works.
The main difference with JobLauncher.run(Job job, JobParameters jobParameters) is that the former has String as parameters while the latter uses objects directly.
So JobOperator, actually SimpleJobOperator, has to obtain a Job with the provided name. In order to do so, it uses the JobRegistry.getJob(String name) method. The available Spring Batch implementation is MapJobRegistry that uses a ConcurrentMap to store using the job name as the key, a JobFactory to create the Job when requested.
The problem is that this map has not been populated.
The first solution is easy: the JobRegistry allows you to register at runtime a JobFactory to later obtain the Job as explained above. So, we only need to create this JobFactory…
1 2 3 4 5 6 7 8 9 10 11 |
@Configuration public class AdditionalBatchConfiguration { // Rest omitted @Autowired Job importUserJob; @Bean public JobFactory jobFactory() { return new ReferenceJobFactory(importUserJob); } } |
…and register it in the main method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
@Component public class MainJobOperator { @Autowired JobFactory jobFactory; @Autowired JobRegistry jobRegistry; @Autowired JobOperator jobOperator; @Autowired Job importUserJob; public static void main(String... args) throws JobParametersInvalidException, JobInstanceAlreadyExistsException, NoSuchJobException, DuplicateJobException, NoSuchJobExecutionException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfiguration.class); MainJobOperator main = context.getBean(MainJobOperator.class); main.jobRegistry.register(main.jobFactory); long executionId = main.jobOperator.start(main.importUserJob.getName(), null); MainHelper.reportResults(main.jobOperator, executionId); MainHelper.reportPeople(context.getBean(JdbcTemplate.class)); context.close(); System.out.printf("\nFIN %s", main.getClass().getName()); } } |
And now it works:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
*********************************************************** JobExecution: id=0, version=2, startTime=2014-09-04 13:03:37.964, endTime=2014-09-04 13:03:38.141, lastUpdated=2014-09-04 13:03:38.141, status=COMPLETED, exitStatus=exitCode=COMPLETED;exitDescription=, job=[JobInstance: id=0, version=0, Job=[importUserJob]], jobParameters=[{}] * Steps executed: StepExecution: id=0, version=3, name=step1, status=COMPLETED, exitStatus=COMPLETED, readCount=5, filterCount=0, writeCount=5 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=1, rollbackCount=0, exitDescription= *********************************************************** *********************************************************** * People found: * Found firstName: JILL, lastName: DOE in the database * Found firstName: JOE, lastName: DOE in the database * Found firstName: JUSTIN, lastName: DOE in the database * Found firstName: JANE, lastName: DOE in the database * Found firstName: JOHN, lastName: DOE in the database *********************************************************** |
But I don’t like this approach, it’s too manual.
I’d rather to populate the JobRegistry automatically, and Spring Batch provides two mechanisms for doing so, a bean post-processor, JobRegistryBeanPostProcessor, and a component that loads and unloads Jobs by creating child context and registering jobs from those contexts as they are created, AutomaticJobRegistrar.
We’ll see the post-processor approach, because it’s very easy. Just declare the bean in the Batch configuration and run the original main class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Configuration @EnableBatchProcessing @Import(AdditionalBatchConfiguration.class) public class BatchConfiguration { // Omitted @Bean public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) { JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor(); jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry); return jobRegistryBeanPostProcessor; } } |
The bean post processor has to be declared in this configuration file for registering the job when it’s created (this is the issue that I mentioned before, if you declare the post processor in another java file configuration, for instance in the AdditionalBatchConfiguration it will never receive the job bean). It uses the same JobRegistry that uses the JobOperator to launch the Job. Actually the only that exists, but it’s good to know this.
It also works:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
*********************************************************** JobExecution: id=0, version=2, startTime=2014-09-04 13:20:07.343, endTime=2014-09-04 13:20:07.522, lastUpdated=2014-09-04 13:20:07.522, status=COMPLETED, exitStatus=exitCode=COMPLETED;exitDescription=, job=[JobInstance: id=0, version=0, Job=[importUserJob]], jobParameters=[{}] * Steps executed: StepExecution: id=0, version=3, name=step1, status=COMPLETED, exitStatus=COMPLETED, readCount=5, filterCount=0, writeCount=5 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=1, rollbackCount=0, exitDescription= *********************************************************** *********************************************************** * People found: * Found firstName: JILL, lastName: DOE in the database * Found firstName: JOE, lastName: DOE in the database * Found firstName: JUSTIN, lastName: DOE in the database * Found firstName: JANE, lastName: DOE in the database * Found firstName: JOHN, lastName: DOE in the database *********************************************************** |
Running the sample: Spring Boot
We’d like to try how quickly and easy is Spring Boot for launching Spring Batch applications once we already have a functional configuration.
And it seems to be a piece of cake (the problem here is to know what is happening under the hood)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.malsolo.springframework.batch.sample; @ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = ApplicationConfiguration.class)}) @EnableAutoConfiguration public class MainBoot { public static void main(String... args) { ApplicationContext context = SpringApplication.run(MainBoot.class); MainHelper.reportPeople(context.getBean(JdbcTemplate.class)); } } |
Actually, Spring Boot is out of the bounds of this topic, it deserves its own entry (even, an entire book) but we can summarize the important code here:
- Line 3: @EnableAutoConfiguration, with this annotation you want Spring Boot to instantiate the beans that you’re going to need based on the libraries on your classpath.
- Line 8: the run method, to bootstrap the application by passing the class itself (in our case, MainBoot) that serves as the primary Spring component.
It’s enough to run the Batch application:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.1.4.RELEASE) 2014-09-04 16:58:55.109 INFO 15646 --- [ main] c.m.s.batch.sample.MainBoot : Starting MainBoot on jbeneito-Latitude-3540 with PID 15646 (/home/jbeneito/Documents/git/spring-batch-sample/target/classes started by jbeneito in /home/jbeneito/Documents/git/spring-batch-sample) 2014-09-04 16:58:55.237 INFO 15646 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6366ebe0: startup date [Thu Sep 04 16:58:55 CEST 2014]; root of context hierarchy 2014-09-04 16:58:56.039 INFO 15646 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'jdbcTemplate': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=batchConfiguration; factoryMethodName=jdbcTemplate; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/malsolo/springframework/batch/sample/BatchConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=mainForSpringInfo; factoryMethodName=jdbcTemplate; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/malsolo/springframework/batch/sample/MainForSpringInfo.class]] 2014-09-04 16:58:56.691 WARN 15646 --- [ main] o.s.c.a.ConfigurationClassEnhancer : @Bean method ScopeConfiguration.stepScope is non-static and returns an object assignable to Spring's BeanFactoryPostProcessor interface. This will result in a failure to process annotations such as @Autowired, @Resource and @PostConstruct within the method's declaring @Configuration class. Add the 'static' modifier to this method to avoid these container lifecycle issues; see @Bean Javadoc for complete details 2014-09-04 16:58:56.724 WARN 15646 --- [ main] o.s.c.a.ConfigurationClassEnhancer : @Bean method ScopeConfiguration.jobScope is non-static and returns an object assignable to Spring's BeanFactoryPostProcessor interface. This will result in a failure to process annotations such as @Autowired, @Resource and @PostConstruct within the method's declaring @Configuration class. Add the 'static' modifier to this method to avoid these container lifecycle issues; see @Bean Javadoc for complete details 2014-09-04 16:58:56.729 INFO 15646 --- [ main] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring 2014-09-04 16:58:56.975 INFO 15646 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'batchConfiguration' of type [class com.malsolo.springframework.batch.sample.BatchConfiguration$$EnhancerBySpringCGLIB$$c3ec56ab] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2014-09-04 16:58:57.145 INFO 15646 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [class org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$5a15b25b] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2014-09-04 16:58:57.238 INFO 15646 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'transactionAttributeSource' of type [class org.springframework.transaction.annotation.AnnotationTransactionAttributeSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2014-09-04 16:58:57.294 INFO 15646 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'transactionInterceptor' of type [class org.springframework.transaction.interceptor.TransactionInterceptor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2014-09-04 16:58:57.301 INFO 15646 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.config.internalTransactionAdvisor' of type [class org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2014-09-04 16:58:57.374 INFO 15646 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'dataSourceConfiguration' of type [class com.malsolo.springframework.batch.sample.DataSourceConfiguration$$EnhancerBySpringCGLIB$$18a97d02] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2014-09-04 16:58:57.455 INFO 15646 --- [ main] o.s.j.d.e.EmbeddedDatabaseFactory : Creating embedded database 'testdb' 2014-09-04 16:58:58.156 INFO 15646 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executing SQL script from class path resource [schema-all.sql] 2014-09-04 16:58:58.178 INFO 15646 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executed SQL script from class path resource [schema-all.sql] in 20 ms. 2014-09-04 16:58:58.178 INFO 15646 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executing SQL script from class path resource [org/springframework/batch/core/schema-hsqldb.sql] 2014-09-04 16:58:58.189 INFO 15646 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executed SQL script from class path resource [org/springframework/batch/core/schema-hsqldb.sql] in 11 ms. 2014-09-04 16:58:58.198 INFO 15646 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'dataSource' of type [class org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory$EmbeddedDataSourceProxy] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2014-09-04 16:58:58.206 INFO 15646 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration$DataSourceInitializerConfiguration' of type [class org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration$DataSourceInitializerConfiguration$$EnhancerBySpringCGLIB$$c0608242] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2014-09-04 16:58:58.257 INFO 15646 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'spring.datasource.CONFIGURATION_PROPERTIES' of type [class org.springframework.boot.autoconfigure.jdbc.DataSourceProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2014-09-04 16:58:58.260 INFO 15646 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executing SQL script from URL [file:/home/jbeneito/Documents/git/spring-batch-sample/target/classes/schema-all.sql] 2014-09-04 16:58:58.262 INFO 15646 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executed SQL script from URL [file:/home/jbeneito/Documents/git/spring-batch-sample/target/classes/schema-all.sql] in 2 ms. 2014-09-04 16:58:58.263 WARN 15646 --- [ main] o.s.b.a.jdbc.DataSourceInitializer : Could not send event to complete DataSource initialization (ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: org.springframework.context.annotation.AnnotationConfigApplicationContext@6366ebe0: startup date [Thu Sep 04 16:58:55 CEST 2014]; root of context hierarchy) 2014-09-04 16:58:58.263 INFO 15646 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'dataSourceInitializer' of type [class org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2014-09-04 16:58:58.277 INFO 15646 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration' of type [class org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$$EnhancerBySpringCGLIB$$85a27e41] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2014-09-04 16:58:58.320 INFO 15646 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'jobRegistry' of type [class com.sun.proxy.$Proxy25] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2014-09-04 16:58:58.795 INFO 15646 --- [ main] o.s.b.c.r.s.JobRepositoryFactoryBean : No database type set, using meta data indicating: HSQL 2014-09-04 16:58:59.056 INFO 15646 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : No TaskExecutor has been set, defaulting to synchronous executor. 2014-09-04 16:58:59.361 INFO 15646 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executing SQL script from class path resource [org/springframework/batch/core/schema-hsqldb.sql] 2014-09-04 16:58:59.381 INFO 15646 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executed SQL script from class path resource [org/springframework/batch/core/schema-hsqldb.sql] in 20 ms. 2014-09-04 16:58:59.890 INFO 15646 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2014-09-04 16:58:59.926 INFO 15646 --- [ main] o.s.b.a.b.JobLauncherCommandLineRunner : Running default command line with: [] 2014-09-04 16:59:00.023 INFO 15646 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=importUserJob]] launched with the following parameters: [{run.id=1}] 2014-09-04 16:59:00.060 INFO 15646 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1] Converting (firstName: Jill, lastName: Doe) into (firstName: JILL, lastName: DOE) Converting (firstName: Joe, lastName: Doe) into (firstName: JOE, lastName: DOE) Converting (firstName: Justin, lastName: Doe) into (firstName: JUSTIN, lastName: DOE) Converting (firstName: Jane, lastName: Doe) into (firstName: JANE, lastName: DOE) Converting (firstName: John, lastName: Doe) into (firstName: JOHN, lastName: DOE) 2014-09-04 16:59:00.162 INFO 15646 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=importUserJob]] completed with the following parameters: [{run.id=1}] and the following status: [COMPLETED] 2014-09-04 16:59:00.164 INFO 15646 --- [ main] c.m.s.batch.sample.MainBoot : Started MainBoot in 5.963 seconds (JVM running for 8.019) *********************************************************** * People found: * Found firstName: JILL, lastName: DOE in the database * Found firstName: JOE, lastName: DOE in the database * Found firstName: JUSTIN, lastName: DOE in the database * Found firstName: JANE, lastName: DOE in the database * Found firstName: JOHN, lastName: DOE in the database *********************************************************** 2014-09-04 16:59:00.258 INFO 15646 --- [ Thread-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6366ebe0: startup date [Thu Sep 04 16:58:55 CEST 2014]; root of context hierarchy 2014-09-04 16:59:00.262 INFO 15646 --- [ Thread-1] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown Process finished with exit code 0 |
As a side note, this class already scans for compontents, so we don’t need to make an additional scan for components, so we exclude the ApplicationConfiguration class with a filter.
Finally, we use the MainHelper class to show a summary of the results.
That’s all for now, because one more time this entry is growing really fast. Thus, we’ll see in the next post the last topic of Spring Batch that I want to talk about: JSR 352.
Resources
- Webinar: Spring Batch 3.0.0 by Michael Minella. Published on Jun 18, 2014.
- JSR-352, Spring Batch and You by Michael Minella. Published on Feb 3, 2014.
- Integrating Spring Batch and Spring Integration by Gunnar Hillert, Michael Minella. Published on Jul 9, 2014.
- Pro Spring Batch (Expert’s Voice in Spring) by Michael Minella. Published on July 12, 2011 by Apress.
- Spring Batch in Action by Arnaud Cogoluegnes, Thierry Templier, Gary Gregory, Olivier Bazoud. Published on October 10, 2011 by Manning Publications.
- Spring.io GETTING STARTED GUIDE: Creating a Batch Service.
- Spring Batch – Reference Documentation
- Spring Batch – API specification