This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener). # HTTP URLs that Could Not Be Fixed These URLs were unable to be fixed. Please review them to see if they can be manually resolved. * [ ] http://erik.eae.net/archives/2007/07/27/18.54.15/ (200) with 1 occurrences could not be migrated: ([https](https://erik.eae.net/archives/2007/07/27/18.54.15/) result SSLHandshakeException). * [ ] http://gregfranko.com/jquery.tocify.js/ (200) with 1 occurrences could not be migrated: ([https](https://gregfranko.com/jquery.tocify.js/) result SSLHandshakeException). * [ ] http://javascript.nwbox.com/IEContentLoaded/ (200) with 1 occurrences could not be migrated: ([https](https://javascript.nwbox.com/IEContentLoaded/) result SSLHandshakeException). * [ ] http://jeremyworboys.com/writing/toggle-buttons-without-javascript (200) with 1 occurrences could not be migrated: ([https](https://jeremyworboys.com/writing/toggle-buttons-without-javascript) result SSLHandshakeException). * [ ] http://nicolasgallagher.com/micro-clearfix-hack/ (200) with 1 occurrences could not be migrated: ([https](https://nicolasgallagher.com/micro-clearfix-hack/) result SSLHandshakeException). * [ ] http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html (200) with 1 occurrences could not be migrated: ([https](https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html) result SSLException). * [ ] http://blindsignals.com/index.php/2009/07/jquery-delay/ (301) with 1 occurrences could not be migrated: ([https](https://blindsignals.com/index.php/2009/07/jquery-delay/) result SSLHandshakeException). # Fixed URLs ## Fixed But Review Recommended These URLs were fixed, but the https status was not OK. However, the https status was the same as the http request or http redirected to an https URL, so they were migrated. Your review is recommended. * [ ] http://jsperf.com/getall-vs-sizzle/2 (301) with 1 occurrences migrated to: https://jsperf.com/getall-vs-sizzle/2 ([https](https://jsperf.com/getall-vs-sizzle/2) result ReadTimeoutException). * [ ] http://wiki.ecmascript.org/doku.php?id=harmony:egal (ConnectTimeoutException) with 1 occurrences migrated to: https://wiki.ecmascript.org/doku.php?id=harmony:egal ([https](https://wiki.ecmascript.org/doku.php?id=harmony:egal) result ConnectTimeoutException). * [ ] http://www.w3.org/Addressing/URL (ReadTimeoutException) with 1 occurrences migrated to: https://www.w3.org/Addressing/URL ([https](https://www.w3.org/Addressing/URL) result SSLException). * [ ] http://api.example.com (UnknownHostException) with 1 occurrences migrated to: https://api.example.com ([https](https://api.example.com) result UnknownHostException). * [ ] http://jira.codehaus.org/browse/WSTX-165 (UnknownHostException) with 1 occurrences migrated to: https://jira.codehaus.org/browse/WSTX-165 ([https](https://jira.codehaus.org/browse/WSTX-165) result UnknownHostException). * [ ] http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ (301) with 1 occurrences migrated to: https://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ ([https](https://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/) result 404). * [ ] http://help.github.com/send-pull-requests (404) with 1 occurrences migrated to: https://help.github.com/send-pull-requests ([https](https://help.github.com/send-pull-requests) result 404). * [ ] http://json.org/json2.js (404) with 1 occurrences migrated to: https://json.org/json2.js ([https](https://json.org/json2.js) result 404). ## Fixed Success These URLs were switched to an https URL with a 2xx status. While the status was successful, your review is still recommended. * [ ] http://backbonejs.org with 1 occurrences migrated to: https://backbonejs.org ([https](https://backbonejs.org) result 200). * [ ] http://bugs.jquery.com/ticket/12282 with 1 occurrences migrated to: https://bugs.jquery.com/ticket/12282 ([https](https://bugs.jquery.com/ticket/12282) result 200). * [ ] http://bugs.jquery.com/ticket/12359 with 1 occurrences migrated to: https://bugs.jquery.com/ticket/12359 ([https](https://bugs.jquery.com/ticket/12359) result 200). * [ ] http://bugs.jquery.com/ticket/13378 with 1 occurrences migrated to: https://bugs.jquery.com/ticket/13378 ([https](https://bugs.jquery.com/ticket/13378) result 200). * [ ] http://db.apache.org/derby/papers/versionupgrade.html with 1 occurrences migrated to: https://db.apache.org/derby/papers/versionupgrade.html ([https](https://db.apache.org/derby/papers/versionupgrade.html) result 200). * [ ] http://diveintohtml5.info/history.html with 1 occurrences migrated to: https://diveintohtml5.info/history.html ([https](https://diveintohtml5.info/history.html) result 200). * [ ] http://docs.oracle.com/javase/tutorial/java/generics/capture.html with 2 occurrences migrated to: https://docs.oracle.com/javase/tutorial/java/generics/capture.html ([https](https://docs.oracle.com/javase/tutorial/java/generics/capture.html) result 200). * [ ] http://github.com/spring-projects/spring-batch with 1 occurrences migrated to: https://github.com/spring-projects/spring-batch ([https](https://github.com/spring-projects/spring-batch) result 200). * [ ] http://jira.spring.io/browse/BATCH-1681 with 1 occurrences migrated to: https://jira.spring.io/browse/BATCH-1681 ([https](https://jira.spring.io/browse/BATCH-1681) result 200). * [ ] http://jira.spring.io/browse/BATCH-761 with 1 occurrences migrated to: https://jira.spring.io/browse/BATCH-761 ([https](https://jira.spring.io/browse/BATCH-761) result 200). * [ ] http://jquery.com/ with 1 occurrences migrated to: https://jquery.com/ ([https](https://jquery.com/) result 200). * [ ] http://projects.spring.io/spring-batch/ with 2 occurrences migrated to: https://projects.spring.io/spring-batch/ ([https](https://projects.spring.io/spring-batch/) result 200). * [ ] http://projects.spring.io/spring-batch/faq.html with 1 occurrences migrated to: https://projects.spring.io/spring-batch/faq.html ([https](https://projects.spring.io/spring-batch/faq.html) result 200). * [ ] http://projects.spring.io/spring-integration/ with 1 occurrences migrated to: https://projects.spring.io/spring-integration/ ([https](https://projects.spring.io/spring-integration/) result 200). * [ ] http://sizzlejs.com/ with 2 occurrences migrated to: https://sizzlejs.com/ ([https](https://sizzlejs.com/) result 200). * [ ] http://spring.io/guides/gs/batch-processing/ with 1 occurrences migrated to: https://spring.io/guides/gs/batch-processing/ ([https](https://spring.io/guides/gs/batch-processing/) result 200). * [ ] http://spring.io/projects with 1 occurrences migrated to: https://spring.io/projects ([https](https://spring.io/projects) result 200). * [ ] http://spring.io/tools with 1 occurrences migrated to: https://spring.io/tools ([https](https://spring.io/tools) result 200). * [ ] http://spring.io/tools/sts/ with 1 occurrences migrated to: https://spring.io/tools/sts/ ([https](https://spring.io/tools/sts/) result 200). * [ ] http://stackoverflow.com/a/20002493/2910265 with 1 occurrences migrated to: https://stackoverflow.com/a/20002493/2910265 ([https](https://stackoverflow.com/a/20002493/2910265) result 200). * [ ] http://stackoverflow.com/questions/134882/undoing-a-git-rebase with 1 occurrences migrated to: https://stackoverflow.com/questions/134882/undoing-a-git-rebase ([https](https://stackoverflow.com/questions/134882/undoing-a-git-rebase) result 200). * [ ] http://underscorejs.org with 1 occurrences migrated to: https://underscorejs.org ([https](https://underscorejs.org) result 200). * [ ] http://unlicense.org with 1 occurrences migrated to: https://unlicense.org ([https](https://unlicense.org) result 200). * [ ] http://www.apache.org with 1 occurrences migrated to: https://www.apache.org ([https](https://www.apache.org) result 200). * [ ] http://xmlns.jcp.org/xml/ns/javaee/jobXML_1_0.xsd (301) with 1 occurrences migrated to: https://www.oracle.com/webfolder/technetwork/jsc/xml/ns/javaee/jobXML_1_0.xsd ([https](https://xmlns.jcp.org/xml/ns/javaee/jobXML_1_0.xsd) result 200). * [ ] http://www.ow2.org/ with 1 occurrences migrated to: https://www.ow2.org/ ([https](https://www.ow2.org/) result 200). * [ ] http://www.springframework.org/schema/beans/spring-beans.xsd with 3 occurrences migrated to: https://www.springframework.org/schema/beans/spring-beans.xsd ([https](https://www.springframework.org/schema/beans/spring-beans.xsd) result 200). * [ ] http://www.springframework.org/schema/integration/spring-integration.xsd with 3 occurrences migrated to: https://www.springframework.org/schema/integration/spring-integration.xsd ([https](https://www.springframework.org/schema/integration/spring-integration.xsd) result 200). * [ ] http://www.w3.org/TR/2011/REC-css3-selectors-20110929/ with 2 occurrences migrated to: https://www.w3.org/TR/2011/REC-css3-selectors-20110929/ ([https](https://www.w3.org/TR/2011/REC-css3-selectors-20110929/) result 200). * [ ] http://www.w3.org/TR/CSS21/syndata.html with 2 occurrences migrated to: https://www.w3.org/TR/CSS21/syndata.html ([https](https://www.w3.org/TR/CSS21/syndata.html) result 200). * [ ] http://www.w3.org/TR/selectors/ with 4 occurrences migrated to: https://www.w3.org/TR/selectors/ ([https](https://www.w3.org/TR/selectors/) result 200). * [ ] http://contributor-covenant.org with 1 occurrences migrated to: https://contributor-covenant.org ([https](https://contributor-covenant.org) result 301). * [ ] http://contributor-covenant.org/version/1/3/0/ with 1 occurrences migrated to: https://contributor-covenant.org/version/1/3/0/ ([https](https://contributor-covenant.org/version/1/3/0/) result 301). * [ ] http://dev.w3.org/csswg/cssom/ with 1 occurrences migrated to: https://dev.w3.org/csswg/cssom/ ([https](https://dev.w3.org/csswg/cssom/) result 301). * [ ] http://docs.python.org/library/functions.html with 1 occurrences migrated to: https://docs.python.org/library/functions.html ([https](https://docs.python.org/library/functions.html) result 301). * [ ] http://docs.spring.io/spring-batch/ with 1 occurrences migrated to: https://docs.spring.io/spring-batch/ ([https](https://docs.spring.io/spring-batch/) result 301). * [ ] http://docs.spring.io/spring-batch/apidocs/org/springframework/batch/core/launch/JobOperator.html with 1 occurrences migrated to: https://docs.spring.io/spring-batch/apidocs/org/springframework/batch/core/launch/JobOperator.html ([https](https://docs.spring.io/spring-batch/apidocs/org/springframework/batch/core/launch/JobOperator.html) result 301). * [ ] http://docs.spring.io/spring-batch/apidocs/org/springframework/batch/test/MetaDataInstanceFactory.html with 1 occurrences migrated to: https://docs.spring.io/spring-batch/apidocs/org/springframework/batch/test/MetaDataInstanceFactory.html ([https](https://docs.spring.io/spring-batch/apidocs/org/springframework/batch/test/MetaDataInstanceFactory.html) result 301). * [ ] http://docs.spring.io/spring-batch/reference/html/ with 1 occurrences migrated to: https://docs.spring.io/spring-batch/reference/html/ ([https](https://docs.spring.io/spring-batch/reference/html/) result 301). * [ ] http://docs.spring.io/spring-batch/reference/html/scalability.html with 1 occurrences migrated to: https://docs.spring.io/spring-batch/reference/html/scalability.html ([https](https://docs.spring.io/spring-batch/reference/html/scalability.html) result 301). * [ ] http://docs.spring.io/spring-batch/trunk/reference/ with 1 occurrences migrated to: https://docs.spring.io/spring-batch/trunk/reference/ ([https](https://docs.spring.io/spring-batch/trunk/reference/) result 301). * [ ] http://docs.spring.io/spring-batch/trunk/reference/html/index.html with 1 occurrences migrated to: https://docs.spring.io/spring-batch/trunk/reference/html/index.html ([https](https://docs.spring.io/spring-batch/trunk/reference/html/index.html) result 301). * [ ] http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/interceptor/TransactionAttributeEditor.html with 1 occurrences migrated to: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/interceptor/TransactionAttributeEditor.html ([https](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/interceptor/TransactionAttributeEditor.html) result 301). * [ ] http://forum.spring.io/forum/spring-projects/batch with 2 occurrences migrated to: https://forum.spring.io/forum/spring-projects/batch ([https](https://forum.spring.io/forum/spring-projects/batch) result 301). * [ ] http://help.github.com/fork-a-repo/ with 2 occurrences migrated to: https://help.github.com/fork-a-repo/ ([https](https://help.github.com/fork-a-repo/) result 301). * [ ] http://help.github.com/forking/ with 1 occurrences migrated to: https://help.github.com/forking/ ([https](https://help.github.com/forking/) result 301). * [ ] http://help.github.com/send-pull-requests/ with 2 occurrences migrated to: https://help.github.com/send-pull-requests/ ([https](https://help.github.com/send-pull-requests/) result 301). * [ ] http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_ with 1 occurrences migrated to: https://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_ ([https](https://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_) result 301). * [ ] http://jquery.org/license with 2 occurrences migrated to: https://jquery.org/license ([https](https://jquery.org/license) result 301). * [ ] http://springframework.org/batch/sample/io/oxm/domain with 3 occurrences migrated to: https://springframework.org/batch/sample/io/oxm/domain ([https](https://springframework.org/batch/sample/io/oxm/domain) result 301). * [ ] http://www.eaipatterns.com/toc.html (302) with 1 occurrences migrated to: https://www.enterpriseintegrationpatterns.com/toc.html ([https](https://www.eaipatterns.com/toc.html) result 301). * [ ] http://www.spring.io with 1 occurrences migrated to: https://www.spring.io ([https](https://www.spring.io) result 301). * [ ] http://www.spring.io/ with 2 occurrences migrated to: https://www.spring.io/ ([https](https://www.spring.io/) result 301). * [ ] http://www.springframework.org/schema/batch-integration/spring-batch-integration.xsd with 2 occurrences migrated to: https://www.springframework.org/schema/batch-integration/spring-batch-integration.xsd ([https](https://www.springframework.org/schema/batch-integration/spring-batch-integration.xsd) result 301). * [ ] http://www.springframework.org/schema/batch/spring-batch.xsd with 2 occurrences migrated to: https://www.springframework.org/schema/batch/spring-batch.xsd ([https](https://www.springframework.org/schema/batch/spring-batch.xsd) result 301). * [ ] http://www.springframework.org/schema/beans/spring-beans-2.5.xsd with 3 occurrences migrated to: https://www.springframework.org/schema/beans/spring-beans-2.5.xsd ([https](https://www.springframework.org/schema/beans/spring-beans-2.5.xsd) result 301). * [ ] http://www.springframework.org/schema/beans/spring-beans-3.1.xsd with 1 occurrences migrated to: https://www.springframework.org/schema/beans/spring-beans-3.1.xsd ([https](https://www.springframework.org/schema/beans/spring-beans-3.1.xsd) result 301). * [ ] http://www.springframework.org/schema/beans/spring-beans-3.2.xsd with 1 occurrences migrated to: https://www.springframework.org/schema/beans/spring-beans-3.2.xsd ([https](https://www.springframework.org/schema/beans/spring-beans-3.2.xsd) result 301). * [ ] http://www.springframework.org/schema/tool/spring-tool-2.5.xsd with 1 occurrences migrated to: https://www.springframework.org/schema/tool/spring-tool-2.5.xsd ([https](https://www.springframework.org/schema/tool/spring-tool-2.5.xsd) result 301). * [ ] http://www.springframework.org/schema/tool/spring-tool-3.1.xsd with 2 occurrences migrated to: https://www.springframework.org/schema/tool/spring-tool-3.1.xsd ([https](https://www.springframework.org/schema/tool/spring-tool-3.1.xsd) result 301). * [ ] http://www.springframework.org/test with 34 occurrences migrated to: https://www.springframework.org/test ([https](https://www.springframework.org/test) result 301). * [ ] http://www.w3.org/TR/css3-selectors/ with 2 occurrences migrated to: https://www.w3.org/TR/css3-selectors/ ([https](https://www.w3.org/TR/css3-selectors/) result 301). * [ ] http://www.w3.org/TR/css3-syntax/ with 1 occurrences migrated to: https://www.w3.org/TR/css3-syntax/ ([https](https://www.w3.org/TR/css3-syntax/) result 301). * [ ] http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx with 2 occurrences migrated to: https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx ([https](https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx) result 302). * [ ] http://sourceforge.net/projects/cglib/files/cglib3/3.0/cglib-3.0.jar/download with 1 occurrences migrated to: https://sourceforge.net/projects/cglib/files/cglib3/3.0/cglib-3.0.jar/download ([https](https://sourceforge.net/projects/cglib/files/cglib3/3.0/cglib-3.0.jar/download) result 302). * [ ] http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context with 1 occurrences migrated to: https://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context ([https](https://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context) result 302). * [ ] http://www.springsource.org/download with 1 occurrences migrated to: https://www.springsource.org/download ([https](https://www.springsource.org/download) result 302). # Ignored These URLs were intentionally ignored. * http://test with 2 occurrences * http://www.springframework.org/schema/batch with 15 occurrences * http://www.springframework.org/schema/batch-integration with 8 occurrences * http://www.springframework.org/schema/batch/test with 2 occurrences * http://www.springframework.org/schema/beans with 26 occurrences * http://www.springframework.org/schema/integration with 6 occurrences * http://www.springframework.org/schema/tool with 15 occurrences * http://www.w3.org/1999/XSL/Format with 4 occurrences * http://www.w3.org/1999/XSL/Transform with 3 occurrences * http://www.w3.org/2001/XMLSchema with 10 occurrences * http://www.w3.org/2001/XMLSchema-instance with 12 occurrences * http://xmlns.jcp.org/xml/ns/javaee with 8 occurrences
1353 lines
44 KiB
Plaintext
1353 lines
44 KiB
Plaintext
:batch-asciidoc: ./
|
|
:toc: left
|
|
:toclevels: 4
|
|
|
|
[[springBatchIntegration]]
|
|
|
|
== Spring Batch Integration
|
|
|
|
ifndef::onlyonetoggle[]
|
|
include::toggle.adoc[]
|
|
endif::onlyonetoggle[]
|
|
|
|
[[spring-batch-integration-introduction]]
|
|
|
|
=== Spring Batch Integration Introduction
|
|
|
|
Many users of Spring Batch may encounter requirements that are
|
|
outside the scope of Spring Batch but that may be efficiently and
|
|
concisely implemented by using Spring Integration. Conversely, Spring
|
|
Integration users may encounter Spring Batch requirements and need a way
|
|
to efficiently integrate both frameworks. In this context, several
|
|
patterns and use-cases emerge, and Spring Batch Integration
|
|
addresses those requirements.
|
|
|
|
The line between Spring Batch and Spring Integration is not always
|
|
clear, but two pieces of advice can
|
|
help: Think about granularity, and apply common patterns. Some
|
|
of those common patterns are described in this reference manual
|
|
section.
|
|
|
|
Adding messaging to a batch process enables automation of
|
|
operations and also separation and strategizing of key concerns.
|
|
For example, a message might trigger a job to execute, and then the
|
|
sending of the message can be exposed in a variety of ways. Alternatively, when
|
|
a job completes or fails, that event might trigger a message to be sent,
|
|
and the consumers of those messages might have operational concerns
|
|
that have nothing to do with the application itself. Messaging can
|
|
also be embedded in a job (for example reading or writing items for
|
|
processing via channels). Remote partitioning and remote chunking
|
|
provide methods to distribute workloads over a number of workers.
|
|
|
|
|
|
This section covers the following key concepts:
|
|
|
|
[role="xmlContent"]
|
|
* <<spring-batch-integration.adoc#namespace-support,Namespace Support>>
|
|
|
|
[[continue-section-list]]
|
|
* <<spring-batch-integration.adoc#launching-batch-jobs-through-messages,Launching Batch Jobs through Messages>>
|
|
|
|
|
|
|
|
* <<spring-batch-integration.adoc#providing-feedback-with-informational-messages,Providing Feedback with Informational Messages>>
|
|
|
|
|
|
|
|
* <<spring-batch-integration.adoc#asynchronous-processors,Asynchronous Processors>>
|
|
|
|
|
|
|
|
* <<spring-batch-integration.adoc#externalizing-batch-process-execution,Externalizing
|
|
Batch Process Execution>>
|
|
|
|
|
|
[[namespace-support]]
|
|
[role="xmlContent"]
|
|
==== Namespace Support
|
|
|
|
Since Spring Batch Integration 1.3, dedicated XML Namespace
|
|
support was added, with the aim to provide an easier configuration
|
|
experience. In order to activate the namespace, add the following
|
|
namespace declarations to your Spring XML Application Context
|
|
file:
|
|
|
|
[source, xml]
|
|
----
|
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:batch-int="http://www.springframework.org/schema/batch-integration"
|
|
xsi:schemaLocation="
|
|
http://www.springframework.org/schema/batch-integration
|
|
https://www.springframework.org/schema/batch-integration/spring-batch-integration.xsd">
|
|
|
|
...
|
|
|
|
</beans>
|
|
----
|
|
|
|
A fully configured Spring XML Application Context file for Spring
|
|
Batch Integration may look like the following:
|
|
|
|
[source, xml]
|
|
----
|
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:int="http://www.springframework.org/schema/integration"
|
|
xmlns:batch="http://www.springframework.org/schema/batch"
|
|
xmlns:batch-int="http://www.springframework.org/schema/batch-integration"
|
|
xsi:schemaLocation="
|
|
http://www.springframework.org/schema/batch-integration
|
|
https://www.springframework.org/schema/batch-integration/spring-batch-integration.xsd
|
|
http://www.springframework.org/schema/batch
|
|
https://www.springframework.org/schema/batch/spring-batch.xsd
|
|
http://www.springframework.org/schema/beans
|
|
https://www.springframework.org/schema/beans/spring-beans.xsd
|
|
http://www.springframework.org/schema/integration
|
|
https://www.springframework.org/schema/integration/spring-integration.xsd">
|
|
|
|
...
|
|
|
|
</beans>
|
|
----
|
|
|
|
Appending version numbers to the referenced XSD file is also
|
|
allowed, but, as a version-less declaration always uses the
|
|
latest schema, we generally do not recommend appending the version
|
|
number to the XSD name. Adding a version number
|
|
could possibly create issues when updating the Spring Batch
|
|
Integration dependencies, as they may require more recent versions
|
|
of the XML schema.
|
|
|
|
|
|
[[launching-batch-jobs-through-messages]]
|
|
|
|
==== Launching Batch Jobs through Messages
|
|
|
|
|
|
When starting batch jobs by using the core Spring Batch API, you
|
|
basically have 2 options:
|
|
|
|
* From the command line, with the `CommandLineJobRunner`
|
|
* Programmatically, with either `JobOperator.start()` or `JobLauncher.run()`
|
|
|
|
|
|
|
|
For example, you may want to use the
|
|
`CommandLineJobRunner` when invoking Batch Jobs by
|
|
using a shell script. Alternatively, you may use the
|
|
`JobOperator` directly (for example, when using
|
|
Spring Batch as part of a web application). However, what about
|
|
more complex use cases? Maybe you need to poll a remote (S)FTP
|
|
server to retrieve the data for the Batch Job or your application
|
|
has to support multiple different data sources simultaneously. For
|
|
example, you may receive data files not only from the web, but also from
|
|
FTP and other sources. Maybe additional transformation of the input files is
|
|
needed before invoking Spring Batch.
|
|
|
|
|
|
|
|
Therefore, it would be much more powerful to execute the batch job
|
|
using Spring Integration and its numerous adapters. For example,
|
|
you can use a __File Inbound Channel Adapter__ to
|
|
monitor a directory in the file-system and start the Batch Job as
|
|
soon as the input file arrives. Additionally, you can create Spring
|
|
Integration flows that use multiple different adapters to easily
|
|
ingest data for your batch jobs from multiple sources
|
|
simultaneously using only configuration. Implementing all these
|
|
scenarios with Spring Integration is easy, as it allows for
|
|
decoupled, event-driven execution of the
|
|
`JobLauncher`.
|
|
|
|
|
|
|
|
Spring Batch Integration provides the
|
|
`JobLaunchingMessageHandler` class that you can
|
|
use to launch batch jobs. The input for the
|
|
`JobLaunchingMessageHandler` is provided by a
|
|
Spring Integration message, which has a payload of type
|
|
`JobLaunchRequest`. This class is a wrapper around the `Job`
|
|
that needs to be launched and around the `JobParameters`
|
|
necessary to launch the Batch job.
|
|
|
|
|
|
|
|
The following image illustrates the typical Spring Integration
|
|
message flow in order to start a Batch job. The
|
|
link:$$https://www.enterpriseintegrationpatterns.com/toc.html$$[EIP (Enterprise Integration Patterns) website]
|
|
provides a full overview of messaging icons and their descriptions.
|
|
|
|
.Launch Batch Job
|
|
image::{batch-asciidoc}images/launch-batch-job.png[Launch Batch Job, scaledwidth="60%"]
|
|
|
|
|
|
[[transforming-a-file-into-a-joblaunchrequest]]
|
|
|
|
===== Transforming a file into a JobLaunchRequest
|
|
|
|
|
|
[source, java]
|
|
----
|
|
package io.spring.sbi;
|
|
|
|
import org.springframework.batch.core.Job;
|
|
import org.springframework.batch.core.JobParametersBuilder;
|
|
import org.springframework.batch.integration.launch.JobLaunchRequest;
|
|
import org.springframework.integration.annotation.Transformer;
|
|
import org.springframework.messaging.Message;
|
|
|
|
import java.io.File;
|
|
|
|
public class FileMessageToJobRequest {
|
|
private Job job;
|
|
private String fileParameterName;
|
|
|
|
public void setFileParameterName(String fileParameterName) {
|
|
this.fileParameterName = fileParameterName;
|
|
}
|
|
|
|
public void setJob(Job job) {
|
|
this.job = job;
|
|
}
|
|
|
|
@Transformer
|
|
public JobLaunchRequest toRequest(Message<File> message) {
|
|
JobParametersBuilder jobParametersBuilder =
|
|
new JobParametersBuilder();
|
|
|
|
jobParametersBuilder.addString(fileParameterName,
|
|
message.getPayload().getAbsolutePath());
|
|
|
|
return new JobLaunchRequest(job, jobParametersBuilder.toJobParameters());
|
|
}
|
|
}
|
|
----
|
|
|
|
[[the-jobexecution-response]]
|
|
|
|
===== The `JobExecution` Response
|
|
|
|
When a batch job is being executed, a
|
|
`JobExecution` instance is returned. This
|
|
instance can be used to determine the status of an execution. If
|
|
a `JobExecution` is able to be created
|
|
successfully, it is always returned, regardless of whether
|
|
or not the actual execution is successful.
|
|
|
|
|
|
|
|
The exact behavior on how the `JobExecution`
|
|
instance is returned depends on the provided
|
|
`TaskExecutor`. If a
|
|
`synchronous` (single-threaded)
|
|
`TaskExecutor` implementation is used, the
|
|
`JobExecution` response is returned only
|
|
`after` the job completes. When using an
|
|
`asynchronous`
|
|
`TaskExecutor`, the
|
|
`JobExecution` instance is returned
|
|
immediately. Users can then take the `id` of
|
|
`JobExecution` instance
|
|
(with `JobExecution.getJobId()`) and query the
|
|
`JobRepository` for the job's updated status
|
|
using the `JobExplorer`. For more
|
|
information, please refer to the Spring
|
|
Batch reference documentation on
|
|
<<job.adoc#queryingRepository,Querying the Repository>>.
|
|
|
|
|
|
|
|
[[spring-batch-integration-configuration]]
|
|
|
|
===== Spring Batch Integration Configuration
|
|
|
|
The following configuration creates a file
|
|
`inbound-channel-adapter` to listen for CSV
|
|
files in the provided directory, hand them off to our
|
|
transformer (`FileMessageToJobRequest`),
|
|
launch the job via the __Job Launching Gateway__, and then log the output of the
|
|
`JobExecution` with the
|
|
`logging-channel-adapter`.
|
|
|
|
.XML Configuration
|
|
[source, xml, role="xmlContent"]
|
|
----
|
|
<int:channel id="inboundFileChannel"/>
|
|
<int:channel id="outboundJobRequestChannel"/>
|
|
<int:channel id="jobLaunchReplyChannel"/>
|
|
|
|
<int-file:inbound-channel-adapter id="filePoller"
|
|
channel="inboundFileChannel"
|
|
directory="file:/tmp/myfiles/"
|
|
filename-pattern="*.csv">
|
|
<int:poller fixed-rate="1000"/>
|
|
</int-file:inbound-channel-adapter>
|
|
|
|
<int:transformer input-channel="inboundFileChannel"
|
|
output-channel="outboundJobRequestChannel">
|
|
<bean class="io.spring.sbi.FileMessageToJobRequest">
|
|
<property name="job" ref="personJob"/>
|
|
<property name="fileParameterName" value="input.file.name"/>
|
|
</bean>
|
|
</int:transformer>
|
|
|
|
<batch-int:job-launching-gateway request-channel="outboundJobRequestChannel"
|
|
reply-channel="jobLaunchReplyChannel"/>
|
|
|
|
<int:logging-channel-adapter channel="jobLaunchReplyChannel"/>
|
|
----
|
|
|
|
.Java Configuration
|
|
[source, java, role="javaContent"]
|
|
----
|
|
@Bean
|
|
public FileMessageToJobRequest fileMessageToJobRequest() {
|
|
FileMessageToJobRequest fileMessageToJobRequest = new FileMessageToJobRequest();
|
|
fileMessageToJobRequest.setFileParameterName("input.file.name");
|
|
fileMessageToJobRequest.setJob(personJob());
|
|
return fileMessageToJobRequest;
|
|
}
|
|
|
|
@Bean
|
|
public JobLaunchingGateway jobLaunchingGateway() {
|
|
SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
|
|
simpleJobLauncher.setJobRepository(jobRepository);
|
|
simpleJobLauncher.setTaskExecutor(new SyncTaskExecutor());
|
|
JobLaunchingGateway jobLaunchingGateway = new JobLaunchingGateway(simpleJobLauncher);
|
|
|
|
return jobLaunchingGateway;
|
|
}
|
|
|
|
@Bean
|
|
public IntegrationFlow integrationFlow(JobLaunchingGateway jobLaunchingGateway) {
|
|
return IntegrationFlows.from(Files.inboundAdapter(new File("/tmp/myfiles")).
|
|
filter(new SimplePatternFileListFilter("*.csv")),
|
|
c -> c.poller(Pollers.fixedRate(1000).maxMessagesPerPoll(1))).
|
|
handle(fileMessageToJobRequest()).
|
|
handle(jobLaunchingGateway).
|
|
log(LoggingHandler.Level.WARN, "headers.id + ': ' + payload").
|
|
get();
|
|
}
|
|
----
|
|
|
|
|
|
[[example-itemreader-configuration]]
|
|
|
|
===== Example ItemReader Configuration
|
|
|
|
Now that we are polling for files and launching jobs, we need to
|
|
configure our Spring Batch
|
|
`ItemReader` (for example) to use the files found at the location defined
|
|
by the job parameter called "input.file.name", as shown in the following bean configuration:
|
|
|
|
.XML Configuration
|
|
[source, xml, role="xmlContent"]
|
|
----
|
|
<bean id="itemReader" class="org.springframework.batch.item.file.FlatFileItemReader"
|
|
scope="step">
|
|
<property name="resource" value="file://#{jobParameters['input.file.name']}"/>
|
|
...
|
|
</bean>
|
|
----
|
|
|
|
.Java Configuration
|
|
[source, java, role="javaContent"]
|
|
----
|
|
@Bean
|
|
@StepScope
|
|
public ItemReader sampleReader(@Value("#{jobParameters[input.file.name]}") String resource) {
|
|
...
|
|
FlatFileItemReader flatFileItemReader = new FlatFileItemReader();
|
|
flatFileItemReader.setResource(new FileSystemResource(resource));
|
|
...
|
|
return flatFileItemReader;
|
|
}
|
|
----
|
|
|
|
The main points of interest in the preceding example are injecting the value of
|
|
`#{jobParameters['input.file.name']}`
|
|
as the Resource property value and setting the `ItemReader` bean
|
|
to have __Step scope__. Setting the bean to have Step scope takes advantage of
|
|
the late binding support, which allows access to the
|
|
`jobParameters` variable.
|
|
|
|
|
|
[[availableAttributesOfTheJobLaunchingGateway]]
|
|
=== Available Attributes of the Job-Launching Gateway
|
|
|
|
The job-launching gateway has the following attributes that you can set to control a job:
|
|
|
|
* `id`: Identifies the underlying Spring bean definition, which is an instance of either:
|
|
** `EventDrivenConsumer`
|
|
** `PollingConsumer`
|
|
(The exact implementation depends on whether the component's input channel is a
|
|
`SubscribableChannel` or `PollableChannel`.)
|
|
* `auto-startup`: Boolean flag to indicate that the endpoint should start automatically on
|
|
startup. The default is __true__.
|
|
* `request-channel`: The input `MessageChannel` of this endpoint.
|
|
* `reply-channel`: `MessageChannel` to which the resulting `JobExecution` payload is sent.
|
|
* `reply-timeout`: Lets you specify how long (in milliseconds) this gateway waits for the reply message
|
|
to be sent successfully to the reply channel before throwing
|
|
an exception. This attribute only applies when the channel
|
|
might block (for example, when using a bounded queue channel
|
|
that is currently full). Also, keep in mind that, when sending to a
|
|
`DirectChannel`, the invocation occurs
|
|
in the sender's thread. Therefore, the failing of the send
|
|
operation may be caused by other components further downstream.
|
|
The `reply-timeout` attribute maps to the
|
|
`sendTimeout` property of the underlying
|
|
`MessagingTemplate` instance. If not specified, the attribute
|
|
defaults to<emphasis>-1</emphasis>,
|
|
meaning that, by default, the `Gateway` waits indefinitely.
|
|
* `job-launcher`: Optional. Accepts a
|
|
custom
|
|
`JobLauncher`
|
|
bean reference.
|
|
If not specified the adapter
|
|
re-uses the instance that is registered under the `id` of
|
|
`jobLauncher`. If no default instance
|
|
exists, an exception is thrown.
|
|
* `order`: Specifies the order of invocation when this endpoint is connected as a subscriber
|
|
to a `SubscribableChannel`.
|
|
|
|
=== Sub-Elements
|
|
When this `Gateway` is receiving messages from a
|
|
`PollableChannel`, you must either provide
|
|
a global default `Poller` or provide a `Poller` sub-element to the
|
|
`Job Launching Gateway`, as shown in the following example:
|
|
|
|
.XML Configuration
|
|
[source, xml, role="xmlContent"]
|
|
----
|
|
<batch-int:job-launching-gateway request-channel="queueChannel"
|
|
reply-channel="replyChannel" job-launcher="jobLauncher">
|
|
<int:poller fixed-rate="1000">
|
|
</batch-int:job-launching-gateway>
|
|
----
|
|
|
|
.Java Configuration
|
|
[source, java, role="javaContent"]
|
|
----
|
|
@Bean
|
|
@ServiceActivator(inputChannel = "queueChannel", poller = @Poller(fixedRate="1000"))
|
|
public JobLaunchingGateway sampleJobLaunchingGateway() {
|
|
JobLaunchingGateway jobLaunchingGateway = new JobLaunchingGateway(jobLauncher());
|
|
jobLaunchingGateway.setOutputChannel(replyChannel());
|
|
return jobLaunchingGateway;
|
|
}
|
|
----
|
|
|
|
[[providing-feedback-with-informational-messages]]
|
|
|
|
==== Providing Feedback with Informational Messages
|
|
|
|
|
|
As Spring Batch jobs can run for long times, providing progress
|
|
information is often critical. For example, stake-holders may want
|
|
to be notified if some or all parts of a batch job have failed.
|
|
Spring Batch provides support for this information being gathered
|
|
through:
|
|
|
|
|
|
|
|
* Active polling
|
|
|
|
* Event-driven listeners
|
|
|
|
|
|
When starting a Spring Batch job asynchronously (for example, by using the
|
|
`Job Launching Gateway`), a
|
|
`JobExecution` instance is returned. Thus,
|
|
`JobExecution.getJobId()` can be used to
|
|
continuously poll for status updates by retrieving updated
|
|
instances of the `JobExecution` from the
|
|
`JobRepository` by using the
|
|
`JobExplorer`. However, this is considered
|
|
sub-optimal, and an event-driven approach should be preferred.
|
|
|
|
|
|
Therefore, Spring Batch provides listeners, including the three most commonly used listeners:
|
|
|
|
* StepListener
|
|
* ChunkListener
|
|
* JobExecutionListener
|
|
|
|
In the example shown in the following image, a Spring Batch job has been configured with a
|
|
`StepExecutionListener`. Thus, Spring
|
|
Integration receives and processes any step before or after
|
|
events. For example, the received
|
|
`StepExecution` can be inspected by using a
|
|
`Router`. Based on the results of that
|
|
inspection, various things can occur (such as routing a message
|
|
to a Mail Outbound Channel Adapter), so that an Email notification
|
|
can be sent out based on some condition.
|
|
|
|
.Handling Informational Messages
|
|
image::{batch-asciidoc}images/handling-informational-messages.png[Handling Informational Messages, scaledwidth="60%"]
|
|
|
|
|
|
The following two-part example shows how a listener is configured to send a
|
|
message to a `Gateway` for a
|
|
`StepExecution` events and log its output to a
|
|
`logging-channel-adapter`.
|
|
|
|
First, create the notification integration beans:
|
|
|
|
.XML Configuration
|
|
[source, xml, role="xmlContent"]
|
|
----
|
|
<int:channel id="stepExecutionsChannel"/>
|
|
|
|
<int:gateway id="notificationExecutionsListener"
|
|
service-interface="org.springframework.batch.core.StepExecutionListener"
|
|
default-request-channel="stepExecutionsChannel"/>
|
|
|
|
<int:logging-channel-adapter channel="stepExecutionsChannel"/>
|
|
----
|
|
|
|
.Java Configuration
|
|
[source, java, role="javaContent"]
|
|
----
|
|
@Bean
|
|
@ServiceActivator(inputChannel = "stepExecutionsChannel")
|
|
public LoggingHandler loggingHandler() {
|
|
LoggingHandler adapter = new LoggingHandler(LoggingHandler.Level.WARN);
|
|
adapter.setLoggerName("TEST_LOGGER");
|
|
adapter.setLogExpressionString("headers.id + ': ' + payload");
|
|
return adapter;
|
|
}
|
|
|
|
@MessagingGateway(name = "notificationExecutionsListener", defaultRequestChannel = "stepExecutionsChannel")
|
|
public interface NotificationExecutionListener extends StepExecutionListener {}
|
|
----
|
|
|
|
[role="javaContent"]
|
|
NOTE: You will need to add the `@IntegrationComponentScan` annotation to your configuration.
|
|
|
|
[[message-gateway-entry-list]]
|
|
|
|
Second, modify your job to add a step-level listener:
|
|
|
|
.XML Configuration
|
|
[source, xml, role="xmlContent"]
|
|
----
|
|
<job id="importPayments">
|
|
<step id="step1">
|
|
<tasklet ../>
|
|
<chunk ../>
|
|
<listeners>
|
|
<listener ref="notificationExecutionsListener"/>
|
|
</listeners>
|
|
</tasklet>
|
|
...
|
|
</step>
|
|
</job>
|
|
----
|
|
|
|
.Java Configuration
|
|
[source, java, role="javaContent"]
|
|
----
|
|
public Job importPaymentsJob() {
|
|
return jobBuilderFactory.get("importPayments")
|
|
.start(stepBuilderFactory.get("step1")
|
|
.chunk(200)
|
|
.listener(notificationExecutionsListener())
|
|
...
|
|
}
|
|
----
|
|
|
|
[[asynchronous-processors]]
|
|
|
|
==== Asynchronous Processors
|
|
|
|
|
|
Asynchronous Processors help you to scale the processing of
|
|
items. In the asynchronous processor use case, an
|
|
`AsyncItemProcessor` serves as a dispatcher,
|
|
executing the logic of the `ItemProcessor` for an
|
|
item on a new thread. Once the item completes, the `Future` is passed to
|
|
the `AsynchItemWriter` to be written.
|
|
|
|
|
|
|
|
Therefore, you can increase performance by using asynchronous item
|
|
processing, basically allowing you to implement
|
|
__fork-join__ scenarios. The
|
|
`AsyncItemWriter` gathers the results and
|
|
writes back the chunk as soon as all the results become available.
|
|
|
|
|
|
|
|
The following example shows how to configuration the `AsyncItemProcessor`:
|
|
|
|
|
|
.XML Configuration
|
|
[source, xml, role="xmlContent"]
|
|
----
|
|
<bean id="processor"
|
|
class="org.springframework.batch.integration.async.AsyncItemProcessor">
|
|
<property name="delegate">
|
|
<bean class="your.ItemProcessor"/>
|
|
</property>
|
|
<property name="taskExecutor">
|
|
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
|
|
</property>
|
|
</bean>
|
|
----
|
|
|
|
.Java Configuration
|
|
[source, java, role="javaContent"]
|
|
----
|
|
@Bean
|
|
public AsyncItemProcessor processor(ItemProcessor itemProcessor, TaskExecutor taskExecutor) {
|
|
AsyncItemProcessor asyncItemProcessor = new AsyncItemProcessor();
|
|
asyncItemProcessor.setTaskExecutor(taskExecutor);
|
|
asyncItemProcessor.setDelegate(itemProcessor);
|
|
return asyncItemProcessor;
|
|
}
|
|
----
|
|
|
|
The `delegate` property refers
|
|
to your `ItemProcessor` bean, and
|
|
the `taskExecutor` property
|
|
refers to the `TaskExecutor` of your choice.
|
|
|
|
The following example shows how to configure the `AsyncItemWriter`:
|
|
|
|
.XML Configuration
|
|
[source, xml, role="xmlContent"]
|
|
----
|
|
<bean id="itemWriter"
|
|
class="org.springframework.batch.integration.async.AsyncItemWriter">
|
|
<property name="delegate">
|
|
<bean id="itemWriter" class="your.ItemWriter"/>
|
|
</property>
|
|
</bean>
|
|
----
|
|
|
|
.Java Configuration
|
|
[source, java, role="javaContent"]
|
|
----
|
|
@Bean
|
|
public AsyncItemWriter writer(ItemWriter itemWriter) {
|
|
AsyncItemWriter asyncItemWriter = new AsyncItemWriter();
|
|
asyncItemWriter.setDelegate(itemWriter);
|
|
return asyncItemWriter;
|
|
}
|
|
----
|
|
|
|
Again, the `delegate` property is
|
|
actually a reference to your `ItemWriter` bean.
|
|
|
|
|
|
[[externalizing-batch-process-execution]]
|
|
|
|
==== Externalizing Batch Process Execution
|
|
|
|
|
|
The integration approaches discussed so far suggest use cases
|
|
where Spring Integration wraps Spring Batch like an outer-shell.
|
|
However, Spring Batch can also use Spring Integration internally.
|
|
Using this approach, Spring Batch users can delegate the
|
|
processing of items or even chunks to outside processes. This
|
|
allows you to offload complex processing. Spring Batch Integration
|
|
provides dedicated support for:
|
|
|
|
|
|
|
|
* Remote Chunking
|
|
|
|
|
|
|
|
* Remote Partitioning
|
|
|
|
|
|
[[remote-chunking]]
|
|
|
|
===== Remote Chunking
|
|
|
|
.Remote Chunking
|
|
image::{batch-asciidoc}images/remote-chunking-sbi.png[Remote Chunking, scaledwidth="60%"]
|
|
|
|
Taking things one step further, one can also externalize the
|
|
chunk processing by using the
|
|
`ChunkMessageChannelItemWriter`
|
|
(provided by Spring Batch Integration), which sends items out
|
|
and collects the result. Once sent, Spring Batch continues the
|
|
process of reading and grouping items, without waiting for the results.
|
|
Rather, it is the responsibility of the `ChunkMessageChannelItemWriter`
|
|
to gather the results and integrate them back into the Spring Batch process.
|
|
|
|
|
|
With Spring Integration, you have full
|
|
control over the concurrency of your processes (for instance, by
|
|
using a `QueueChannel` instead of a
|
|
`DirectChannel`). Furthermore, by relying on
|
|
Spring Integration's rich collection of Channel Adapters (such as
|
|
JMS and AMQP), you can distribute chunks of a Batch job to
|
|
external systems for processing.
|
|
|
|
A simple job with a step to be remotely chunked might have a
|
|
configuration similar to the following:
|
|
|
|
.XML Configuration
|
|
[source, xml, role="xmlContent"]
|
|
----
|
|
<job id="personJob">
|
|
<step id="step1">
|
|
<tasklet>
|
|
<chunk reader="itemReader" writer="itemWriter" commit-interval="200"/>
|
|
</tasklet>
|
|
...
|
|
</step>
|
|
</job>
|
|
----
|
|
|
|
.Java Configuration
|
|
[source, java, role="javaContent"]
|
|
----
|
|
public Job chunkJob() {
|
|
return jobBuilderFactory.get("personJob")
|
|
.start(stepBuilderFactory.get("step1")
|
|
.<Person, Person>chunk(200)
|
|
.reader(itemReader())
|
|
.writer(itemWriter())
|
|
.build())
|
|
.build();
|
|
}
|
|
----
|
|
|
|
The `ItemReader` reference points to the bean you want
|
|
to use for reading data on the master. The `ItemWriter` reference
|
|
points to a special `ItemWriter`
|
|
(called `ChunkMessageChannelItemWriter`),
|
|
as described above. The processor (if any) is left off the
|
|
master configuration, as it is configured on the worker. The
|
|
following configuration provides a basic master setup. You
|
|
should check any additional component properties, such as
|
|
throttle limits and so on, when implementing your use case.
|
|
|
|
.XML Configuration
|
|
[source, xml, role="xmlContent"]
|
|
----
|
|
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
|
|
<property name="brokerURL" value="tcp://localhost:61616"/>
|
|
</bean>
|
|
|
|
<int-jms:outbound-channel-adapter id="jmsRequests" destination-name="requests"/>
|
|
|
|
<bean id="messagingTemplate"
|
|
class="org.springframework.integration.core.MessagingTemplate">
|
|
<property name="defaultChannel" ref="requests"/>
|
|
<property name="receiveTimeout" value="2000"/>
|
|
</bean>
|
|
|
|
<bean id="itemWriter"
|
|
class="org.springframework.batch.integration.chunk.ChunkMessageChannelItemWriter"
|
|
scope="step">
|
|
<property name="messagingOperations" ref="messagingTemplate"/>
|
|
<property name="replyChannel" ref="replies"/>
|
|
</bean>
|
|
|
|
<int:channel id="replies">
|
|
<int:queue/>
|
|
</int:channel>
|
|
|
|
<int-jms:message-driven-channel-adapter id="jmsReplies"
|
|
destination-name="replies"
|
|
channel="replies"/>
|
|
----
|
|
|
|
.Java Configuration
|
|
[source, java, role="javaContent"]
|
|
----
|
|
@Bean
|
|
public org.apache.activemq.ActiveMQConnectionFactory connectionFactory() {
|
|
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
|
|
factory.setBrokerURL("tcp://localhost:61616");
|
|
return factory;
|
|
}
|
|
|
|
/*
|
|
* Configure outbound flow (requests going to workers)
|
|
*/
|
|
@Bean
|
|
public DirectChannel requests() {
|
|
return new DirectChannel();
|
|
}
|
|
|
|
@Bean
|
|
public IntegrationFlow outboundFlow(ActiveMQConnectionFactory connectionFactory) {
|
|
return IntegrationFlows
|
|
.from(requests())
|
|
.handle(Jms.outboundAdapter(connectionFactory).destination("requests"))
|
|
.get();
|
|
}
|
|
|
|
/*
|
|
* Configure inbound flow (replies coming from workers)
|
|
*/
|
|
@Bean
|
|
public QueueChannel replies() {
|
|
return new QueueChannel();
|
|
}
|
|
|
|
@Bean
|
|
public IntegrationFlow inboundFlow(ActiveMQConnectionFactory connectionFactory) {
|
|
return IntegrationFlows
|
|
.from(Jms.messageDrivenChannelAdapter(connectionFactory).destination("replies"))
|
|
.channel(replies())
|
|
.get();
|
|
}
|
|
|
|
/*
|
|
* Configure the ChunkMessageChannelItemWriter
|
|
*/
|
|
@Bean
|
|
public ItemWriter<Integer> itemWriter() {
|
|
MessagingTemplate messagingTemplate = new MessagingTemplate();
|
|
messagingTemplate.setDefaultChannel(requests());
|
|
messagingTemplate.setReceiveTimeout(2000);
|
|
ChunkMessageChannelItemWriter<Integer> chunkMessageChannelItemWriter
|
|
= new ChunkMessageChannelItemWriter<>();
|
|
chunkMessageChannelItemWriter.setMessagingOperations(messagingTemplate);
|
|
chunkMessageChannelItemWriter.setReplyChannel(replies());
|
|
return chunkMessageChannelItemWriter;
|
|
}
|
|
----
|
|
|
|
The preceding configuration provides us with a number of beans. We
|
|
configure our messaging middleware using ActiveMQ and the
|
|
inbound/outbound JMS adapters provided by Spring Integration. As
|
|
shown, our `itemWriter` bean, which is
|
|
referenced by our job step, uses the
|
|
`ChunkMessageChannelItemWriter` for writing chunks over the
|
|
configured middleware.
|
|
|
|
Now we can move on to the worker configuration, as shown in the following example:
|
|
|
|
|
|
.XML Configuration
|
|
[source, xml, role="xmlContent"]
|
|
----
|
|
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
|
|
<property name="brokerURL" value="tcp://localhost:61616"/>
|
|
</bean>
|
|
|
|
<int:channel id="requests"/>
|
|
<int:channel id="replies"/>
|
|
|
|
<int-jms:message-driven-channel-adapter id="incomingRequests"
|
|
destination-name="requests"
|
|
channel="requests"/>
|
|
|
|
<int-jms:outbound-channel-adapter id="outgoingReplies"
|
|
destination-name="replies"
|
|
channel="replies">
|
|
</int-jms:outbound-channel-adapter>
|
|
|
|
<int:service-activator id="serviceActivator"
|
|
input-channel="requests"
|
|
output-channel="replies"
|
|
ref="chunkProcessorChunkHandler"
|
|
method="handleChunk"/>
|
|
|
|
<bean id="chunkProcessorChunkHandler"
|
|
class="org.springframework.batch.integration.chunk.ChunkProcessorChunkHandler">
|
|
<property name="chunkProcessor">
|
|
<bean class="org.springframework.batch.core.step.item.SimpleChunkProcessor">
|
|
<property name="itemWriter">
|
|
<bean class="io.spring.sbi.PersonItemWriter"/>
|
|
</property>
|
|
<property name="itemProcessor">
|
|
<bean class="io.spring.sbi.PersonItemProcessor"/>
|
|
</property>
|
|
</bean>
|
|
</property>
|
|
</bean>
|
|
----
|
|
|
|
.Java Configuration
|
|
[source, java, role="javaContent"]
|
|
----
|
|
@Bean
|
|
public org.apache.activemq.ActiveMQConnectionFactory connectionFactory() {
|
|
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
|
|
factory.setBrokerURL("tcp://localhost:61616");
|
|
return factory;
|
|
}
|
|
|
|
/*
|
|
* Configure inbound flow (requests coming from the master)
|
|
*/
|
|
@Bean
|
|
public DirectChannel requests() {
|
|
return new DirectChannel();
|
|
}
|
|
|
|
@Bean
|
|
public IntegrationFlow inboundFlow(ActiveMQConnectionFactory connectionFactory) {
|
|
return IntegrationFlows
|
|
.from(Jms.messageDrivenChannelAdapter(connectionFactory).destination("requests"))
|
|
.channel(requests())
|
|
.get();
|
|
}
|
|
|
|
/*
|
|
* Configure outbound flow (replies going to the master)
|
|
*/
|
|
@Bean
|
|
public DirectChannel replies() {
|
|
return new DirectChannel();
|
|
}
|
|
|
|
@Bean
|
|
public IntegrationFlow outboundFlow(ActiveMQConnectionFactory connectionFactory) {
|
|
return IntegrationFlows
|
|
.from(replies())
|
|
.handle(Jms.outboundAdapter(connectionFactory).destination("replies"))
|
|
.get();
|
|
}
|
|
|
|
/*
|
|
* Configure the ChunkProcessorChunkHandler
|
|
*/
|
|
@Bean
|
|
@ServiceActivator(inputChannel = "requests", outputChannel = "replies")
|
|
public ChunkProcessorChunkHandler<Integer> chunkProcessorChunkHandler() {
|
|
ChunkProcessor<Integer> chunkProcessor
|
|
= new SimpleChunkProcessor<>(itemProcessor(), itemWriter());
|
|
ChunkProcessorChunkHandler<Integer> chunkProcessorChunkHandler
|
|
= new ChunkProcessorChunkHandler<>();
|
|
chunkProcessorChunkHandler.setChunkProcessor(chunkProcessor);
|
|
return chunkProcessorChunkHandler;
|
|
}
|
|
----
|
|
|
|
Most of these configuration items should look familiar from the
|
|
master configuration. Workers do not need access to
|
|
the Spring Batch `JobRepository` nor
|
|
to the actual job configuration file. The main bean of interest
|
|
is the `chunkProcessorChunkHandler`. The
|
|
`chunkProcessor` property of `ChunkProcessorChunkHandler` takes a
|
|
configured `SimpleChunkProcessor`, which is where you would provide a reference to your
|
|
`ItemWriter` (and, optionally, your
|
|
`ItemProcessor`) that will run on the worker
|
|
when it receives chunks from the master.
|
|
|
|
For more information, see the section of the "Scalability" chapter on
|
|
link:$$https://docs.spring.io/spring-batch/reference/html/scalability.html#remoteChunking$$[Remote Chunking].
|
|
|
|
Starting from version 4.1, Spring Batch Integration introduces the `@EnableBatchIntegration`
|
|
annotation that can be used to simplify a remote chunking setup. This annotation provides
|
|
two beans that can be autowired in the application context:
|
|
|
|
* `RemoteChunkingMasterStepBuilderFactory`: used to configure the master step
|
|
* `RemoteChunkingWorkerBuilder`: used to configure the remote worker integration flow
|
|
|
|
These APIs take care of configuring a number of components as described in the following diagram:
|
|
|
|
.Remote Chunking Configuration
|
|
image::{batch-asciidoc}images/remote-chunking-config.png[Remote Chunking Configuration, scaledwidth="80%"]
|
|
|
|
On the master side, the `RemoteChunkingMasterStepBuilderFactory` allows you to
|
|
configure a master step by declaring:
|
|
|
|
* the item reader to read items and send them to workers
|
|
* the output channel ("Outgoing requests") to send requests to workers
|
|
* the input channel ("Incoming replies") to receive replies from workers
|
|
|
|
A `ChunkMessageChannelItemWriter` and the `MessagingTemplate` are not needed to be explicitly configured
|
|
(Those can still be explicitly configured if required).
|
|
|
|
On the worker side, the `RemoteChunkingWorkerBuilder` allows you to configure a worker to:
|
|
|
|
* listen to requests sent by the master on the input channel ("Incoming requests")
|
|
* call the `handleChunk` method of `ChunkProcessorChunkHandler` for each request
|
|
with the configured `ItemProcessor` and `ItemWriter`
|
|
* send replies on the output channel ("Outgoing replies") to the master
|
|
|
|
There is no need to explicitly configure the `SimpleChunkProcessor`
|
|
and the `ChunkProcessorChunkHandler` (Those can be explicitly configured if required).
|
|
|
|
The following example shows how to use these APIs:
|
|
|
|
[source, java]
|
|
----
|
|
@EnableBatchIntegration
|
|
@EnableBatchProcessing
|
|
public class RemoteChunkingJobConfiguration {
|
|
|
|
@Configuration
|
|
public static class MasterConfiguration {
|
|
|
|
@Autowired
|
|
private RemoteChunkingMasterStepBuilderFactory masterStepBuilderFactory;
|
|
|
|
@Bean
|
|
public TaskletStep masterStep() {
|
|
return this.masterStepBuilderFactory.get("masterStep")
|
|
.chunk(100)
|
|
.reader(itemReader())
|
|
.outputChannel(requests()) // requests sent to workers
|
|
.inputChannel(replies()) // replies received from workers
|
|
.build();
|
|
}
|
|
|
|
// Middleware beans setup omitted
|
|
|
|
}
|
|
|
|
@Configuration
|
|
public static class WorkerConfiguration {
|
|
|
|
@Autowired
|
|
private RemoteChunkingWorkerBuilder workerBuilder;
|
|
|
|
@Bean
|
|
public IntegrationFlow workerFlow() {
|
|
return this.workerBuilder
|
|
.itemProcessor(itemProcessor())
|
|
.itemWriter(itemWriter())
|
|
.inputChannel(requests()) // requests received from the master
|
|
.outputChannel(replies()) // replies sent to the master
|
|
.build();
|
|
}
|
|
|
|
// Middleware beans setup omitted
|
|
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
You can find a complete example of a remote chunking job
|
|
link:$$https://github.com/spring-projects/spring-batch/tree/master/spring-batch-samples#remote-chunking-sample$$[here].
|
|
|
|
[[remote-partitioning]]
|
|
|
|
===== Remote Partitioning
|
|
|
|
.Remote Partitioning
|
|
image::{batch-asciidoc}images/remote-partitioning.png[Remote Partitioning, scaledwidth="60%"]
|
|
|
|
|
|
Remote Partitioning, on the other hand, is useful when it
|
|
is not the processing of items but rather the associated I/O that
|
|
causes the bottleneck. Using Remote Partitioning, work can
|
|
be farmed out to workers that execute complete Spring Batch
|
|
steps. Thus, each worker has its own `ItemReader`, `ItemProcessor`, and
|
|
`ItemWriter`. For this purpose, Spring Batch
|
|
Integration provides the `MessageChannelPartitionHandler`.
|
|
|
|
|
|
|
|
This implementation of the `PartitionHandler`
|
|
interface uses `MessageChannel` instances to
|
|
send instructions to remote workers and receive their responses.
|
|
This provides a nice abstraction from the transports (such as JMS
|
|
and AMQP) being used to communicate with the remote workers.
|
|
|
|
|
|
|
|
The section of the "Scalability" chapter that addresses
|
|
<<scalability.adoc#partitioning,remote partitioning>> provides an overview of the concepts and
|
|
components needed to configure remote partitioning and shows an
|
|
example of using the default
|
|
`TaskExecutorPartitionHandler` to partition
|
|
in separate local threads of execution. For remote partitioning
|
|
to multiple JVMs, two additional components are required:
|
|
|
|
* A remoting fabric or grid environment
|
|
* A `PartitionHandler` implementation that supports the desired
|
|
remoting fabric or grid environment
|
|
|
|
|
|
|
|
Similar to remote chunking, JMS can be used as the "remoting
|
|
fabric". In that case, use a `MessageChannelPartitionHandler` instance as the `PartitionHandler` implementation,
|
|
as described above.
|
|
The following example
|
|
assumes an existing partitioned job and focuses on
|
|
the `MessageChannelPartitionHandler` and JMS
|
|
configuration:
|
|
|
|
|
|
.XML Configuration
|
|
[source, xml, role="xmlContent"]
|
|
----
|
|
<bean id="partitionHandler"
|
|
class="org.springframework.batch.integration.partition.MessageChannelPartitionHandler">
|
|
<property name="stepName" value="step1"/>
|
|
<property name="gridSize" value="3"/>
|
|
<property name="replyChannel" ref="outbound-replies"/>
|
|
<property name="messagingOperations">
|
|
<bean class="org.springframework.integration.core.MessagingTemplate">
|
|
<property name="defaultChannel" ref="outbound-requests"/>
|
|
<property name="receiveTimeout" value="100000"/>
|
|
</bean>
|
|
</property>
|
|
</bean>
|
|
|
|
<int:channel id="outbound-requests"/>
|
|
<int-jms:outbound-channel-adapter destination="requestsQueue"
|
|
channel="outbound-requests"/>
|
|
|
|
<int:channel id="inbound-requests"/>
|
|
<int-jms:message-driven-channel-adapter destination="requestsQueue"
|
|
channel="inbound-requests"/>
|
|
|
|
<bean id="stepExecutionRequestHandler"
|
|
class="org.springframework.batch.integration.partition.StepExecutionRequestHandler">
|
|
<property name="jobExplorer" ref="jobExplorer"/>
|
|
<property name="stepLocator" ref="stepLocator"/>
|
|
</bean>
|
|
|
|
<int:service-activator ref="stepExecutionRequestHandler" input-channel="inbound-requests"
|
|
output-channel="outbound-staging"/>
|
|
|
|
<int:channel id="outbound-staging"/>
|
|
<int-jms:outbound-channel-adapter destination="stagingQueue"
|
|
channel="outbound-staging"/>
|
|
|
|
<int:channel id="inbound-staging"/>
|
|
<int-jms:message-driven-channel-adapter destination="stagingQueue"
|
|
channel="inbound-staging"/>
|
|
|
|
<int:aggregator ref="partitionHandler" input-channel="inbound-staging"
|
|
output-channel="outbound-replies"/>
|
|
|
|
<int:channel id="outbound-replies">
|
|
<int:queue/>
|
|
</int:channel>
|
|
|
|
<bean id="stepLocator"
|
|
class="org.springframework.batch.integration.partition.BeanFactoryStepLocator" />
|
|
----
|
|
|
|
.Java Configuration
|
|
[source, java, role="javaContent"]
|
|
----
|
|
|
|
/*
|
|
* Configuration of the master side
|
|
*/
|
|
@Bean
|
|
public PartitionHandler partitionHandler() {
|
|
MessageChannelPartitionHandler partitionHandler = new MessageChannelPartitionHandler();
|
|
partitionHandler.setStepName("step1");
|
|
partitionHandler.setGridSize(3);
|
|
partitionHandler.setReplyChannel(outboundReplies());
|
|
MessagingTemplate template = new MessagingTemplate();
|
|
template.setDefaultChannel(outboundRequests());
|
|
template.setReceiveTimeout(100000);
|
|
partitionHandler.setMessagingOperations(template);
|
|
return partitionHandler;
|
|
}
|
|
|
|
@Bean
|
|
public QueueChannel outboundReplies() {
|
|
return new QueueChannel();
|
|
}
|
|
|
|
@Bean
|
|
public DirectChannel outboundRequests() {
|
|
return new DirectChannel();
|
|
}
|
|
|
|
@Bean
|
|
public IntegrationFlow outboundJmsRequests() {
|
|
return IntegrationFlows.from("outboundRequests")
|
|
.handle(Jms.outboundGateway(connectionFactory())
|
|
.requestDestination("requestsQueue"))
|
|
.get();
|
|
}
|
|
|
|
@Bean
|
|
@ServiceActivator(inputChannel = "inboundStaging")
|
|
public AggregatorFactoryBean partitioningMessageHandler() throws Exception {
|
|
AggregatorFactoryBean aggregatorFactoryBean = new AggregatorFactoryBean();
|
|
aggregatorFactoryBean.setProcessorBean(partitionHandler());
|
|
aggregatorFactoryBean.setOutputChannel(outboundReplies());
|
|
// configure other propeties of the aggregatorFactoryBean
|
|
return aggregatorFactoryBean;
|
|
}
|
|
|
|
@Bean
|
|
public DirectChannel inboundStaging() {
|
|
return new DirectChannel();
|
|
}
|
|
|
|
@Bean
|
|
public IntegrationFlow inboundJmsStaging() {
|
|
return IntegrationFlows
|
|
.from(Jms.messageDrivenChannelAdapter(connectionFactory())
|
|
.configureListenerContainer(c -> c.subscriptionDurable(false))
|
|
.destination("stagingQueue"))
|
|
.channel(inboundStaging())
|
|
.get();
|
|
}
|
|
|
|
/*
|
|
* Configuration of the worker side
|
|
*/
|
|
@Bean
|
|
public StepExecutionRequestHandler stepExecutionRequestHandler() {
|
|
StepExecutionRequestHandler stepExecutionRequestHandler = new StepExecutionRequestHandler();
|
|
stepExecutionRequestHandler.setJobExplorer(jobExplorer);
|
|
stepExecutionRequestHandler.setStepLocator(stepLocator());
|
|
return stepExecutionRequestHandler;
|
|
}
|
|
|
|
@Bean
|
|
@ServiceActivator(inputChannel = "inboundRequests", outputChannel = "outboundStaging")
|
|
public StepExecutionRequestHandler serviceActivator() throws Exception {
|
|
return stepExecutionRequestHandler();
|
|
}
|
|
|
|
@Bean
|
|
public DirectChannel inboundRequests() {
|
|
return new DirectChannel();
|
|
}
|
|
|
|
public IntegrationFlow inboundJmsRequests() {
|
|
return IntegrationFlows
|
|
.from(Jms.messageDrivenChannelAdapter(connectionFactory())
|
|
.configureListenerContainer(c -> c.subscriptionDurable(false))
|
|
.destination("requestsQueue"))
|
|
.channel(inboundRequests())
|
|
.get();
|
|
}
|
|
|
|
@Bean
|
|
public DirectChannel outboundStaging() {
|
|
return new DirectChannel();
|
|
}
|
|
|
|
@Bean
|
|
public IntegrationFlow outboundJmsStaging() {
|
|
return IntegrationFlows.from("outboundStaging")
|
|
.handle(Jms.outboundGateway(connectionFactory())
|
|
.requestDestination("stagingQueue"))
|
|
.get();
|
|
}
|
|
----
|
|
|
|
You must also ensure that the partition `handler` attribute maps to the `partitionHandler` bean, as shown in the following example:
|
|
|
|
.XML Configuration
|
|
[source, xml, role="xmlContent"]
|
|
----
|
|
<job id="personJob">
|
|
<step id="step1.master">
|
|
<partition partitioner="partitioner" handler="partitionHandler"/>
|
|
...
|
|
</step>
|
|
</job>
|
|
----
|
|
|
|
.Java Configuration
|
|
[source, java, role="javaContent"]
|
|
----
|
|
public Job personJob() {
|
|
return jobBuilderFactory.get("personJob")
|
|
.start(stepBuilderFactory.get("step1.master")
|
|
.partitioner("step1.worker", partitioner())
|
|
.partitionHandler(partitionHandler())
|
|
.build())
|
|
.build();
|
|
}
|
|
----
|
|
|
|
You can find a complete example of a remote partitioning job
|
|
link:$$https://github.com/spring-projects/spring-batch/tree/master/spring-batch-samples#remote-partitioning-sample$$[here].
|
|
|
|
The `@EnableBatchIntegration` annotation that can be used to simplify a remote
|
|
partitioning setup. This annotation provides two beans useful for remote partitioning:
|
|
|
|
* `RemotePartitioningMasterStepBuilderFactory`: used to configure the master step
|
|
* `RemotePartitioningWorkerStepBuilderFactory`: used to configure the worker step
|
|
|
|
These APIs take care of configuring a number of components as described in the following diagram:
|
|
|
|
.Remote Partitioning Configuration (with job repository polling)
|
|
image::{batch-asciidoc}images/remote-partitioning-polling-config.png[Remote Partitioning Configuration (with job repository polling), scaledwidth="80%"]
|
|
|
|
.Remote Partitioning Configuration (with replies aggregation)
|
|
image::{batch-asciidoc}images/remote-partitioning-aggregation-config.png[Remote Partitioning Configuration (with replies aggregation), scaledwidth="80%"]
|
|
|
|
On the master side, the `RemotePartitioningMasterStepBuilderFactory` allows you to
|
|
configure a master step by declaring:
|
|
|
|
* the `Partitioner` used to partition data
|
|
* the output channel ("Outgoing requests") to send requests to workers
|
|
* the input channel ("Incoming replies") to receive replies from workers (when configuring replies aggregation)
|
|
* the poll interval and timeout parameters (when configuring job repository polling)
|
|
|
|
The `MessageChannelPartitionHandler` and the `MessagingTemplate` are not needed to be explicitly configured
|
|
(Those can still be explicitly configured if required).
|
|
|
|
On the worker side, the `RemotePartitioningWorkerStepBuilderFactory` allows you to configure a worker to:
|
|
|
|
* listen to requests sent by the master on the input channel ("Incoming requests")
|
|
* call the `handle` method of `StepExecutionRequestHandler` for each request
|
|
* send replies on the output channel ("Outgoing replies") to the master
|
|
|
|
There is no need to explicitly configure the `StepExecutionRequestHandler` (which can be explicitly configured if required).
|
|
|
|
The following example shows how to use these APIs:
|
|
|
|
[source, java]
|
|
----
|
|
@Configuration
|
|
@EnableBatchProcessing
|
|
@EnableBatchIntegration
|
|
public class RemotePartitioningJobConfiguration {
|
|
|
|
@Configuration
|
|
public static class MasterConfiguration {
|
|
|
|
@Autowired
|
|
private RemotePartitioningMasterStepBuilderFactory masterStepBuilderFactory;
|
|
|
|
@Bean
|
|
public Step masterStep() {
|
|
return this.masterStepBuilderFactory
|
|
.get("masterStep")
|
|
.partitioner("workerStep", partitioner())
|
|
.gridSize(10)
|
|
.outputChannel(outgoingRequestsToWorkers())
|
|
.inputChannel(incomingRepliesFromWorkers())
|
|
.build();
|
|
}
|
|
|
|
// Middleware beans setup omitted
|
|
|
|
}
|
|
|
|
@Configuration
|
|
public static class WorkerConfiguration {
|
|
|
|
@Autowired
|
|
private RemotePartitioningWorkerStepBuilderFactory workerStepBuilderFactory;
|
|
|
|
@Bean
|
|
public Step workerStep() {
|
|
return this.workerStepBuilderFactory
|
|
.get("workerStep")
|
|
.inputChannel(incomingRequestsFromMaster())
|
|
.outputChannel(outgoingRepliesToMaster())
|
|
.chunk(100)
|
|
.reader(itemReader())
|
|
.processor(itemProcessor())
|
|
.writer(itemWriter())
|
|
.build();
|
|
}
|
|
|
|
// Middleware beans setup omitted
|
|
|
|
}
|
|
|
|
}
|
|
----
|