From 9cc8e4996a51a0496cc47ea6f8165703696995c7 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 5 Dec 2023 14:33:11 -0600 Subject: [PATCH] Migrate docs to Antora infrastructure * Migrate Structure * Insert explicit ids for headers * Remove unnecessary asciidoc attributes * Fix image::image * Copy default antora files * Fix indentation for all pages * Split files * Generate a default navigation * Remove includes * Fix cross references * Enable Section Summary TOC for small pages * Remove src/reference/asciidoc * Fix antora build files * Fix Antora Build and Migrate to Asciidoctor Tabs * Fix Antora Nav Hiearchy * Add deploy-docs.yml * PR Feedback * Fix `modifiedFiles` for `updateCopyrights` task --- .github/workflows/deploy-docs.yml | 32 + build.gradle | 40 +- src/reference/antora/antora-playbook.yml | 44 + src/reference/antora/antora.yml | 17 + .../ROOT/assets}/images/cacheStats.png | Bin .../modules/ROOT/assets}/images/tickmark.png | Bin src/reference/antora/modules/ROOT/nav.adoc | 88 + .../antora/modules/ROOT/pages/amqp.adoc | 6 + .../modules/ROOT/pages/amqp/abstractions.adoc | 183 + .../ROOT/pages/amqp/broker-configuration.adoc | 682 ++ .../ROOT/pages/amqp/broker-events.adoc | 26 + .../modules/ROOT/pages/amqp/connections.adoc | 839 ++ .../ROOT/pages/amqp/containerAttributes.adoc | 734 ++ .../containers-and-broker-named-queues.adoc | 35 + .../ROOT/pages/amqp/custom-client-props.adoc | 15 + .../modules/ROOT/pages/amqp/debugging.adoc | 12 + .../pages/amqp/delayed-message-exchange.adoc | 51 + .../ROOT/pages/amqp/exception-handling.adoc | 46 + .../ROOT/pages/amqp/exclusive-consumer.adoc | 10 + .../ROOT/pages/amqp/listener-concurrency.adoc | 46 + .../ROOT/pages/amqp/listener-queues.adoc | 19 + .../ROOT/pages/amqp/management-rest-api.adoc | 14 + .../ROOT/pages/amqp/message-converters.adoc | 513 ++ .../modules/ROOT/pages/amqp/multi-rabbit.adoc | 149 + .../ROOT/pages/amqp/post-processing.adoc | 35 + .../ROOT/pages/amqp/receiving-messages.adoc | 10 + .../async-annotation-driven.adoc | 124 + .../container-management.adoc | 23 + .../async-annotation-driven/conversion.adoc | 101 + .../custom-argument-resolver.adoc | 36 + .../enable-signature.adoc | 71 + .../async-annotation-driven/enable.adoc | 121 + .../error-handling.adoc | 55 + .../async-annotation-driven/meta.adoc | 71 + .../method-selection.adoc | 47 + .../multiple-queues.adoc | 40 + .../proxy-rabbitlistener-and-generics.adoc | 46 + .../rabbit-validation.adoc | 69 + .../async-annotation-driven/registration.adoc | 29 + .../repeatable-rabbit-listener.adoc | 10 + .../reply-content-type.adoc | 51 + .../async-annotation-driven/reply.adoc | 158 + .../receiving-messages/async-consumer.adoc | 275 + .../receiving-messages/async-returns.adoc | 24 + .../pages/amqp/receiving-messages/batch.adoc | 90 + .../receiving-messages/choose-container.adoc | 36 + .../receiving-messages/consumer-events.adoc | 39 + .../amqp/receiving-messages/consumerTags.adoc | 21 + .../amqp/receiving-messages/de-batching.adoc | 16 + .../receiving-messages/idle-containers.adoc | 95 + .../micrometer-observation.adoc | 19 + .../amqp/receiving-messages/micrometer.adoc | 21 + .../receiving-messages/polling-consumer.adoc | 102 + .../amqp/receiving-messages/threading.adoc | 28 + .../using-container-factories.adoc | 51 + .../ROOT/pages/amqp/request-reply.adoc | 301 + ...ering-from-errors-and-broker-failures.adoc | 212 + .../ROOT/pages/amqp/sending-messages.adoc | 224 + .../modules/ROOT/pages/amqp/template.adoc | 547 ++ .../modules/ROOT/pages/amqp/transactions.adoc | 157 + .../ROOT/pages/appendix/change-history.adoc | 5 + .../ROOT/pages/appendix/current-release.adoc | 6 + .../ROOT/pages/appendix/micrometer.adoc | 8 + .../modules/ROOT/pages/appendix/native.adoc | 7 + .../pages/appendix/previous-whats-new.adoc | 4 + .../changes-in-1-3-since-1-2.adoc | 107 + .../changes-in-1-4-since-1-3.adoc | 120 + .../changes-in-1-5-since-1-4.adoc | 197 + .../changes-in-1-6-since-1-5.adoc | 262 + .../changes-in-1-7-since-1-6.adoc | 76 + .../changes-in-2-0-since-1-7.adoc | 203 + .../changes-in-2-1-since-2-0.adoc | 127 + .../changes-in-2-2-since-2-1.adoc | 137 + .../changes-in-2-3-since-2-2.adoc | 72 + .../changes-in-2-4-since-2-3.adoc | 24 + .../changes-in-3-0-since-2-4.adoc | 70 + .../changes-to-1-1-since-1-0.adoc | 21 + .../changes-to-1-2-since-1-1.adoc | 58 + .../previous-whats-new/earlier-releases.adoc | 6 + .../message-converter-changes-1.adoc | 7 + .../message-converter-changes.adoc | 7 + .../stream-support-changes.adoc | 7 + .../modules/ROOT/pages}/further-reading.adoc | 3 +- .../modules/ROOT/pages/index.adoc} | 14 + .../ROOT/pages/integration-reference.adoc | 4 + .../ROOT/pages/introduction/index.adoc | 5 + .../ROOT/pages/introduction}/quick-tour.adoc | 33 +- .../modules/ROOT/pages}/logging.adoc | 50 +- .../antora/modules/ROOT/pages/reference.adoc | 9 + .../antora/modules/ROOT/pages/resources.adoc | 6 + .../modules/ROOT/pages}/sample-apps.adoc | 49 +- .../modules/ROOT/pages}/si-amqp.adoc | 24 +- .../modules/ROOT/pages}/stream.adoc | 61 +- .../modules/ROOT/pages}/testing.adoc | 70 +- .../modules/ROOT/pages}/whats-new.adoc | 13 +- src/reference/asciidoc/amqp.adoc | 7059 ----------------- src/reference/asciidoc/appendix.adoc | 1329 ---- src/reference/asciidoc/docinfo.html | 5 - src/reference/asciidoc/index.adoc | 70 - 99 files changed, 8627 insertions(+), 8664 deletions(-) create mode 100644 .github/workflows/deploy-docs.yml create mode 100644 src/reference/antora/antora-playbook.yml create mode 100644 src/reference/antora/antora.yml rename src/reference/{asciidoc => antora/modules/ROOT/assets}/images/cacheStats.png (100%) rename src/reference/{asciidoc => antora/modules/ROOT/assets}/images/tickmark.png (100%) create mode 100644 src/reference/antora/modules/ROOT/nav.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/abstractions.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/broker-configuration.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/broker-events.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/connections.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/containerAttributes.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/containers-and-broker-named-queues.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/custom-client-props.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/debugging.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/delayed-message-exchange.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/exception-handling.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/exclusive-consumer.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/listener-concurrency.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/listener-queues.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/management-rest-api.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/message-converters.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/multi-rabbit.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/post-processing.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/container-management.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/conversion.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/custom-argument-resolver.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/enable-signature.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/enable.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/error-handling.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/meta.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/method-selection.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/multiple-queues.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/proxy-rabbitlistener-and-generics.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/rabbit-validation.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/registration.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/repeatable-rabbit-listener.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/reply-content-type.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/reply.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-consumer.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-returns.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/batch.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/choose-container.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/consumer-events.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/consumerTags.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/de-batching.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/idle-containers.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer-observation.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/polling-consumer.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/threading.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/using-container-factories.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/request-reply.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/resilience-recovering-from-errors-and-broker-failures.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/sending-messages.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/template.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/amqp/transactions.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/change-history.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/current-release.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/micrometer.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/native.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-3-since-1-2.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-4-since-1-3.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-5-since-1-4.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-6-since-1-5.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-7-since-1-6.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-0-since-1-7.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-1-since-2-0.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-2-since-2-1.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-3-since-2-2.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-4-since-2-3.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-3-0-since-2-4.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-to-1-1-since-1-0.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-to-1-2-since-1-1.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/earlier-releases.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/message-converter-changes-1.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/message-converter-changes.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/stream-support-changes.adoc rename src/reference/{asciidoc => antora/modules/ROOT/pages}/further-reading.adoc (94%) rename src/reference/{asciidoc/preface.adoc => antora/modules/ROOT/pages/index.adoc} (51%) create mode 100644 src/reference/antora/modules/ROOT/pages/integration-reference.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/introduction/index.adoc rename src/reference/{asciidoc => antora/modules/ROOT/pages/introduction}/quick-tour.adoc (93%) rename src/reference/{asciidoc => antora/modules/ROOT/pages}/logging.adoc (93%) create mode 100644 src/reference/antora/modules/ROOT/pages/reference.adoc create mode 100644 src/reference/antora/modules/ROOT/pages/resources.adoc rename src/reference/{asciidoc => antora/modules/ROOT/pages}/sample-apps.adoc (96%) rename src/reference/{asciidoc => antora/modules/ROOT/pages}/si-amqp.adoc (92%) rename src/reference/{asciidoc => antora/modules/ROOT/pages}/stream.adoc (92%) rename src/reference/{asciidoc => antora/modules/ROOT/pages}/testing.adoc (94%) rename src/reference/{asciidoc => antora/modules/ROOT/pages}/whats-new.adoc (67%) delete mode 100644 src/reference/asciidoc/amqp.adoc delete mode 100644 src/reference/asciidoc/appendix.adoc delete mode 100644 src/reference/asciidoc/docinfo.html delete mode 100644 src/reference/asciidoc/index.adoc diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 00000000..f3e899c4 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,32 @@ +name: Deploy Docs +on: + push: + branches-ignore: [ gh-pages ] + tags: '**' + repository_dispatch: + types: request-build-reference # legacy + schedule: + - cron: '0 10 * * *' # Once per day at 10am UTC + workflow_dispatch: +permissions: + actions: write +jobs: + build: + runs-on: ubuntu-latest + if: github.repository_owner == 'spring-projects' + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: docs-build + fetch-depth: 1 + - name: Dispatch (partial build) + if: github.ref_type == 'branch' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }} + - name: Dispatch (full build) + if: github.ref_type == 'tag' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) diff --git a/build.gradle b/build.gradle index 08603718..ce2a02b6 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,8 @@ plugins { id 'io.spring.dependency-management' version '1.1.4' apply false id 'org.asciidoctor.jvm.pdf' version '3.3.2' id 'org.asciidoctor.jvm.convert' version '3.3.2' + id 'org.antora' version '1.0.0' + id 'io.spring.antora.generate-antora-yml' version '0.0.1' } description = 'Spring AMQP' @@ -38,7 +40,11 @@ ext { springAsciidoctorBackendsVersion = '0.0.7' modifiedFiles = - files(grgit.status().unstaged.modified).filter{ f -> f.name.endsWith('.java') || f.name.endsWith('.kt') } + files() + .from { + files(grgit.status().unstaged.modified) + .filter { f -> f.name.endsWith('.java') || f.name.endsWith('.kt') } + } assertjVersion = '3.24.2' assertkVersion = '0.27.0' @@ -74,6 +80,38 @@ ext { javaProjects = subprojects - project(':spring-amqp-bom') } + + +antora { + version = '3.2.0-alpha.2' + playbook = file('src/reference/antora/antora-playbook.yml') + options = ['to-dir' : project.layout.buildDirectory.dir('site').get().toString(), clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true] + dependencies = [ + '@antora/atlas-extension': '1.0.0-alpha.1', + '@antora/collector-extension': '1.0.0-alpha.3', + '@asciidoctor/tabs': '1.0.0-beta.3', + '@springio/antora-extensions': '1.4.2', + '@springio/asciidoctor-extensions': '1.0.0-alpha.8', + ] +} + +tasks.named("generateAntoraYml") { + asciidocAttributes = project.provider( { + return ['project-version' : project.version ] + } ) + baseAntoraYmlFile = file('src/reference/antora/antora.yml') +} + +tasks.create(name: 'createAntoraPartials', type: Sync) { + from { tasks.filterMetricsDocsContent.outputs } + into layout.buildDirectory.dir('generated-antora-resources/modules/ROOT/partials') +} + +tasks.create('generateAntoraResources') { + dependsOn 'createAntoraPartials' + dependsOn 'generateAntoraYml' +} + nohttp { source.include '**/src/**' source.exclude '**/*.gif', '**/*.ks' diff --git a/src/reference/antora/antora-playbook.yml b/src/reference/antora/antora-playbook.yml new file mode 100644 index 00000000..ca962c06 --- /dev/null +++ b/src/reference/antora/antora-playbook.yml @@ -0,0 +1,44 @@ +antora: + extensions: + - '@springio/antora-extensions/partial-build-extension' + - require: '@springio/antora-extensions/latest-version-extension' + - require: '@springio/antora-extensions/inject-collector-cache-config-extension' + - '@antora/collector-extension' + - '@antora/atlas-extension' + - require: '@springio/antora-extensions/root-component-extension' + root_component_name: 'amqp' + # FIXME: Run antora once using this extension to migrate to the Asciidoc Tabs syntax + # and then remove this extension + - require: '@springio/antora-extensions/tabs-migration-extension' + unwrap_example_block: always + save_result: true +site: + title: Spring AMQP + url: https://docs.spring.io/spring-amqp/reference/ +content: + sources: + - url: ./../../.. + branches: HEAD + # See https://docs.antora.org/antora/latest/playbook/content-source-start-path/#start-path-key + start_path: src/reference/antora + worktrees: true +asciidoc: + attributes: + page-stackoverflow-url: https://stackoverflow.com/tags/spring-amqp + page-pagination: '' + hide-uri-scheme: '@' + tabs-sync-option: '@' + chomp: 'all' + extensions: + - '@asciidoctor/tabs' + - '@springio/asciidoctor-extensions' + sourcemap: true +urls: + latest_version_segment: '' +runtime: + log: + failure_level: warn + format: pretty +ui: + bundle: + url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.9/ui-bundle.zip diff --git a/src/reference/antora/antora.yml b/src/reference/antora/antora.yml new file mode 100644 index 00000000..6529e29d --- /dev/null +++ b/src/reference/antora/antora.yml @@ -0,0 +1,17 @@ +name: amqp +version: true +title: Spring AMQP +nav: + - modules/ROOT/nav.adoc +ext: + collector: + run: + command: gradlew -q "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :generateAntoraResources + local: true + scan: + dir: build/generated-antora-resources + +asciidoc: + attributes: + attribute-missing: 'warn' + chomp: 'all' \ No newline at end of file diff --git a/src/reference/asciidoc/images/cacheStats.png b/src/reference/antora/modules/ROOT/assets/images/cacheStats.png similarity index 100% rename from src/reference/asciidoc/images/cacheStats.png rename to src/reference/antora/modules/ROOT/assets/images/cacheStats.png diff --git a/src/reference/asciidoc/images/tickmark.png b/src/reference/antora/modules/ROOT/assets/images/tickmark.png similarity index 100% rename from src/reference/asciidoc/images/tickmark.png rename to src/reference/antora/modules/ROOT/assets/images/tickmark.png diff --git a/src/reference/antora/modules/ROOT/nav.adoc b/src/reference/antora/modules/ROOT/nav.adoc new file mode 100644 index 00000000..9d71ec1e --- /dev/null +++ b/src/reference/antora/modules/ROOT/nav.adoc @@ -0,0 +1,88 @@ +* xref:index.adoc[] +* xref:whats-new.adoc[] +* xref:introduction/index.adoc[] +** xref:introduction/quick-tour.adoc[] +* xref:reference.adoc[] +** xref:amqp.adoc[] +*** xref:amqp/abstractions.adoc[] +*** xref:amqp/connections.adoc[] +*** xref:amqp/custom-client-props.adoc[] +*** xref:amqp/template.adoc[] +*** xref:amqp/sending-messages.adoc[] +*** xref:amqp/receiving-messages.adoc[] +**** xref:amqp/receiving-messages/polling-consumer.adoc[] +**** xref:amqp/receiving-messages/async-consumer.adoc[] +**** xref:amqp/receiving-messages/de-batching.adoc[] +**** xref:amqp/receiving-messages/consumer-events.adoc[] +**** xref:amqp/receiving-messages/consumerTags.adoc[] +**** xref:amqp/receiving-messages/async-annotation-driven.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/meta.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/enable.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/conversion.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/custom-argument-resolver.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/registration.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/enable-signature.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/rabbit-validation.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/multiple-queues.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/reply.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/reply-content-type.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/method-selection.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/repeatable-rabbit-listener.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/proxy-rabbitlistener-and-generics.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/error-handling.adoc[] +***** xref:amqp/receiving-messages/async-annotation-driven/container-management.adoc[] +**** xref:amqp/receiving-messages/batch.adoc[] +**** xref:amqp/receiving-messages/using-container-factories.adoc[] +**** xref:amqp/receiving-messages/async-returns.adoc[] +**** xref:amqp/receiving-messages/threading.adoc[] +**** xref:amqp/receiving-messages/choose-container.adoc[] +**** xref:amqp/receiving-messages/idle-containers.adoc[] +**** xref:amqp/receiving-messages/micrometer.adoc[] +**** xref:amqp/receiving-messages/micrometer-observation.adoc[] +*** xref:amqp/containers-and-broker-named-queues.adoc[] +*** xref:amqp/message-converters.adoc[] +*** xref:amqp/post-processing.adoc[] +*** xref:amqp/request-reply.adoc[] +*** xref:amqp/broker-configuration.adoc[] +*** xref:amqp/broker-events.adoc[] +*** xref:amqp/delayed-message-exchange.adoc[] +*** xref:amqp/management-rest-api.adoc[] +*** xref:amqp/exception-handling.adoc[] +*** xref:amqp/transactions.adoc[] +*** xref:amqp/containerAttributes.adoc[] +*** xref:amqp/listener-concurrency.adoc[] +*** xref:amqp/exclusive-consumer.adoc[] +*** xref:amqp/listener-queues.adoc[] +*** xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc[] +*** xref:amqp/multi-rabbit.adoc[] +*** xref:amqp/debugging.adoc[] +** xref:stream.adoc[] +** xref:logging.adoc[] +** xref:sample-apps.adoc[] +** xref:testing.adoc[] +* xref:integration-reference.adoc[] +** xref:si-amqp.adoc[] +* xref:resources.adoc[] +** xref:further-reading.adoc[] +* xref:appendix/micrometer.adoc[] +* xref:appendix/native.adoc[] +* Change History +** xref:appendix/current-release.adoc[] +** xref:appendix/previous-whats-new.adoc[] +*** xref:appendix/previous-whats-new/changes-in-3-0-since-2-4.adoc[] +*** xref:appendix/previous-whats-new/changes-in-2-4-since-2-3.adoc[] +*** xref:appendix/previous-whats-new/message-converter-changes.adoc[] +*** xref:appendix/previous-whats-new/message-converter-changes-1.adoc[] +*** xref:appendix/previous-whats-new/stream-support-changes.adoc[] +*** xref:appendix/previous-whats-new/changes-in-2-3-since-2-2.adoc[] +*** xref:appendix/previous-whats-new/changes-in-2-2-since-2-1.adoc[] +*** xref:appendix/previous-whats-new/changes-in-2-1-since-2-0.adoc[] +*** xref:appendix/previous-whats-new/changes-in-2-0-since-1-7.adoc[] +*** xref:appendix/previous-whats-new/changes-in-1-7-since-1-6.adoc[] +*** xref:appendix/previous-whats-new/earlier-releases.adoc[] +*** xref:appendix/previous-whats-new/changes-in-1-6-since-1-5.adoc[] +*** xref:appendix/previous-whats-new/changes-in-1-5-since-1-4.adoc[] +*** xref:appendix/previous-whats-new/changes-in-1-4-since-1-3.adoc[] +*** xref:appendix/previous-whats-new/changes-in-1-3-since-1-2.adoc[] +*** xref:appendix/previous-whats-new/changes-to-1-2-since-1-1.adoc[] +*** xref:appendix/previous-whats-new/changes-to-1-1-since-1-0.adoc[] diff --git a/src/reference/antora/modules/ROOT/pages/amqp.adoc b/src/reference/antora/modules/ROOT/pages/amqp.adoc new file mode 100644 index 00000000..79cba0eb --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp.adoc @@ -0,0 +1,6 @@ +[[amqp]] += Using Spring AMQP +:page-section-summary-toc: 1 + +This chapter explores the interfaces and classes that are the essential components for developing applications with Spring AMQP. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/abstractions.adoc b/src/reference/antora/modules/ROOT/pages/amqp/abstractions.adoc new file mode 100644 index 00000000..192c5c7f --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/abstractions.adoc @@ -0,0 +1,183 @@ +[[amqp-abstractions]] += AMQP Abstractions + +Spring AMQP consists of two modules (each represented by a JAR in the distribution): `spring-amqp` and `spring-rabbit`. +The 'spring-amqp' module contains the `org.springframework.amqp.core` package. +Within that package, you can find the classes that represent the core AMQP "`model`". +Our intention is to provide generic abstractions that do not rely on any particular AMQP broker implementation or client library. +End user code can be more portable across vendor implementations as it can be developed against the abstraction layer only. +These abstractions are then implemented by broker-specific modules, such as 'spring-rabbit'. +There is currently only a RabbitMQ implementation. +However, the abstractions have been validated in .NET using Apache Qpid in addition to RabbitMQ. +Since AMQP operates at the protocol level, in principle, you can use the RabbitMQ client with any broker that supports the same protocol version, but we do not test any other brokers at present. + +This overview assumes that you are already familiar with the basics of the AMQP specification. +If not, have a look at the resources listed in xref:index.adoc#resources[Other Resources] + +[[message]] +== `Message` + +The 0-9-1 AMQP specification does not define a `Message` class or interface. +Instead, when performing an operation such as `basicPublish()`, the content is passed as a byte-array argument and additional properties are passed in as separate arguments. +Spring AMQP defines a `Message` class as part of a more general AMQP domain model representation. +The purpose of the `Message` class is to encapsulate the body and properties within a single instance so that the API can, in turn, be simpler. +The following example shows the `Message` class definition: + +[source,java] +---- +public class Message { + + private final MessageProperties messageProperties; + + private final byte[] body; + + public Message(byte[] body, MessageProperties messageProperties) { + this.body = body; + this.messageProperties = messageProperties; + } + + public byte[] getBody() { + return this.body; + } + + public MessageProperties getMessageProperties() { + return this.messageProperties; + } +} +---- + +The `MessageProperties` interface defines several common properties, such as 'messageId', 'timestamp', 'contentType', and several more. +You can also extend those properties with user-defined 'headers' by calling the `setHeader(String key, Object value)` method. + +IMPORTANT: Starting with versions `1.5.7`, `1.6.11`, `1.7.4`, and `2.0.0`, if a message body is a serialized `Serializable` java object, it is no longer deserialized (by default) when performing `toString()` operations (such as in log messages). +This is to prevent unsafe deserialization. +By default, only `java.util` and `java.lang` classes are deserialized. +To revert to the previous behavior, you can add allowable class/package patterns by invoking `Message.addAllowedListPatterns(...)`. +A simple `*` wildcard is supported, for example `com.something.*, *.MyClass`. +Bodies that cannot be deserialized are represented by `byte[]` in log messages. + +[[exchange]] +== Exchange + +The `Exchange` interface represents an AMQP Exchange, which is what a Message Producer sends to. +Each Exchange within a virtual host of a broker has a unique name as well as a few other properties. +The following example shows the `Exchange` interface: + +[source,java] +---- +public interface Exchange { + + String getName(); + + String getExchangeType(); + + boolean isDurable(); + + boolean isAutoDelete(); + + Map getArguments(); + +} +---- + +As you can see, an `Exchange` also has a 'type' represented by constants defined in `ExchangeTypes`. +The basic types are: `direct`, `topic`, `fanout`, and `headers`. +In the core package, you can find implementations of the `Exchange` interface for each of those types. +The behavior varies across these `Exchange` types in terms of how they handle bindings to queues. +For example, a `Direct` exchange lets a queue be bound by a fixed routing key (often the queue's name). +A `Topic` exchange supports bindings with routing patterns that may include the '*' and '#' wildcards for 'exactly-one' and 'zero-or-more', respectively. +The `Fanout` exchange publishes to all queues that are bound to it without taking any routing key into consideration. +For much more information about these and the other Exchange types, see xref:index.adoc#resources[Other Resources]. + +NOTE: The AMQP specification also requires that any broker provide a "`default`" direct exchange that has no name. +All queues that are declared are bound to that default `Exchange` with their names as routing keys. +You can learn more about the default Exchange's usage within Spring AMQP in xref:amqp/template.adoc[`AmqpTemplate`]. + +[[queue]] +== Queue + +The `Queue` class represents the component from which a message consumer receives messages. +Like the various `Exchange` classes, our implementation is intended to be an abstract representation of this core AMQP type. +The following listing shows the `Queue` class: + +[source,java] +---- +public class Queue { + + private final String name; + + private volatile boolean durable; + + private volatile boolean exclusive; + + private volatile boolean autoDelete; + + private volatile Map arguments; + + /** + * The queue is durable, non-exclusive and non auto-delete. + * + * @param name the name of the queue. + */ + public Queue(String name) { + this(name, true, false, false); + } + + // Getters and Setters omitted for brevity + +} +---- + +Notice that the constructor takes the queue name. +Depending on the implementation, the admin template may provide methods for generating a uniquely named queue. +Such queues can be useful as a "`reply-to`" address or in other *temporary* situations. +For that reason, the 'exclusive' and 'autoDelete' properties of an auto-generated queue would both be set to 'true'. + +NOTE: See the section on queues in xref:amqp/broker-configuration.adoc[Configuring the Broker] for information about declaring queues by using namespace support, including queue arguments. + +[[binding]] +== Binding + +Given that a producer sends to an exchange and a consumer receives from a queue, the bindings that connect queues to exchanges are critical for connecting those producers and consumers via messaging. +In Spring AMQP, we define a `Binding` class to represent those connections. +This section reviews the basic options for binding queues to exchanges. + +You can bind a queue to a `DirectExchange` with a fixed routing key, as the following example shows: + +[source,java] +---- +new Binding(someQueue, someDirectExchange, "foo.bar"); +---- + +You can bind a queue to a `TopicExchange` with a routing pattern, as the following example shows: + +[source,java] +---- +new Binding(someQueue, someTopicExchange, "foo.*"); +---- + +You can bind a queue to a `FanoutExchange` with no routing key, as the following example shows: + +[source,java] +---- +new Binding(someQueue, someFanoutExchange); +---- + +We also provide a `BindingBuilder` to facilitate a "`fluent API`" style, as the following example shows: + +[source,java] +---- +Binding b = BindingBuilder.bind(someQueue).to(someTopicExchange).with("foo.*"); +---- + +NOTE: For clarity, the preceding example shows the `BindingBuilder` class, but this style works well when using a static import for the 'bind()' method. + +By itself, an instance of the `Binding` class only holds the data about a connection. +In other words, it is not an "`active`" component. +However, as you will see later in xref:amqp/broker-configuration.adoc[Configuring the Broker], the `AmqpAdmin` class can use `Binding` instances to actually trigger the binding actions on the broker. +Also, as you can see in that same section, you can define the `Binding` instances by using Spring's `@Bean` annotations within `@Configuration` classes. +There is also a convenient base class that further simplifies that approach for generating AMQP-related bean definitions and recognizes the queues, exchanges, and bindings so that they are all declared on the AMQP broker upon application startup. + +The `AmqpTemplate` is also defined within the core package. +As one of the main components involved in actual AMQP messaging, it is discussed in detail in its own section (see xref:amqp/template.adoc[`AmqpTemplate`]). + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/broker-configuration.adoc b/src/reference/antora/modules/ROOT/pages/amqp/broker-configuration.adoc new file mode 100644 index 00000000..e3eb3bf9 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/broker-configuration.adoc @@ -0,0 +1,682 @@ +[[broker-configuration]] += Configuring the Broker + +The AMQP specification describes how the protocol can be used to configure queues, exchanges, and bindings on the broker. +These operations (which are portable from the 0.8 specification and higher) are present in the `AmqpAdmin` interface in the `org.springframework.amqp.core` package. +The RabbitMQ implementation of that class is `RabbitAdmin` located in the `org.springframework.amqp.rabbit.core` package. + +The `AmqpAdmin` interface is based on using the Spring AMQP domain abstractions and is shown in the following listing: + +[source,java] +---- +public interface AmqpAdmin { + + // Exchange Operations + + void declareExchange(Exchange exchange); + + void deleteExchange(String exchangeName); + + // Queue Operations + + Queue declareQueue(); + + String declareQueue(Queue queue); + + void deleteQueue(String queueName); + + void deleteQueue(String queueName, boolean unused, boolean empty); + + void purgeQueue(String queueName, boolean noWait); + + // Binding Operations + + void declareBinding(Binding binding); + + void removeBinding(Binding binding); + + Properties getQueueProperties(String queueName); + +} +---- + +See also xref:amqp/template.adoc#scoped-operations[Scoped Operations]. + +The `getQueueProperties()` method returns some limited information about the queue (message count and consumer count). +The keys for the properties returned are available as constants in the `RabbitTemplate` (`QUEUE_NAME`, +`QUEUE_MESSAGE_COUNT`, and `QUEUE_CONSUMER_COUNT`). +The <> provides much more information in the `QueueInfo` object. + +The no-arg `declareQueue()` method defines a queue on the broker with a name that is automatically generated. +The additional properties of this auto-generated queue are `exclusive=true`, `autoDelete=true`, and `durable=false`. + +The `declareQueue(Queue queue)` method takes a `Queue` object and returns the name of the declared queue. +If the `name` property of the provided `Queue` is an empty `String`, the broker declares the queue with a generated name. +That name is returned to the caller. +That name is also added to the `actualName` property of the `Queue`. +You can use this functionality programmatically only by invoking the `RabbitAdmin` directly. +When using auto-declaration by the admin when defining a queue declaratively in the application context, you can set the name property to `""` (the empty string). +The broker then creates the name. +Starting with version 2.1, listener containers can use queues of this type. +See xref:amqp/containers-and-broker-named-queues.adoc[Containers and Broker-Named queues] for more information. + +This is in contrast to an `AnonymousQueue` where the framework generates a unique (`UUID`) name and sets `durable` to +`false` and `exclusive`, `autoDelete` to `true`. +A `` with an empty (or missing) `name` attribute always creates an `AnonymousQueue`. + +See xref:amqp/broker-configuration.adoc#anonymous-queue[`AnonymousQueue`] to understand why `AnonymousQueue` is preferred over broker-generated queue names as well as +how to control the format of the name. +Starting with version 2.1, anonymous queues are declared with argument `Queue.X_QUEUE_LEADER_LOCATOR` set to `client-local` by default. +This ensures that the queue is declared on the node to which the application is connected. +Declarative queues must have fixed names because they might be referenced elsewhere in the context -- such as in the +listener shown in the following example: + +[source,xml] +---- + + + +---- + +See xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc#automatic-declaration[Automatic Declaration of Exchanges, Queues, and Bindings]. + +The RabbitMQ implementation of this interface is `RabbitAdmin`, which, when configured by using Spring XML, resembles the following example: + +[source,xml] +---- + + + +---- + +When the `CachingConnectionFactory` cache mode is `CHANNEL` (the default), the `RabbitAdmin` implementation does automatic lazy declaration of queues, exchanges, and bindings declared in the same `ApplicationContext`. +These components are declared as soon as a `Connection` is opened to the broker. +There are some namespace features that make this very convenient -- for example, +in the Stocks sample application, we have the following: + +[source,xml] +---- + + + + + + + + + + + + + + + +---- + +In the preceding example, we use anonymous queues (actually, internally, just queues with names generated by the framework, not by the broker) and refer to them by ID. +We can also declare queues with explicit names, which also serve as identifiers for their bean definitions in the context. +The following example configures a queue with an explicit name: + +[source,xml] +---- + +---- + +TIP: You can provide both `id` and `name` attributes. +This lets you refer to the queue (for example, in a binding) by an ID that is independent of the queue name. +It also allows standard Spring features (such as property placeholders and SpEL expressions for the queue name). +These features are not available when you use the name as the bean identifier. + +Queues can be configured with additional arguments -- for example, `x-message-ttl`. +When you use the namespace support, they are provided in the form of a `Map` of argument-name/argument-value pairs, which are defined by using the `` element. +The following example shows how to do so: + +[source,xml] +---- + + + + + + +---- + +By default, the arguments are assumed to be strings. +For arguments of other types, you must provide the type. +The following example shows how to specify the type: + +[source,xml] +---- + + + + + +---- + +When providing arguments of mixed types, you must provide the type for each entry element. +The following example shows how to do so: + +[source,xml] +---- + + + + 100 + + + + + +---- + +With Spring Framework 3.2 and later, this can be declared a little more succinctly, as follows: + +[source,xml] +---- + + + + + + +---- + +When you use Java configuration, the `Queue.X_QUEUE_LEADER_LOCATOR` argument is supported as a first class property through the `setLeaderLocator()` method on the `Queue` class. +Starting with version 2.1, anonymous queues are declared with this property set to `client-local` by default. +This ensures that the queue is declared on the node the application is connected to. + +IMPORTANT: The RabbitMQ broker does not allow declaration of a queue with mismatched arguments. +For example, if a `queue` already exists with no `time to live` argument, and you attempt to declare it with (for example) `key="x-message-ttl" value="100"`, an exception is thrown. + +By default, the `RabbitAdmin` immediately stops processing all declarations when any exception occurs. +This could cause downstream issues, such as a listener container failing to initialize because another queue (defined after the one in error) is not declared. + +This behavior can be modified by setting the `ignore-declaration-exceptions` attribute to `true` on the `RabbitAdmin` instance. +This option instructs the `RabbitAdmin` to log the exception and continue declaring other elements. +When configuring the `RabbitAdmin` using Java, this property is called `ignoreDeclarationExceptions`. +This is a global setting that applies to all elements. +Queues, exchanges, and bindings have a similar property that applies to just those elements. + +Prior to version 1.6, this property took effect only if an `IOException` occurred on the channel, such as when there is a mismatch between current and desired properties. +Now, this property takes effect on any exception, including `TimeoutException` and others. + +In addition, any declaration exceptions result in the publishing of a `DeclarationExceptionEvent`, which is an `ApplicationEvent` that can be consumed by any `ApplicationListener` in the context. +The event contains a reference to the admin, the element that was being declared, and the `Throwable`. + +[[headers-exchange]] +== Headers Exchange + +Starting with version 1.3, you can configure the `HeadersExchange` to match on multiple headers. +You can also specify whether any or all headers must match. +The following example shows how to do so: + +[source,xml] +---- + + + + + + + + + + + +---- + +Starting with version 1.6, you can configure `Exchanges` with an `internal` flag (defaults to `false`) and such an +`Exchange` is properly configured on the Broker through a `RabbitAdmin` (if one is present in the application context). +If the `internal` flag is `true` for an exchange, RabbitMQ does not let clients use the exchange. +This is useful for a dead letter exchange or exchange-to-exchange binding, where you do not wish the exchange to be used +directly by publishers. + +To see how to use Java to configure the AMQP infrastructure, look at the Stock sample application, +where there is the `@Configuration` class `AbstractStockRabbitConfiguration`, which ,in turn has +`RabbitClientConfiguration` and `RabbitServerConfiguration` subclasses. +The following listing shows the code for `AbstractStockRabbitConfiguration`: + +[source,java] +---- +@Configuration +public abstract class AbstractStockAppRabbitConfiguration { + + @Bean + public CachingConnectionFactory connectionFactory() { + CachingConnectionFactory connectionFactory = + new CachingConnectionFactory("localhost"); + connectionFactory.setUsername("guest"); + connectionFactory.setPassword("guest"); + return connectionFactory; + } + + @Bean + public RabbitTemplate rabbitTemplate() { + RabbitTemplate template = new RabbitTemplate(connectionFactory()); + template.setMessageConverter(jsonMessageConverter()); + configureRabbitTemplate(template); + return template; + } + + @Bean + public Jackson2JsonMessageConverter jsonMessageConverter() { + return new Jackson2JsonMessageConverter(); + } + + @Bean + public TopicExchange marketDataExchange() { + return new TopicExchange("app.stock.marketdata"); + } + + // additional code omitted for brevity + +} +---- + +In the Stock application, the server is configured by using the following `@Configuration` class: + +[source,java] +---- +@Configuration +public class RabbitServerConfiguration extends AbstractStockAppRabbitConfiguration { + + @Bean + public Queue stockRequestQueue() { + return new Queue("app.stock.request"); + } +} +---- + +This is the end of the whole inheritance chain of `@Configuration` classes. +The end result is that `TopicExchange` and `Queue` are declared to the broker upon application startup. +There is no binding of `TopicExchange` to a queue in the server configuration, as that is done in the client application. +The stock request queue, however, is automatically bound to the AMQP default exchange. +This behavior is defined by the specification. + +The client `@Configuration` class is a little more interesting. +Its declaration follows: + +[source,java] +---- +@Configuration +public class RabbitClientConfiguration extends AbstractStockAppRabbitConfiguration { + + @Value("${stocks.quote.pattern}") + private String marketDataRoutingKey; + + @Bean + public Queue marketDataQueue() { + return amqpAdmin().declareQueue(); + } + + /** + * Binds to the market data exchange. + * Interested in any stock quotes + * that match its routing key. + */ + @Bean + public Binding marketDataBinding() { + return BindingBuilder.bind( + marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey); + } + + // additional code omitted for brevity + +} +---- + +The client declares another queue through the `declareQueue()` method on the `AmqpAdmin`. +It binds that queue to the market data exchange with a routing pattern that is externalized in a properties file. + + +[[builder-api]] +== Builder API for Queues and Exchanges + +Version 1.6 introduces a convenient fluent API for configuring `Queue` and `Exchange` objects when using Java configuration. +The following example shows how to use it: + +[source, java] +---- +@Bean +public Queue queue() { + return QueueBuilder.nonDurable("foo") + .autoDelete() + .exclusive() + .withArgument("foo", "bar") + .build(); +} + +@Bean +public Exchange exchange() { + return ExchangeBuilder.directExchange("foo") + .autoDelete() + .internal() + .withArgument("foo", "bar") + .build(); +} +---- + +See the Javadoc for https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/core/QueueBuilder.html[`org.springframework.amqp.core.QueueBuilder`] and https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/core/ExchangeBuilder.html[`org.springframework.amqp.core.ExchangeBuilder`] for more information. + +Starting with version 2.0, the `ExchangeBuilder` now creates durable exchanges by default, to be consistent with the simple constructors on the individual `AbstractExchange` classes. +To make a non-durable exchange with the builder, use `.durable(false)` before invoking `.build()`. +The `durable()` method with no parameter is no longer provided. + +Version 2.2 introduced fluent APIs to add "well known" exchange and queue arguments... + +[source, java] +---- +@Bean +public Queue allArgs1() { + return QueueBuilder.nonDurable("all.args.1") + .ttl(1000) + .expires(200_000) + .maxLength(42) + .maxLengthBytes(10_000) + .overflow(Overflow.rejectPublish) + .deadLetterExchange("dlx") + .deadLetterRoutingKey("dlrk") + .maxPriority(4) + .lazy() + .leaderLocator(LeaderLocator.minLeaders) + .singleActiveConsumer() + .build(); +} + +@Bean +public DirectExchange ex() { + return ExchangeBuilder.directExchange("ex.with.alternate") + .durable(true) + .alternate("alternate") + .build(); +} +---- + +[[collection-declaration]] +== Declaring Collections of Exchanges, Queues, and Bindings + +You can wrap collections of `Declarable` objects (`Queue`, `Exchange`, and `Binding`) in `Declarables` objects. +The `RabbitAdmin` detects such beans (as well as discrete `Declarable` beans) in the application context, and declares the contained objects on the broker whenever a connection is established (initially and after a connection failure). +The following example shows how to do so: + +[source, java] +---- +@Configuration +public static class Config { + + @Bean + public CachingConnectionFactory cf() { + return new CachingConnectionFactory("localhost"); + } + + @Bean + public RabbitAdmin admin(ConnectionFactory cf) { + return new RabbitAdmin(cf); + } + + @Bean + public DirectExchange e1() { + return new DirectExchange("e1", false, true); + } + + @Bean + public Queue q1() { + return new Queue("q1", false, false, true); + } + + @Bean + public Binding b1() { + return BindingBuilder.bind(q1()).to(e1()).with("k1"); + } + + @Bean + public Declarables es() { + return new Declarables( + new DirectExchange("e2", false, true), + new DirectExchange("e3", false, true)); + } + + @Bean + public Declarables qs() { + return new Declarables( + new Queue("q2", false, false, true), + new Queue("q3", false, false, true)); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public Declarables prototypes() { + return new Declarables(new Queue(this.prototypeQueueName, false, false, true)); + } + + @Bean + public Declarables bs() { + return new Declarables( + new Binding("q2", DestinationType.QUEUE, "e2", "k2", null), + new Binding("q3", DestinationType.QUEUE, "e3", "k3", null)); + } + + @Bean + public Declarables ds() { + return new Declarables( + new DirectExchange("e4", false, true), + new Queue("q4", false, false, true), + new Binding("q4", DestinationType.QUEUE, "e4", "k4", null)); + } + +} +---- + +IMPORTANT: In versions prior to 2.1, you could declare multiple `Declarable` instances by defining beans of type `Collection`. +This can cause undesirable side effects in some cases, because the admin has to iterate over all `Collection` beans. + +Version 2.2 added the `getDeclarablesByType` method to `Declarables`; this can be used as a convenience, for example, when declaring the listener container bean(s). + +[source, java] +---- +public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, + Declarables mixedDeclarables, MessageListener listener) { + + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory); + container.setQueues(mixedDeclarables.getDeclarablesByType(Queue.class).toArray(new Queue[0])); + container.setMessageListener(listener); + return container; +} +---- + +[[conditional-declaration]] +== Conditional Declaration + +By default, all queues, exchanges, and bindings are declared by all `RabbitAdmin` instances (assuming they have `auto-startup="true"`) in the application context. + +Starting with version 2.1.9, the `RabbitAdmin` has a new property `explicitDeclarationsOnly` (which is `false` by default); when this is set to `true`, the admin will only declare beans that are explicitly configured to be declared by that admin. + +NOTE: Starting with the 1.2 release, you can conditionally declare these elements. +This is particularly useful when an application connects to multiple brokers and needs to specify with which brokers a particular element should be declared. + +The classes representing these elements implement `Declarable`, which has two methods: `shouldDeclare()` and `getDeclaringAdmins()`. +The `RabbitAdmin` uses these methods to determine whether a particular instance should actually process the declarations on its `Connection`. + +The properties are available as attributes in the namespace, as shown in the following examples: + +[source,xml] +---- + + + + + + + + + + + + + + + + + + + +---- + +NOTE: By default, the `auto-declare` attribute is `true` and, if the `declared-by` is not supplied (or is empty), then all `RabbitAdmin` instances declare the object (as long as the admin's `auto-startup` attribute is `true`, the default, and the admin's `explicit-declarations-only` attribute is false). + +Similarly, you can use Java-based `@Configuration` to achieve the same effect. +In the following example, the components are declared by `admin1` but not by `admin2`: + +[source,java] +---- +@Bean +public RabbitAdmin admin1() { + return new RabbitAdmin(cf1()); +} + +@Bean +public RabbitAdmin admin2() { + return new RabbitAdmin(cf2()); +} + +@Bean +public Queue queue() { + Queue queue = new Queue("foo"); + queue.setAdminsThatShouldDeclare(admin1()); + return queue; +} + +@Bean +public Exchange exchange() { + DirectExchange exchange = new DirectExchange("bar"); + exchange.setAdminsThatShouldDeclare(admin1()); + return exchange; +} + +@Bean +public Binding binding() { + Binding binding = new Binding("foo", DestinationType.QUEUE, exchange().getName(), "foo", null); + binding.setAdminsThatShouldDeclare(admin1()); + return binding; +} +---- + +[[note-id-name]] +== A Note On the `id` and `name` Attributes + +The `name` attribute on `` and `` elements reflects the name of the entity in the broker. +For queues, if the `name` is omitted, an anonymous queue is created (see xref:amqp/broker-configuration.adoc#anonymous-queue[`AnonymousQueue`]). + +In versions prior to 2.0, the `name` was also registered as a bean name alias (similar to `name` on `` elements). + +This caused two problems: + +* It prevented the declaration of a queue and exchange with the same name. +* The alias was not resolved if it contained a SpEL expression (`#{...}`). + +Starting with version 2.0, if you declare one of these elements with both an `id` _and_ a `name` attribute, the name is no longer declared as a bean name alias. +If you wish to declare a queue and exchange with the same `name`, you must provide an `id`. + +There is no change if the element has only a `name` attribute. +The bean can still be referenced by the `name` -- for example, in binding declarations. +However, you still cannot reference it if the name contains SpEL -- you must provide an `id` for reference purposes. + + +[[anonymous-queue]] +== `AnonymousQueue` + +In general, when you need a uniquely-named, exclusive, auto-delete queue, we recommend that you use the `AnonymousQueue` +instead of broker-defined queue names (using `""` as a `Queue` name causes the broker to generate the queue +name). + +This is because: + +. The queues are actually declared when the connection to the broker is established. +This is long after the beans are created and wired together. +Beans that use the queue need to know its name. +In fact, the broker might not even be running when the application is started. +. If the connection to the broker is lost for some reason, the admin re-declares the `AnonymousQueue` with the same name. +If we used broker-declared queues, the queue name would change. + +You can control the format of the queue name used by `AnonymousQueue` instances. + +By default, the queue name is prefixed by `spring.gen-` followed by a base64 representation of the `UUID` -- for example: `spring.gen-MRBv9sqISkuCiPfOYfpo4g`. + +You can provide an `AnonymousQueue.NamingStrategy` implementation in a constructor argument. +The following example shows how to do so: + +[source, java] +---- +@Bean +public Queue anon1() { + return new AnonymousQueue(); +} + +@Bean +public Queue anon2() { + return new AnonymousQueue(new AnonymousQueue.Base64UrlNamingStrategy("something-")); +} + +@Bean +public Queue anon3() { + return new AnonymousQueue(AnonymousQueue.UUIDNamingStrategy.DEFAULT); +} +---- + +The first bean generates a queue name prefixed by `spring.gen-` followed by a base64 representation of the `UUID` -- for +example: `spring.gen-MRBv9sqISkuCiPfOYfpo4g`. +The second bean generates a queue name prefixed by `something-` followed by a base64 representation of the `UUID`. +The third bean generates a name by using only the UUID (no base64 conversion) -- for example, `f20c818a-006b-4416-bf91-643590fedb0e`. + +The base64 encoding uses the "`URL and Filename Safe Alphabet`" from RFC 4648. +Trailing padding characters (`=`) are removed. + +You can provide your own naming strategy, whereby you can include other information (such as the application name or client host) in the queue name. + +You can specify the naming strategy when you use XML configuration. +The `naming-strategy` attribute is present on the `` element +for a bean reference that implements `AnonymousQueue.NamingStrategy`. +The following examples show how to specify the naming strategy in various ways: + +[source, xml] +---- + + + + + + + + + + + +---- + +The first example creates names such as `spring.gen-MRBv9sqISkuCiPfOYfpo4g`. +The second example creates names with a String representation of a UUID. +The third example creates names such as `custom.gen-MRBv9sqISkuCiPfOYfpo4g`. + +You can also provide your own naming strategy bean. + +Starting with version 2.1, anonymous queues are declared with argument `Queue.X_QUEUE_LEADER_LOCATOR` set to `client-local` by default. +This ensures that the queue is declared on the node to which the application is connected. +You can revert to the previous behavior by calling `queue.setLeaderLocator(null)` after constructing the instance. + +[[declarable-recovery]] +== Recovering Auto-Delete Declarations + +Normally, the `RabbitAdmin` (s) only recover queues/exchanges/bindings that are declared as beans in the application context; if any such declarations are auto-delete, they will be removed by the broker if the connection is lost. +When the connection is re-established, the admin will redeclare the entities. +Normally, entities created by calling `admin.declareQueue(...)`, `admin.declareExchange(...)` and `admin.declareBinding(...)` will not be recovered. + +Starting with version 2.4, the admin has a new property `redeclareManualDeclarations`; when `true`, the admin will recover these entities in addition to the beans in the application context. + +Recovery of individual declarations will not be performed if `deleteQueue(...)`, `deleteExchange(...)` or `removeBinding(...)` is called. +Associated bindings are removed from the recoverable entities when queues and exchanges are deleted. + +Finally, calling `resetAllManualDeclarations()` will prevent the recovery of any previously declared entities. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/broker-events.adoc b/src/reference/antora/modules/ROOT/pages/amqp/broker-events.adoc new file mode 100644 index 00000000..ab9ad77b --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/broker-events.adoc @@ -0,0 +1,26 @@ +[[broker-events]] += Broker Event Listener + +When the https://www.rabbitmq.com/event-exchange.html[Event Exchange Plugin] is enabled, if you add a bean of type `BrokerEventListener` to the application context, it publishes selected broker events as `BrokerEvent` instances, which can be consumed with a normal Spring `ApplicationListener` or `@EventListener` method. +Events are published by the broker to a topic exchange `amq.rabbitmq.event` with a different routing key for each event type. +The listener uses event keys, which are used to bind an `AnonymousQueue` to the exchange so the listener receives only selected events. +Since it is a topic exchange, wildcards can be used (as well as explicitly requesting specific events), as the following example shows: + +[source, java] +---- +@Bean +public BrokerEventListener eventListener() { + return new BrokerEventListener(connectionFactory(), "user.deleted", "channel.#", "queue.#"); +} +---- + +You can further narrow the received events in individual event listeners, by using normal Spring techniques, as the following example shows: + +[source, java] +---- +@EventListener(condition = "event.eventType == 'queue.created'") +public void listener(BrokerEvent event) { + ... +} +---- + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/connections.adoc b/src/reference/antora/modules/ROOT/pages/amqp/connections.adoc new file mode 100644 index 00000000..d2199ecd --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/connections.adoc @@ -0,0 +1,839 @@ +[[connections]] += Connection and Resource Management + +Whereas the AMQP model we described in the previous section is generic and applicable to all implementations, when we get into the management of resources, the details are specific to the broker implementation. +Therefore, in this section, we focus on code that exists only within our "`spring-rabbit`" module since, at this point, RabbitMQ is the only supported implementation. + +The central component for managing a connection to the RabbitMQ broker is the `ConnectionFactory` interface. +The responsibility of a `ConnectionFactory` implementation is to provide an instance of `org.springframework.amqp.rabbit.connection.Connection`, which is a wrapper for `com.rabbitmq.client.Connection`. + +[[choosing-factory]] +== Choosing a Connection Factory + +There are three connection factories to chose from + +* `PooledChannelConnectionFactory` +* `ThreadChannelConnectionFactory` +* `CachingConnectionFactory` + +The first two were added in version 2.3. + +For most use cases, the `CachingConnectionFactory` should be used. +The `ThreadChannelConnectionFactory` can be used if you want to ensure strict message ordering without the need to use xref:amqp/template.adoc#scoped-operations[Scoped Operations]. +The `PooledChannelConnectionFactory` is similar to the `CachingConnectionFactory` in that it uses a single connection and a pool of channels. +It's implementation is simpler but it doesn't support correlated publisher confirmations. + +Simple publisher confirmations are supported by all three factories. + +When configuring a `RabbitTemplate` to use a xref:amqp/template.adoc#separate-connection[separate connection], you can now, starting with version 2.3.2, configure the publishing connection factory to be a different type. +By default, the publishing factory is the same type and any properties set on the main factory are also propagated to the publishing factory. + +[[pooledchannelconnectionfactory]] +=== `PooledChannelConnectionFactory` + +This factory manages a single connection and two pools of channels, based on the Apache Pool2. +One pool is for transactional channels, the other is for non-transactional channels. +The pools are `GenericObjectPool` s with default configuration; a callback is provided to configure the pools; refer to the Apache documentation for more information. + +The Apache `commons-pool2` jar must be on the class path to use this factory. + +[source, java] +---- +@Bean +PooledChannelConnectionFactory pcf() throws Exception { + ConnectionFactory rabbitConnectionFactory = new ConnectionFactory(); + rabbitConnectionFactory.setHost("localhost"); + PooledChannelConnectionFactory pcf = new PooledChannelConnectionFactory(rabbitConnectionFactory); + pcf.setPoolConfigurer((pool, tx) -> { + if (tx) { + // configure the transactional pool + } + else { + // configure the non-transactional pool + } + }); + return pcf; +} +---- + +[[threadchannelconnectionfactory]] +=== `ThreadChannelConnectionFactory` + +This factory manages a single connection and two `ThreadLocal` s, one for transactional channels, the other for non-transactional channels. +This factory ensures that all operations on the same thread use the same channel (as long as it remains open). +This facilitates strict message ordering without the need for xref:amqp/template.adoc#scoped-operations[Scoped Operations]. +To avoid memory leaks, if your application uses many short-lived threads, you must call the factory's `closeThreadChannel()` to release the channel resource. +Starting with version 2.3.7, a thread can transfer its channel(s) to another thread. +See xref:amqp/template.adoc#multi-strict[Strict Message Ordering in a Multi-Threaded Environment] for more information. + +[[cachingconnectionfactory]] +=== `CachingConnectionFactory` + +The third implementation provided is the `CachingConnectionFactory`, which, by default, establishes a single connection proxy that can be shared by the application. +Sharing of the connection is possible since the "`unit of work`" for messaging with AMQP is actually a "`channel`" (in some ways, this is similar to the relationship between a connection and a session in JMS). +The connection instance provides a `createChannel` method. +The `CachingConnectionFactory` implementation supports caching of those channels, and it maintains separate caches for channels based on whether they are transactional. +When creating an instance of `CachingConnectionFactory`, you can provide the 'hostname' through the constructor. +You should also provide the 'username' and 'password' properties. +To configure the size of the channel cache (the default is 25), you can call the +`setChannelCacheSize()` method. + +Starting with version 1.3, you can configure the `CachingConnectionFactory` to cache connections as well as only channels. +In this case, each call to `createConnection()` creates a new connection (or retrieves an idle one from the cache). +Closing a connection returns it to the cache (if the cache size has not been reached). +Channels created on such connections are also cached. +The use of separate connections might be useful in some environments, such as consuming from an HA cluster, in +conjunction with a load balancer, to connect to different cluster members, and others. +To cache connections, set the `cacheMode` to `CacheMode.CONNECTION`. + +NOTE: This does not limit the number of connections. +Rather, it specifies how many idle open connections are allowed. + +Starting with version 1.5.5, a new property called `connectionLimit` is provided. +When this property is set, it limits the total number of connections allowed. +When set, if the limit is reached, the `channelCheckoutTimeLimit` is used to wait for a connection to become idle. +If the time is exceeded, an `AmqpTimeoutException` is thrown. + +[IMPORTANT] +====== +When the cache mode is `CONNECTION`, automatic declaration of queues and others +(See xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc#automatic-declaration[Automatic Declaration of Exchanges, Queues, and Bindings]) is NOT supported. + +Also, at the time of this writing, the `amqp-client` library by default creates a fixed thread pool for each connection (default size: `Runtime.getRuntime().availableProcessors() * 2` threads). +When using a large number of connections, you should consider setting a custom `executor` on the `CachingConnectionFactory`. +Then, the same executor can be used by all connections and its threads can be shared. +The executor's thread pool should be unbounded or set appropriately for the expected use (usually, at least one thread per connection). +If multiple channels are created on each connection, the pool size affects the concurrency, so a variable (or simple cached) thread pool executor would be most suitable. +====== + +It is important to understand that the cache size is (by default) not a limit but is merely the number of channels that can be cached. +With a cache size of, say, 10, any number of channels can actually be in use. +If more than 10 channels are being used and they are all returned to the cache, 10 go in the cache. +The remainder are physically closed. + +Starting with version 1.6, the default channel cache size has been increased from 1 to 25. +In high volume, multi-threaded environments, a small cache means that channels are created and closed at a high rate. +Increasing the default cache size can avoid this overhead. +You should monitor the channels in use through the RabbitMQ Admin UI and consider increasing the cache size further if you +see many channels being created and closed. +The cache grows only on-demand (to suit the concurrency requirements of the application), so this change does not +impact existing low-volume applications. + +Starting with version 1.4.2, the `CachingConnectionFactory` has a property called `channelCheckoutTimeout`. +When this property is greater than zero, the `channelCacheSize` becomes a limit on the number of channels that can be created on a connection. +If the limit is reached, calling threads block until a channel is available or this timeout is reached, in which case a `AmqpTimeoutException` is thrown. + +WARNING: Channels used within the framework (for example, +`RabbitTemplate`) are reliably returned to the cache. +If you create channels outside of the framework, (for example, +by accessing the connections directly and invoking `createChannel()`), you must return them (by closing) reliably, perhaps in a `finally` block, to avoid running out of channels. + +The following example shows how to create a new `connection`: + +[source,java] +---- +CachingConnectionFactory connectionFactory = new CachingConnectionFactory("somehost"); +connectionFactory.setUsername("guest"); +connectionFactory.setPassword("guest"); + +Connection connection = connectionFactory.createConnection(); +---- + +When using XML, the configuration might look like the following example: + +[source,xml] +---- + + + + + +---- + +NOTE: There is also a `SingleConnectionFactory` implementation that is available only in the unit test code of the framework. +It is simpler than `CachingConnectionFactory`, since it does not cache channels, but it is not intended for practical usage outside of simple tests due to its lack of performance and resilience. +If you need to implement your own `ConnectionFactory` for some reason, the `AbstractConnectionFactory` base class may provide a nice starting point. + +A `ConnectionFactory` can be created quickly and conveniently by using the rabbit namespace, as follows: + +[source,xml] +---- + +---- + +In most cases, this approach is preferable, since the framework can choose the best defaults for you. +The created instance is a `CachingConnectionFactory`. +Keep in mind that the default cache size for channels is 25. +If you want more channels to be cached, set a larger value by setting the 'channelCacheSize' property. +In XML it would look like as follows: + +[source,xml] +---- + + + + + + +---- + +Also, with the namespace, you can add the 'channel-cache-size' attribute, as follows: + +[source,xml] +---- + +---- + +The default cache mode is `CHANNEL`, but you can configure it to cache connections instead. +In the following example, we use `connection-cache-size`: + +[source,xml] +---- + +---- + +You can provide host and port attributes by using the namespace, as follows: + +[source,xml] +---- + +---- + +Alternatively, if running in a clustered environment, you can use the addresses attribute, as follows: + +[source,xml] +---- + +---- + +See xref:amqp/connections.adoc#cluster[Connecting to a Cluster] for information about `address-shuffle-mode`. + +The following example with a custom thread factory that prefixes thread names with `rabbitmq-`: + +[source, xml] +---- + + + + + + +---- + +[[addressresolver]] +== AddressResolver + +Starting with version 2.1.15, you can now use an `AddressResolver` to resolve the connection address(es). +This will override any settings of the `addresses` and `host/port` properties. + +[[naming-connections]] +== Naming Connections + +Starting with version 1.7, a `ConnectionNameStrategy` is provided for the injection into the `AbstractionConnectionFactory`. +The generated name is used for the application-specific identification of the target RabbitMQ connection. +The connection name is displayed in the management UI if the RabbitMQ server supports it. +This value does not have to be unique and cannot be used as a connection identifier -- for example, in HTTP API requests. +This value is supposed to be human-readable and is a part of `ClientProperties` under the `connection_name` key. +You can use a simple Lambda, as follows: + +[source, java] +---- +connectionFactory.setConnectionNameStrategy(connectionFactory -> "MY_CONNECTION"); +---- + +The `ConnectionFactory` argument can be used to distinguish target connection names by some logic. +By default, the `beanName` of the `AbstractConnectionFactory`, a hex string representing the object, and an internal counter are used to generate the `connection_name`. +The `` namespace component is also supplied with the `connection-name-strategy` attribute. + +An implementation of `SimplePropertyValueConnectionNameStrategy` sets the connection name to an application property. +You can declare it as a `@Bean` and inject it into the connection factory, as the following example shows: + +[source, java] +---- +@Bean +public SimplePropertyValueConnectionNameStrategy cns() { + return new SimplePropertyValueConnectionNameStrategy("spring.application.name"); +} + +@Bean +public ConnectionFactory rabbitConnectionFactory(ConnectionNameStrategy cns) { + CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); + ... + connectionFactory.setConnectionNameStrategy(cns); + return connectionFactory; +} +---- + +The property must exist in the application context's `Environment`. + +NOTE: When using Spring Boot and its autoconfigured connection factory, you need only declare the `ConnectionNameStrategy` `@Bean`. +Boot auto-detects the bean and wires it into the factory. + +[[blocked-connections-and-resource-constraints]] +== Blocked Connections and Resource Constraints + +The connection might be blocked for interaction from the broker that corresponds to the https://www.rabbitmq.com/memory.html[Memory Alarm]. +Starting with version 2.0, the `org.springframework.amqp.rabbit.connection.Connection` can be supplied with `com.rabbitmq.client.BlockedListener` instances to be notified for connection blocked and unblocked events. +In addition, the `AbstractConnectionFactory` emits a `ConnectionBlockedEvent` and `ConnectionUnblockedEvent`, respectively, through its internal `BlockedListener` implementation. +These let you provide application logic to react appropriately to problems on the broker and (for example) take some corrective actions. + +IMPORTANT: When the application is configured with a single `CachingConnectionFactory`, as it is by default with Spring Boot auto-configuration, the application stops working when the connection is blocked by the Broker. +And when it is blocked by the Broker, any of its clients stop to work. +If we have producers and consumers in the same application, we may end up with a deadlock when producers are blocking the connection (because there are no resources on the Broker any more) and consumers cannot free them (because the connection is blocked). +To mitigate the problem, we suggest having one more separate `CachingConnectionFactory` instance with the same options -- one for producers and one for consumers. +A separate `CachingConnectionFactory` is not possible for transactional producers that execute on a consumer thread, since they should reuse the `Channel` associated with the consumer transactions. + +Starting with version 2.0.2, the `RabbitTemplate` has a configuration option to automatically use a second connection factory, unless transactions are being used. +See xref:amqp/template.adoc#separate-connection[Using a Separate Connection] for more information. +The `ConnectionNameStrategy` for the publisher connection is the same as the primary strategy with `.publisher` appended to the result of calling the method. + +Starting with version 1.7.7, an `AmqpResourceNotAvailableException` is provided, which is thrown when `SimpleConnection.createChannel()` cannot create a `Channel` (for example, because the `channelMax` limit is reached and there are no available channels in the cache). +You can use this exception in the `RetryPolicy` to recover the operation after some back-off. + +[[connection-factory]] +== Configuring the Underlying Client Connection Factory + +The `CachingConnectionFactory` uses an instance of the Rabbit client `ConnectionFactory`. +A number of configuration properties are passed through (`host`, `port`, `userName`, `password`, `requestedHeartBeat`, and `connectionTimeout` for example) when setting the equivalent property on the `CachingConnectionFactory`. +To set other properties (`clientProperties`, for example), you can define an instance of the Rabbit factory and provide a reference to it by using the appropriate constructor of the `CachingConnectionFactory`. +When using the namespace (xref:amqp/connections.adoc[as described earlier]), you need to provide a reference to the configured factory in the `connection-factory` attribute. +For convenience, a factory bean is provided to assist in configuring the connection factory in a Spring application context, as discussed in xref:amqp/connections.adoc#rabbitconnectionfactorybean-configuring-ssl[the next section]. + +[source,xml] +---- + +---- + +NOTE: The 4.0.x client enables automatic recovery by default. +While compatible with this feature, Spring AMQP has its own recovery mechanisms and the client recovery feature generally is not needed. +We recommend disabling `amqp-client` automatic recovery, to avoid getting `AutoRecoverConnectionNotCurrentlyOpenException` instances when the broker is available but the connection has not yet recovered. +You may notice this exception, for example, when a `RetryTemplate` is configured in a `RabbitTemplate`, even when failing over to another broker in a cluster. +Since the auto-recovering connection recovers on a timer, the connection may be recovered more quickly by using Spring AMQP's recovery mechanisms. +Starting with version 1.7.1, Spring AMQP disables `amqp-client` automatic recovery unless you explicitly create your own RabbitMQ connection factory and provide it to the `CachingConnectionFactory`. +RabbitMQ `ConnectionFactory` instances created by the `RabbitConnectionFactoryBean` also have the option disabled by default. + +[[rabbitconnectionfactorybean-configuring-ssl]] +== `RabbitConnectionFactoryBean` and Configuring SSL + +Starting with version 1.4, a convenient `RabbitConnectionFactoryBean` is provided to enable convenient configuration of SSL properties on the underlying client connection factory by using dependency injection. +Other setters delegate to the underlying factory. +Previously, you had to configure the SSL options programmatically. +The following example shows how to configure a `RabbitConnectionFactoryBean`: + +[source,java,role=primary] +.Java +---- +@Bean +RabbitConnectionFactoryBean rabbitConnectionFactory() { + RabbitConnectionFactoryBean factoryBean = new RabbitConnectionFactoryBean(); + factoryBean.setUseSSL(true); + factoryBean.setSslPropertiesLocation(new ClassPathResource("secrets/rabbitSSL.properties")); + return factoryBean; +} + +@Bean +CachingConnectionFactory connectionFactory(ConnectionFactory rabbitConnectionFactory) { + CachingConnectionFactory ccf = new CachingConnectionFactory(rabbitConnectionFactory); + ccf.setHost("..."); + // ... + return ccf; +} +---- +[source,properties,role=secondary] +.Boot application.properties +---- +spring.rabbitmq.ssl.enabled:true +spring.rabbitmq.ssl.keyStore=... +spring.rabbitmq.ssl.keyStoreType=jks +spring.rabbitmq.ssl.keyStorePassword=... +spring.rabbitmq.ssl.trustStore=... +spring.rabbitmq.ssl.trustStoreType=jks +spring.rabbitmq.ssl.trustStorePassword=... +spring.rabbitmq.host=... +... +---- +[source,xml,role=secondary] +.XML +---- + + + + + + +---- + +See the https://www.rabbitmq.com/ssl.html[RabbitMQ Documentation] for information about configuring SSL. +Omit the `keyStore` and `trustStore` configuration to connect over SSL without certificate validation. +The next example shows how you can provide key and trust store configuration. + +The `sslPropertiesLocation` property is a Spring `Resource` pointing to a properties file containing the following keys: + +[source] +---- +keyStore=file:/secret/keycert.p12 +trustStore=file:/secret/trustStore +keyStore.passPhrase=secret +trustStore.passPhrase=secret +---- + +The `keyStore` and `truststore` are Spring `Resources` pointing to the stores. +Typically this properties file is secured by the operating system with the application having read access. + +Starting with Spring AMQP version 1.5,you can set these properties directly on the factory bean. +If both discrete properties and `sslPropertiesLocation` is provided, properties in the latter override the +discrete values. + +IMPORTANT: Starting with version 2.0, the server certificate is validated by default because it is more secure. +If you wish to skip this validation for some reason, set the factory bean's `skipServerCertificateValidation` property to `true`. +Starting with version 2.1, the `RabbitConnectionFactoryBean` now calls `enableHostnameVerification()` by default. +To revert to the previous behavior, set the `enableHostnameVerification` property to `false`. + +IMPORTANT: Starting with version 2.2.5, the factory bean will always use TLS v1.2 by default; previously, it used v1.1 in some cases and v1.2 in others (depending on other properties). +If you need to use v1.1 for some reason, set the `sslAlgorithm` property: `setSslAlgorithm("TLSv1.1")`. + +[[cluster]] +== Connecting to a Cluster + +To connect to a cluster, configure the `addresses` property on the `CachingConnectionFactory`: + +[source, java] +---- +@Bean +public CachingConnectionFactory ccf() { + CachingConnectionFactory ccf = new CachingConnectionFactory(); + ccf.setAddresses("host1:5672,host2:5672,host3:5672"); + return ccf; +} +---- + +Starting with version 3.0, the underlying connection factory will attempt to connect to a host, by choosing a random address, whenever a new connection is established. +To revert to the previous behavior of attempting to connect from first to last, set the `addressShuffleMode` property to `AddressShuffleMode.NONE`. + +Starting with version 2.3, the `INORDER` shuffle mode was added, which means the first address is moved to the end after a connection is created. +You may wish to use this mode with the https://github.com/rabbitmq/rabbitmq-sharding[RabbitMQ Sharding Plugin] with `CacheMode.CONNECTION` and suitable concurrency if you wish to consume from all shards on all nodes. + +[source, java] +---- +@Bean +public CachingConnectionFactory ccf() { + CachingConnectionFactory ccf = new CachingConnectionFactory(); + ccf.setAddresses("host1:5672,host2:5672,host3:5672"); + ccf.setAddressShuffleMode(AddressShuffleMode.INORDER); + return ccf; +} +---- + +[[routing-connection-factory]] +== Routing Connection Factory + +Starting with version 1.3, the `AbstractRoutingConnectionFactory` has been introduced. +This factory provides a mechanism to configure mappings for several `ConnectionFactories` and determine a target `ConnectionFactory` by some `lookupKey` at runtime. +Typically, the implementation checks a thread-bound context. +For convenience, Spring AMQP provides the `SimpleRoutingConnectionFactory`, which gets the current thread-bound `lookupKey` from the `SimpleResourceHolder`. +The following examples shows how to configure a `SimpleRoutingConnectionFactory` in both XML and Java: + +[source,xml] +---- + + + + + + + + + + +---- + +[source,java] +---- +public class MyService { + + @Autowired + private RabbitTemplate rabbitTemplate; + + public void service(String vHost, String payload) { + SimpleResourceHolder.bind(rabbitTemplate.getConnectionFactory(), vHost); + rabbitTemplate.convertAndSend(payload); + SimpleResourceHolder.unbind(rabbitTemplate.getConnectionFactory()); + } + +} +---- + +It is important to unbind the resource after use. +For more information, see the https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/rabbit/connection/AbstractRoutingConnectionFactory.html[JavaDoc] for `AbstractRoutingConnectionFactory`. + +Starting with version 1.4, `RabbitTemplate` supports the SpEL `sendConnectionFactorySelectorExpression` and `receiveConnectionFactorySelectorExpression` properties, which are evaluated on each AMQP protocol interaction operation (`send`, `sendAndReceive`, `receive`, or `receiveAndReply`), resolving to a `lookupKey` value for the provided `AbstractRoutingConnectionFactory`. +You can use bean references, such as `@vHostResolver.getVHost(#root)` in the expression. +For `send` operations, the message to be sent is the root evaluation object. +For `receive` operations, the `queueName` is the root evaluation object. + +The routing algorithm is as follows: If the selector expression is `null` or is evaluated to `null` or the provided `ConnectionFactory` is not an instance of `AbstractRoutingConnectionFactory`, everything works as before, relying on the provided `ConnectionFactory` implementation. +The same occurs if the evaluation result is not `null`, but there is no target `ConnectionFactory` for that `lookupKey` and the `AbstractRoutingConnectionFactory` is configured with `lenientFallback = true`. +In the case of an `AbstractRoutingConnectionFactory`, it does fallback to its `routing` implementation based on `determineCurrentLookupKey()`. +However, if `lenientFallback = false`, an `IllegalStateException` is thrown. + +The namespace support also provides the `send-connection-factory-selector-expression` and `receive-connection-factory-selector-expression` attributes on the `` component. + +Also, starting with version 1.4, you can configure a routing connection factory in a listener container. +In that case, the list of queue names is used as the lookup key. +For example, if you configure the container with `setQueueNames("thing1", "thing2")`, the lookup key is `[thing1,thing]"` (note that there is no space in the key). + +Starting with version 1.6.9, you can add a qualifier to the lookup key by using `setLookupKeyQualifier` on the listener container. +Doing so enables, for example, listening to queues with the same name but in a different virtual host (where you would have a connection factory for each). + +For example, with lookup key qualifier `thing1` and a container listening to queue `thing2`, the lookup key you could register the target connection factory with could be `thing1[thing2]`. + +IMPORTANT: The target (and default, if provided) connection factories must have the same settings for publisher confirms and returns. +See xref:amqp/connections.adoc#cf-pub-conf-ret[Publisher Confirms and Returns]. + +Starting with version 2.4.4, this validation can be disabled. +If you have a case that the values between confirms and returns need to be unequal, you can use `AbstractRoutingConnectionFactory#setConsistentConfirmsReturns` to turn of the validation. +Note that the first connection factory added to `AbstractRoutingConnectionFactory` will determine the general values of `confirms` and `returns`. + +It may be useful if you have a case that certain messages you would to check confirms/returns and others you don't. +For example: + +[source, java] +---- +@Bean +public RabbitTemplate rabbitTemplate() { + final com.rabbitmq.client.ConnectionFactory cf = new com.rabbitmq.client.ConnectionFactory(); + cf.setHost("localhost"); + cf.setPort(5672); + + CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(cf); + cachingConnectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED); + + PooledChannelConnectionFactory pooledChannelConnectionFactory = new PooledChannelConnectionFactory(cf); + + final Map connectionFactoryMap = new HashMap<>(2); + connectionFactoryMap.put("true", cachingConnectionFactory); + connectionFactoryMap.put("false", pooledChannelConnectionFactory); + + final AbstractRoutingConnectionFactory routingConnectionFactory = new SimpleRoutingConnectionFactory(); + routingConnectionFactory.setConsistentConfirmsReturns(false); + routingConnectionFactory.setDefaultTargetConnectionFactory(pooledChannelConnectionFactory); + routingConnectionFactory.setTargetConnectionFactories(connectionFactoryMap); + + final RabbitTemplate rabbitTemplate = new RabbitTemplate(routingConnectionFactory); + + final Expression sendExpression = new SpelExpressionParser().parseExpression( + "messageProperties.headers['x-use-publisher-confirms'] ?: false"); + rabbitTemplate.setSendConnectionFactorySelectorExpression(sendExpression); +} +---- + +This way messages with the header `x-use-publisher-confirms: true` will be sent through the caching connection and you can ensure the message delivery. +See xref:amqp/connections.adoc#cf-pub-conf-ret[Publisher Confirms and Returns] for more information about ensuring message delivery. + +[[queue-affinity]] +== Queue Affinity and the `LocalizedQueueConnectionFactory` + +When using HA queues in a cluster, for the best performance, you may want to connect to the physical broker +where the lead queue resides. +The `CachingConnectionFactory` can be configured with multiple broker addresses. +This is to fail over and the client attempts to connect in accordance with the configured `AddressShuffleMode` order. +The `LocalizedQueueConnectionFactory` uses the REST API provided by the management plugin to determine which node is the lead for the queue. +It then creates (or retrieves from a cache) a `CachingConnectionFactory` that connects to just that node. +If the connection fails, the new lead node is determined and the consumer connects to it. +The `LocalizedQueueConnectionFactory` is configured with a default connection factory, in case the physical location of the queue cannot be determined, in which case it connects as normal to the cluster. + +The `LocalizedQueueConnectionFactory` is a `RoutingConnectionFactory` and the `SimpleMessageListenerContainer` uses the queue names as the lookup key as discussed in <> above. + +NOTE: For this reason (the use of the queue name for the lookup), the `LocalizedQueueConnectionFactory` can only be used if the container is configured to listen to a single queue. + +NOTE: The RabbitMQ management plugin must be enabled on each node. + +CAUTION: This connection factory is intended for long-lived connections, such as those used by the `SimpleMessageListenerContainer`. +It is not intended for short connection use, such as with a `RabbitTemplate` because of the overhead of invoking the REST API before making the connection. +Also, for publish operations, the queue is unknown, and the message is published to all cluster members anyway, so the logic of looking up the node has little value. + +The following example configuration shows how to configure the factories: + +[source, java] +---- +@Autowired +private ConfigurationProperties props; + +@Bean +public CachingConnectionFactory defaultConnectionFactory() { + CachingConnectionFactory cf = new CachingConnectionFactory(); + cf.setAddresses(this.props.getAddresses()); + cf.setUsername(this.props.getUsername()); + cf.setPassword(this.props.getPassword()); + cf.setVirtualHost(this.props.getVirtualHost()); + return cf; +} + +@Bean +public LocalizedQueueConnectionFactory queueAffinityCF( + @Qualifier("defaultConnectionFactory") ConnectionFactory defaultCF) { + return new LocalizedQueueConnectionFactory(defaultCF, + StringUtils.commaDelimitedListToStringArray(this.props.getAddresses()), + StringUtils.commaDelimitedListToStringArray(this.props.getAdminUris()), + StringUtils.commaDelimitedListToStringArray(this.props.getNodes()), + this.props.getVirtualHost(), this.props.getUsername(), this.props.getPassword(), + false, null); +} +---- + +Notice that the first three parameters are arrays of `addresses`, `adminUris`, and `nodes`. +These are positional in that, when a container attempts to connect to a queue, it uses the admin API to determine which node is the lead for the queue and connects to the address in the same array position as that node. + +IMPORTANT: Starting with version 3.0, the RabbitMQ `http-client` is no longer used to access the Rest API. +Instead, by default, the `WebClient` from Spring Webflux is used if `spring-webflux` is on the class path; otherwise a `RestTemplate` is used. + +To add `WebFlux` to the class path: + +.Maven +[source,xml,subs="+attributes"] +---- + + org.springframework.amqp + spring-rabbit + +---- +.Gradle +[source,groovy,subs="+attributes"] +---- +compile 'org.springframework.amqp:spring-rabbit' +---- + +You can also use other REST technology by implementing `LocalizedQueueConnectionFactory.NodeLocator` and overriding its `createClient, ``restCall`, and optionally, `close` methods. + +[source, java] +---- +lqcf.setNodeLocator(new NodeLocator() { + + @Override + public MyClient createClient(String userName, String password) { + ... + } + + @Override + public HashMap restCall(MyClient client, URI uri) { + ... + }); + +}); +---- + +The framework provides the `WebFluxNodeLocator` and `RestTemplateNodeLocator`, with the default as discussed above. + +[[cf-pub-conf-ret]] +== Publisher Confirms and Returns + +Confirmed (with correlation) and returned messages are supported by setting the `CachingConnectionFactory` property `publisherConfirmType` to `ConfirmType.CORRELATED` and the `publisherReturns` property to 'true'. + +When these options are set, `Channel` instances created by the factory are wrapped in an `PublisherCallbackChannel`, which is used to facilitate the callbacks. +When such a channel is obtained, the client can register a `PublisherCallbackChannel.Listener` with the `Channel`. +The `PublisherCallbackChannel` implementation contains logic to route a confirm or return to the appropriate listener. +These features are explained further in the following sections. + +See also xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns] and `simplePublisherConfirms` in xref:amqp/template.adoc#scoped-operations[Scoped Operations]. + +TIP: For some more background information, see the blog post by the RabbitMQ team titled https://www.rabbitmq.com/blog/2011/02/10/introducing-publisher-confirms/[Introducing Publisher Confirms]. + +[[connection-channel-listeners]] +== Connection and Channel Listeners + +The connection factory supports registering `ConnectionListener` and `ChannelListener` implementations. +This allows you to receive notifications for connection and channel related events. +(A `ConnectionListener` is used by the `RabbitAdmin` to perform declarations when the connection is established - see xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc#automatic-declaration[Automatic Declaration of Exchanges, Queues, and Bindings] for more information). +The following listing shows the `ConnectionListener` interface definition: + +[source, java] +---- +@FunctionalInterface +public interface ConnectionListener { + + void onCreate(Connection connection); + + default void onClose(Connection connection) { + } + + default void onShutDown(ShutdownSignalException signal) { + } + +} +---- + +Starting with version 2.0, the `org.springframework.amqp.rabbit.connection.Connection` object can be supplied with `com.rabbitmq.client.BlockedListener` instances to be notified for connection blocked and unblocked events. +The following example shows the ChannelListener interface definition: + +[source, java] +---- +@FunctionalInterface +public interface ChannelListener { + + void onCreate(Channel channel, boolean transactional); + + default void onShutDown(ShutdownSignalException signal) { + } + +} +---- + +See xref:amqp/template.adoc#publishing-is-async[Publishing is Asynchronous -- How to Detect Successes and Failures] for one scenario where you might want to register a `ChannelListener`. + +[[channel-close-logging]] +== Logging Channel Close Events + +Version 1.5 introduced a mechanism to enable users to control logging levels. + +The `AbstractConnectionFactory` uses a default strategy to log channel closures as follows: + +* Normal channel closes (200 OK) are not logged. +* If a channel is closed due to a failed passive queue declaration, it is logged at DEBUG level. +* If a channel is closed because the `basic.consume` is refused due to an exclusive consumer condition, it is logged at +DEBUG level (since 3.1, previously INFO). +* All others are logged at ERROR level. + +To modify this behavior, you can inject a custom `ConditionalExceptionLogger` into the +`CachingConnectionFactory` in its `closeExceptionLogger` property. + +Also, the `AbstractConnectionFactory.DefaultChannelCloseLogger` is now public, allowing it to be sub classed. + +See also xref:amqp/receiving-messages/consumer-events.adoc[Consumer Events]. + +[[runtime-cache-properties]] +== Runtime Cache Properties + +Staring with version 1.6, the `CachingConnectionFactory` now provides cache statistics through the `getCacheProperties()` +method. +These statistics can be used to tune the cache to optimize it in production. +For example, the high water marks can be used to determine whether the cache size should be increased. +If it equals the cache size, you might want to consider increasing further. +The following table describes the `CacheMode.CHANNEL` properties: + +.Cache properties for CacheMode.CHANNEL +[cols="2l,4", options="header"] +|=== +|Property + +|Meaning + +|connectionName + +|The name of the connection generated by the `ConnectionNameStrategy`. + +|channelCacheSize + +|The currently configured maximum channels that are allowed to be idle. + +|localPort + +|The local port for the connection (if available). +This can be used to correlate with connections and channels on the RabbitMQ Admin UI. + +|idleChannelsTx + +|The number of transactional channels that are currently idle (cached). + +|idleChannelsNotTx + +|The number of non-transactional channels that are currently idle (cached). + +|idleChannelsTxHighWater + +|The maximum number of transactional channels that have been concurrently idle (cached). + +|idleChannelsNotTxHighWater + +|The maximum number of non-transactional channels have been concurrently idle (cached). + +|=== + +The following table describes the `CacheMode.CONNECTION` properties: + +.Cache properties for CacheMode.CONNECTION +[cols="2l,4", options="header"] +|=== +|Property + +|Meaning + +|connectionName: + +|The name of the connection generated by the `ConnectionNameStrategy`. + +|openConnections + +|The number of connection objects representing connections to brokers. + +|channelCacheSize + +|The currently configured maximum channels that are allowed to be idle. + +|connectionCacheSize + +|The currently configured maximum connections that are allowed to be idle. + +|idleConnections + +|The number of connections that are currently idle. + +|idleConnectionsHighWater + +|The maximum number of connections that have been concurrently idle. + +|idleChannelsTx: + +|The number of transactional channels that are currently idle (cached) for this connection. +You can use the `localPort` part of the property name to correlate with connections and channels on the RabbitMQ Admin UI. + +|idleChannelsNotTx: + +|The number of non-transactional channels that are currently idle (cached) for this connection. +The `localPort` part of the property name can be used to correlate with connections and channels on the RabbitMQ Admin UI. + +|idleChannelsTxHighWater: + +|The maximum number of transactional channels that have been concurrently idle (cached). +The localPort part of the property name can be used to correlate with connections and channels on the RabbitMQ Admin UI. + +|idleChannelsNotTxHighWater: + +|The maximum number of non-transactional channels have been concurrently idle (cached). +You can use the `localPort` part of the property name to correlate with connections and channels on the RabbitMQ Admin UI. + +|=== + +The `cacheMode` property (`CHANNEL` or `CONNECTION`) is also included. + +.JVisualVM Example +image::cacheStats.png[align="center"] + +[[auto-recovery]] +== RabbitMQ Automatic Connection/Topology recovery + +Since the first version of Spring AMQP, the framework has provided its own connection and channel recovery in the event of a broker failure. +Also, as discussed in xref:amqp/broker-configuration.adoc[Configuring the Broker], the `RabbitAdmin` re-declares any infrastructure beans (queues and others) when the connection is re-established. +It therefore does not rely on the https://www.rabbitmq.com/api-guide.html#recovery[auto-recovery] that is now provided by the `amqp-client` library. +The `amqp-client`, has auto recovery enabled by default. +There are some incompatibilities between the two recovery mechanisms so, by default, Spring sets the `automaticRecoveryEnabled` property on the underlying `RabbitMQ connectionFactory` to `false`. +Even if the property is `true`, Spring effectively disables it, by immediately closing any recovered connections. + +IMPORTANT: By default, only elements (queues, exchanges, bindings) that are defined as beans will be re-declared after a connection failure. +See xref:amqp/broker-configuration.adoc#declarable-recovery[Recovering Auto-Delete Declarations] for how to change that behavior. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/containerAttributes.adoc b/src/reference/antora/modules/ROOT/pages/amqp/containerAttributes.adoc new file mode 100644 index 00000000..5d63f226 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/containerAttributes.adoc @@ -0,0 +1,734 @@ +[[containerAttributes]] += Message Listener Container Configuration + +There are quite a few options for configuring a `SimpleMessageListenerContainer` (SMLC) and a `DirectMessageListenerContainer` (DMLC) related to transactions and quality of service, and some of them interact with each other. +Properties that apply to the SMLC, DMLC, or `StreamListenerContainer` (StLC) (see xref:stream.adoc[Using the RabbitMQ Stream Plugin]) are indicated by the check mark in the appropriate column. +See xref:amqp/receiving-messages/choose-container.adoc[Choosing a Container] for information to help you decide which container is appropriate for your application. + +The following table shows the container property names and their equivalent attribute names (in parentheses) when using the namespace to configure a ``. +The `type` attribute on that element can be `simple` (default) or `direct` to specify an `SMLC` or `DMLC` respectively. +Some properties are not exposed by the namespace. +These are indicated by `N/A` for the attribute. + +.Configuration options for a message listener container +[cols="8,16,1,1,1", options="header"] +|=== +|Property +(Attribute) +|Description +|SMLC +|DMLC +|StLC + +|[[ackTimeout]]<> + +(N/A) + +|When `messagesPerAck` is set, this timeout is used as an alternative to send an ack. +When a new message arrives, the count of unacked messages is compared to `messagesPerAck`, and the time since the last ack is compared to this value. +If either condition is `true`, the message is acknowledged. +When no new messages arrive and there are unacked messages, this timeout is approximate since the condition is only checked each `monitorInterval`. +See also `messagesPerAck` and `monitorInterval` in this table. + +a| +a|image::tickmark.png[] +a| + +|[[acknowledgeMode]]<> + +(acknowledge) + +a| +* `NONE`: No acks are sent (incompatible with `channelTransacted=true`). +RabbitMQ calls this "`autoack`", because the broker assumes all messages are acked without any action from the consumer. +* `MANUAL`: The listener must acknowledge all messages by calling `Channel.basicAck()`. +* `AUTO`: The container acknowledges the message automatically, unless the `MessageListener` throws an exception. +Note that `acknowledgeMode` is complementary to `channelTransacted` -- if the channel is transacted, the broker requires a commit notification in addition to the ack. +This is the default mode. +See also `batchSize`. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[adviceChain]]<> + +(advice-chain) + +|An array of AOP Advice to apply to the listener execution. +This can be used to apply additional cross-cutting concerns, such as automatic retry in the event of broker death. +Note that simple re-connection after an AMQP error is handled by the `CachingConnectionFactory`, as long as the broker is still alive. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[afterReceivePostProcessors]]<> + +(N/A) + +|An array of `MessagePostProcessor` instances that are invoked before invoking the listener. +Post processors can implement `PriorityOrdered` or `Ordered`. +The array is sorted with un-ordered members invoked last. +If a post processor returns `null`, the message is discarded (and acknowledged, if appropriate). + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[alwaysRequeueWithTxManagerRollback]]<> + +(N/A) + +|Set to `true` to always requeue messages on rollback when a transaction manager is configured. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[autoDeclare]]<> + +(auto-declare) + +a|When set to `true` (default), the container uses a `RabbitAdmin` to redeclare all AMQP objects (queues, exchanges, bindings), if it detects that at least one of its queues is missing during startup, perhaps because it is an `auto-delete` or an expired queue, but the redeclaration proceeds if the queue is missing for any reason. +To disable this behavior, set this property to `false`. +Note that the container fails to start if all of its queues are missing. + +NOTE: Prior to version 1.6, if there was more than one admin in the context, the container would randomly select one. +If there were no admins, it would create one internally. +In either case, this could cause unexpected results. +Starting with version 1.6, for `autoDeclare` to work, there must be exactly one `RabbitAdmin` in the context, or a reference to a specific instance must be configured on the container using the `rabbitAdmin` property. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[autoStartup]]<> + +(auto-startup) + +|Flag to indicate that the container should start when the `ApplicationContext` does (as part of the `SmartLifecycle` callbacks, which happen after all beans are initialized). +Defaults to `true`, but you can set it to `false` if your broker might not be available on startup and call `start()` later manually when you know the broker is ready. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a|image::tickmark.png[] + +|[[batchSize]]<> + +(transaction-size) +(batch-size) + +|When used with `acknowledgeMode` set to `AUTO`, the container tries to process up to this number of messages before sending an ack (waiting for each one up to the receive timeout setting). +This is also when a transactional channel is committed. +If the `prefetchCount` is less than the `batchSize`, it is increased to match the `batchSize`. + +a|image::tickmark.png[] +a| +a| + +|[[batchingStrategy]]<> + +(N/A) + +|The strategy used when debatchng messages. +Default `SimpleDebatchingStrategy`. +See xref:amqp/sending-messages.adoc#template-batching[Batching] and xref:amqp/receiving-messages/batch.adoc[@RabbitListener with Batching]. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[channelTransacted]]<> + +(channel-transacted) + +|Boolean flag to signal that all messages should be acknowledged in a transaction (either manually or automatically). + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[concurrency]]<> + +(N/A) + +|`m-n` The range of concurrent consumers for each listener (min, max). +If only `n` is provided, `n` is a fixed number of consumers. +See <>. + +a|image::tickmark.png[] +a| +a| + +|[[concurrentConsumers]]<> + +(concurrency) + +|The number of concurrent consumers to initially start for each listener. +See <>. +For the `StLC`, concurrency is controlled via an overloaded `superStream` method; see xref:stream.adoc#super-stream-consumer[Consuming Super Streams with Single Active Consumers]. + +a|image::tickmark.png[] +a| +a|image::tickmark.png[] + +|[[connectionFactory]]<> + +(connection-factory) + +|A reference to the `ConnectionFactory`. +When configuring by using the XML namespace, the default referenced bean name is `rabbitConnectionFactory`. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[consecutiveActiveTrigger]]<> + +(min-consecutive-active) + +|The minimum number of consecutive messages received by a consumer, without a receive timeout occurring, when considering starting a new consumer. +Also impacted by 'batchSize'. +See <>. +Default: 10. + +a|image::tickmark.png[] +a| +a| + +|[[consecutiveIdleTrigger]]<> + +(min-consecutive-idle) + +|The minimum number of receive timeouts a consumer must experience before considering stopping a consumer. +Also impacted by 'batchSize'. +See <>. +Default: 10. + +a|image::tickmark.png[] +a| +a| + +|[[consumerBatchEnabled]]<> + +(batch-enabled) + +|If the `MessageListener` supports it, setting this to true enables batching of discrete messages, up to `batchSize`; a partial batch will be delivered if no new messages arrive in `receiveTimeout`. +When this is false, batching is only supported for batches created by a producer; see xref:amqp/sending-messages.adoc#template-batching[Batching]. + +a|image::tickmark.png[] +a| +a| + +|[[consumerCustomizer]]<> + +(N/A) + +|A `ConsumerCustomizer` bean used to modify stream consumers created by the container. + +a| +a| +a|image::tickmark.png[] + +|[[consumerStartTimeout]]<> + +(N/A) + +|The time in milliseconds to wait for a consumer thread to start. +If this time elapses, an error log is written. +An example of when this might happen is if a configured `taskExecutor` has insufficient threads to support the container `concurrentConsumers`. + +See xref:amqp/receiving-messages/threading.adoc[Threading and Asynchronous Consumers]. +Default: 60000 (one minute). + +a|image::tickmark.png[] +a| +a| + +|[[consumerTagStrategy]]<> + +(consumer-tag-strategy) + +|Set an implementation of xref:amqp/receiving-messages/consumerTags.adoc[ConsumerTagStrategy], enabling the creation of a (unique) tag for each consumer. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[consumersPerQueue]]<> + +(consumers-per-queue) + +|The number of consumers to create for each configured queue. +See <>. + +a| +a|image::tickmark.png[] +a| + +|[[consumeDelay]]<> + +(N/A) + +|When using the https://github.com/rabbitmq/rabbitmq-sharding[RabbitMQ Sharding Plugin] with `concurrentConsumers > 1`, there is a race condition that can prevent even distribution of the consumers across the shards. +Use this property to add a small delay between consumer starts to avoid this race condition. +You should experiment with values to determine the suitable delay for your environment. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[debatchingEnabled]]<> + +(N/A) + +|When true, the listener container will debatch batched messages and invoke the listener with each message from the batch. +Starting with version 2.2.7, xref:amqp/sending-messages.adoc#template-batching[producer created batches] will be debatched as a `List` if the listener is a `BatchMessageListener` or `ChannelAwareBatchMessageListener`. +Otherwise messages from the batch are presented one-at-a-time. +Default true. +See xref:amqp/sending-messages.adoc#template-batching[Batching] and xref:amqp/receiving-messages/batch.adoc[@RabbitListener with Batching]. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[declarationRetries]]<> + +(declaration-retries) + +|The number of retry attempts when passive queue declaration fails. +Passive queue declaration occurs when the consumer starts or, when consuming from multiple queues, when not all queues were available during initialization. +When none of the configured queues can be passively declared (for any reason) after the retries are exhausted, the container behavior is controlled by the 'missingQueuesFatal` property, described earlier. +Default: Three retries (for a total of four attempts). + +a|image::tickmark.png[] +a| +a| + +|[[defaultRequeueRejected]]<> + +(requeue-rejected) + +|Determines whether messages that are rejected because the listener threw an exception should be requeued or not. +Default: `true`. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[errorHandler]]<> + +(error-handler) + +|A reference to an `ErrorHandler` strategy for handling any uncaught exceptions that may occur during the execution of the MessageListener. +Default: `ConditionalRejectingErrorHandler` + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[exclusive]]<> + +(exclusive) + +|Determines whether the single consumer in this container has exclusive access to the queues. +The concurrency of the container must be 1 when this is `true`. +If another consumer has exclusive access, the container tries to recover the consumer, according to the +`recovery-interval` or `recovery-back-off`. +When using the namespace, this attribute appears on the `` element along with the queue names. +Default: `false`. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[exclusiveConsumerExceptionLogger]]<> + +(N/A) + +|An exception logger used when an exclusive consumer cannot gain access to a queue. +By default, this is logged at the `WARN` level. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[failedDeclarationRetryInterval]]<> + +(failed-declaration +-retry-interval) + +|The interval between passive queue declaration retry attempts. +Passive queue declaration occurs when the consumer starts or, when consuming from multiple queues, when not all queues were available during initialization. +Default: 5000 (five seconds). + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[forceCloseChannel]]<> + +(N/A) + +|If the consumers do not respond to a shutdown within `shutdownTimeout`, if this is `true`, the channel will be closed, causing any unacked messages to be requeued. +Defaults to `true` since 2.0. +You can set it to `false` to revert to the previous behavior. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[forceStop]]<> + +(N/A) + +|Set to true to stop (when the container is stopped) after the current record is processed; causing all prefetched messages to be requeued. +By default, the container will cancel the consumer and process all prefetched messages before stopping. +Since versions 2.4.14, 3.0.6 +Defaults to `false`. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[globalQos]]<> + +(global-qos) + +|When true, the `prefetchCount` is applied globally to the channel rather than to each consumer on the channel. +See https://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.qos.global[`basicQos.global`] for more information. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|(group) + +|This is available only when using the namespace. +When specified, a bean of type `Collection` is registered with this name, and the +container for each `` element is added to the collection. +This allows, for example, starting and stopping the group of containers by iterating over the collection. +If multiple `` elements have the same group value, the containers in the collection form +an aggregate of all containers so designated. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[idleEventInterval]]<> + +(idle-event-interval) + +|See xref:amqp/receiving-messages/idle-containers.adoc[Detecting Idle Asynchronous Consumers]. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[javaLangErrorHandler]]<> + +(N/A) + +|An `AbstractMessageListenerContainer.JavaLangErrorHandler` implementation that is called when a container thread catches an `Error`. +The default implementation calls `System.exit(99)`; to revert to the previous behavior (do nothing), add a no-op handler. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[maxConcurrentConsumers]]<> + +(max-concurrency) + +|The maximum number of concurrent consumers to start, if needed, on demand. +Must be greater than or equal to 'concurrentConsumers'. +See <>. + +a|image::tickmark.png[] +a| +a| + +|[[messagesPerAck]]<> + +(N/A) + +|The number of messages to receive between acks. +Use this to reduce the number of acks sent to the broker (at the cost of increasing the possibility of redelivered messages). +Generally, you should set this property only on high-volume listener containers. +If this is set and a message is rejected (exception thrown), pending acks are acknowledged and the failed message is rejected. +Not allowed with transacted channels. +If the `prefetchCount` is less than the `messagesPerAck`, it is increased to match the `messagesPerAck`. +Default: ack every message. +See also `ackTimeout` in this table. + +a| +a|image::tickmark.png[] +a| + +|[[mismatchedQueuesFatal]]<> + +(mismatched-queues-fatal) + +a|When the container starts, if this property is `true` (default: `false`), the container checks that all queues declared in the context are compatible with queues already on the broker. +If mismatched properties (such as `auto-delete`) or arguments (skuch as `x-message-ttl`) exist, the container (and application context) fails to start with a fatal exception. + +If the problem is detected during recovery (for example, after a lost connection), the container is stopped. + +There must be a single `RabbitAdmin` in the application context (or one specifically configured on the container by using the `rabbitAdmin` property). +Otherwise, this property must be `false`. + +NOTE: If the broker is not available during initial startup, the container starts and the conditions are checked when the connection is established. + +IMPORTANT: The check is done against all queues in the context, not just the queues that a particular listener is configured to use. +If you wish to limit the checks to just those queues used by a container, you should configure a separate `RabbitAdmin` for the container, and provide a reference to it using the `rabbitAdmin` property. +See xref:amqp/broker-configuration.adoc#conditional-declaration[Conditional Declaration] for more information. + +IMPORTANT: Mismatched queue argument detection is disabled while starting a container for a `@RabbitListener` in a bean that is marked `@Lazy`. +This is to avoid a potential deadlock which can delay the start of such containers for up to 60 seconds. +Applications using lazy listener beans should check the queue arguments before getting a reference to the lazy bean. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[missingQueuesFatal]]<> + +(missing-queues-fatal) + +a|When set to `true` (default), if none of the configured queues are available on the broker, it is considered fatal. +This causes the application context to fail to initialize during startup. +Also, when the queues are deleted while the container is running, by default, the consumers make three retries to connect to the queues (at five second intervals) and stop the container if these attempts fail. + +This was not configurable in previous versions. + +When set to `false`, after making the three retries, the container goes into recovery mode, as with other problems, such as the broker being down. +The container tries to recover according to the `recoveryInterval` property. +During each recovery attempt, each consumer again tries four times to passively declare the queues at five second intervals. +This process continues indefinitely. + +You can also use a properties bean to set the property globally for all containers, as follows: + +[source,xml] +---- + + + false + + +---- + +This global property is not applied to any containers that have an explicit `missingQueuesFatal` property set. + +The default retry properties (three retries at five-second intervals) can be overridden by setting the properties below. + +IMPORTANT: Missing queue detection is disabled while starting a container for a `@RabbitListener` in a bean that is marked `@Lazy`. +This is to avoid a potential deadlock which can delay the start of such containers for up to 60 seconds. +Applications using lazy listener beans should check the queue(s) before getting a reference to the lazy bean. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[monitorInterval]]<> + +(monitor-interval) + +|With the DMLC, a task is scheduled to run at this interval to monitor the state of the consumers and recover any that have failed. + +a| +a|image::tickmark.png[] +a| + +|[[noLocal]]<> + +(N/A) + +|Set to `true` to disable delivery from the server to consumers messages published on the same channel's connection. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[phase]]<> + +(phase) + +|When `autoStartup` is `true`, the lifecycle phase within which this container should start and stop. +The lower the value, the earlier this container starts and the later it stops. +The default is `Integer.MAX_VALUE`, meaning the container starts as late as possible and stops as soon as possible. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[possibleAuthenticationFailureFatal]]<> + +(possible-authentication-failure-fatal) + +a|When set to `true` (default for SMLC), if a `PossibleAuthenticationFailureException` is thrown during connection, it is considered fatal. +This causes the application context to fail to initialize during startup (if the container is configured with auto startup). + +Since _version 2.0_. + +**DirectMessageListenerContainer** + +When set to `false` (default), each consumer will attempt to reconnect according to the `monitorInterval`. + +**SimpleMessageListenerContainer** + +When set to `false`, after making the 3 retries, the container will go into recovery mode, as with other problems, such as the broker being down. +The container will attempt to recover according to the `recoveryInterval` property. +During each recovery attempt, each consumer will again try 4 times to start. +This process will continue indefinitely. + +You can also use a properties bean to set the property globally for all containers, as follows: + +[source,xml] +---- + + + false + + +---- + +This global property will not be applied to any containers that have an explicit `missingQueuesFatal` property set. + +The default retry properties (3 retries at 5 second intervals) can be overridden using the properties after this one. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[prefetchCount]]<> + +(prefetch) + +a|The number of unacknowledged messages that can be outstanding at each consumer. +The higher this value is, the faster the messages can be delivered, but the higher the risk of non-sequential processing. +Ignored if the `acknowledgeMode` is `NONE`. +This is increased, if necessary, to match the `batchSize` or `messagePerAck`. +Defaults to 250 since 2.0. +You can set it to 1 to revert to the previous behavior. + +IMPORTANT: There are scenarios where the prefetch value should +be low -- for example, with large messages, especially if the processing is slow (messages could add up +to a large amount of memory in the client process), and if strict message ordering is necessary +(the prefetch value should be set back to 1 in this case). +Also, with low-volume messaging and multiple consumers (including concurrency within a single listener container instance), you may wish to reduce the prefetch to get a more even distribution of messages across consumers. + +Also see `globalQos`. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[rabbitAdmin]]<> + +(admin) + +|When a listener container listens to at least one auto-delete queue and it is found to be missing during startup, the container uses a `RabbitAdmin` to declare the queue and any related bindings and exchanges. +If such elements are configured to use conditional declaration (see xref:amqp/broker-configuration.adoc#conditional-declaration[Conditional Declaration]), the container must use the admin that was configured to declare those elements. +Specify that admin here. +It is required only when using auto-delete queues with conditional declaration. +If you do not wish the auto-delete queues to be declared until the container is started, set `auto-startup` to `false` on the admin. +Defaults to a `RabbitAdmin` that declares all non-conditional elements. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[receiveTimeout]]<> + +(receive-timeout) + +|The maximum time to wait for each message. +If `acknowledgeMode=NONE`, this has very little effect -- the container spins round and asks for another message. +It has the biggest effect for a transactional `Channel` with `batchSize > 1`, since it can cause messages already consumed not to be acknowledged until the timeout expires. +When `consumerBatchEnabled` is true, a partial batch will be delivered if this timeout occurs before a batch is complete. + +a|image::tickmark.png[] +a| +a| + +|[[recoveryBackOff]]<> + +(recovery-back-off) + +|Specifies the `BackOff` for intervals between attempts to start a consumer if it fails to start for non-fatal reasons. +Default is `FixedBackOff` with unlimited retries every five seconds. +Mutually exclusive with `recoveryInterval`. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[recoveryInterval]]<> + +(recovery-interval) + +|Determines the time in milliseconds between attempts to start a consumer if it fails to start for non-fatal reasons. +Default: 5000. +Mutually exclusive with `recoveryBackOff`. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[retryDeclarationInterval]]<> + +(missing-queue- +retry-interval) + +|If a subset of the configured queues are available during consumer initialization, the consumer starts consuming from those queues. +The consumer tries to passively declare the missing queues by using this interval. +When this interval elapses, the 'declarationRetries' and 'failedDeclarationRetryInterval' is used again. +If there are still missing queues, the consumer again waits for this interval before trying again. +This process continues indefinitely until all queues are available. +Default: 60000 (one minute). + +a|image::tickmark.png[] +a| +a| + +|[[shutdownTimeout]]<> + +(N/A) + +|When a container shuts down (for example, +if its enclosing `ApplicationContext` is closed), it waits for in-flight messages to be processed up to this limit. +Defaults to five seconds. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[startConsumerMinInterval]]<> + +(min-start-interval) + +|The time in milliseconds that must elapse before each new consumer is started on demand. +See <>. +Default: 10000 (10 seconds). + +a|image::tickmark.png[] +a| +a| + +|[[statefulRetryFatal]]<> + +WithNullMessageId +(N/A) + +|When using a stateful retry advice, if a message with a missing `messageId` property is received, it is considered +fatal for the consumer (it is stopped) by default. +Set this to `false` to discard (or route to a dead-letter queue) such messages. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[stopConsumerMinInterval]]<> + +(min-stop-interval) + +|The time in milliseconds that must elapse before a consumer is stopped since the last consumer was stopped when an idle consumer is detected. +See <>. +Default: 60000 (one minute). + +a|image::tickmark.png[] +a| +a| + +|[[streamConverter]]<> + +(N/A) + +|A `StreamMessageConverter` to convert a native Stream message to a Spring AMQP message. + +a| +a| +a|image::tickmark.png[] + +|[[taskExecutor]]<> + +(task-executor) + +|A reference to a Spring `TaskExecutor` (or standard JDK 1.5+ `Executor`) for executing listener invokers. +Default is a `SimpleAsyncTaskExecutor`, using internally managed threads. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| + +|[[taskScheduler]]<> + +(task-scheduler) + +|With the DMLC, the scheduler used to run the monitor task at the 'monitorInterval'. + +a| +a|image::tickmark.png[] +a| + +|[[transactionManager]]<> + +(transaction-manager) + +|External transaction manager for the operation of the listener. +Also complementary to `channelTransacted` -- if the `Channel` is transacted, its transaction is synchronized with the external transaction. + +a|image::tickmark.png[] +a|image::tickmark.png[] +a| +|=== + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/containers-and-broker-named-queues.adoc b/src/reference/antora/modules/ROOT/pages/amqp/containers-and-broker-named-queues.adoc new file mode 100644 index 00000000..4ca70426 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/containers-and-broker-named-queues.adoc @@ -0,0 +1,35 @@ +[[containers-and-broker-named-queues]] += Containers and Broker-Named queues + +While it is preferable to use `AnonymousQueue` instances as auto-delete queues, starting with version 2.1, you can use broker named queues with listener containers. +The following example shows how to do so: + +[source, java] +---- +@Bean +public Queue queue() { + return new Queue("", false, true, true); +} + +@Bean +public SimpleMessageListenerContainer container() { + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(cf()); + container.setQueues(queue()); + container.setMessageListener(m -> { + ... + }); + container.setMissingQueuesFatal(false); + return container; +} +---- + +Notice the empty `String` for the name. +When the `RabbitAdmin` declares queues, it updates the `Queue.actualName` property with the name returned by the broker. +You must use `setQueues()` when you configure the container for this to work, so that the container can access the declared name at runtime. +Just setting the names is insufficient. + +NOTE: You cannot add broker-named queues to the containers while they are running. + +IMPORTANT: When a connection is reset and a new one is established, the new queue gets a new name. +Since there is a race condition between the container restarting and the queue being re-declared, it is important to set the container's `missingQueuesFatal` property to `false`, since the container is likely to initially try to reconnect to the old queue. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/custom-client-props.adoc b/src/reference/antora/modules/ROOT/pages/amqp/custom-client-props.adoc new file mode 100644 index 00000000..e80e4974 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/custom-client-props.adoc @@ -0,0 +1,15 @@ +[[custom-client-props]] += Adding Custom Client Connection Properties +:page-section-summary-toc: 1 + +The `CachingConnectionFactory` now lets you access the underlying connection factory to allow, for example, +setting custom client properties. +The following example shows how to do so: + +[source, java] +---- +connectionFactory.getRabbitConnectionFactory().getClientProperties().put("thing1", "thing2"); +---- + +These properties appear in the RabbitMQ Admin UI when viewing the connection. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/debugging.adoc b/src/reference/antora/modules/ROOT/pages/amqp/debugging.adoc new file mode 100644 index 00000000..5ea5ccef --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/debugging.adoc @@ -0,0 +1,12 @@ +[[debugging]] += Debugging +:page-section-summary-toc: 1 + +Spring AMQP provides extensive logging, especially at the `DEBUG` level. + +If you wish to monitor the AMQP protocol between the application and broker, you can use a tool such as WireShark, which has a plugin to decode the protocol. +Alternatively, the RabbitMQ Java client comes with a very useful class called `Tracer`. +When run as a `main`, by default, it listens on port 5673 and connects to port 5672 on localhost. +You can run it and change your connection factory configuration to connect to port 5673 on localhost. +It displays the decoded protocol on the console. +Refer to the `Tracer` Javadoc for more information. diff --git a/src/reference/antora/modules/ROOT/pages/amqp/delayed-message-exchange.adoc b/src/reference/antora/modules/ROOT/pages/amqp/delayed-message-exchange.adoc new file mode 100644 index 00000000..5593e3b4 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/delayed-message-exchange.adoc @@ -0,0 +1,51 @@ +[[delayed-message-exchange]] += Delayed Message Exchange + +Version 1.6 introduces support for the +https://www.rabbitmq.com/blog/2015/04/16/scheduling-messages-with-rabbitmq/[Delayed Message Exchange Plugin] + +NOTE: The plugin is currently marked as experimental but has been available for over a year (at the time of writing). +If changes to the plugin make it necessary, we plan to add support for such changes as soon as practical. +For that reason, this support in Spring AMQP should be considered experimental, too. +This functionality was tested with RabbitMQ 3.6.0 and version 0.0.1 of the plugin. + +To use a `RabbitAdmin` to declare an exchange as delayed, you can set the `delayed` property on the exchange bean to +`true`. +The `RabbitAdmin` uses the exchange type (`Direct`, `Fanout`, and so on) to set the `x-delayed-type` argument and +declare the exchange with type `x-delayed-message`. + +The `delayed` property (default: `false`) is also available when configuring exchange beans using XML. +The following example shows how to use it: + +[source, xml] +---- + +---- + +To send a delayed message, you can set the `x-delay` header through `MessageProperties`, as the following examples show: + +[source, java] +---- +MessageProperties properties = new MessageProperties(); +properties.setDelay(15000); +template.send(exchange, routingKey, + MessageBuilder.withBody("foo".getBytes()).andProperties(properties).build()); +---- + +[source, java] +---- +rabbitTemplate.convertAndSend(exchange, routingKey, "foo", new MessagePostProcessor() { + + @Override + public Message postProcessMessage(Message message) throws AmqpException { + message.getMessageProperties().setDelay(15000); + return message; + } + +}); +---- + +To check if a message was delayed, use the `getReceivedDelay()` method on the `MessageProperties`. +It is a separate property to avoid unintended propagation to an output message generated from an input message. + + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/exception-handling.adoc b/src/reference/antora/modules/ROOT/pages/amqp/exception-handling.adoc new file mode 100644 index 00000000..9658b72b --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/exception-handling.adoc @@ -0,0 +1,46 @@ +[[exception-handling]] += Exception Handling + +Many operations with the RabbitMQ Java client can throw checked exceptions. +For example, there are a lot of cases where `IOException` instances may be thrown. +The `RabbitTemplate`, `SimpleMessageListenerContainer`, and other Spring AMQP components catch those exceptions and convert them into one of the exceptions within `AmqpException` hierarchy. +Those are defined in the 'org.springframework.amqp' package, and `AmqpException` is the base of the hierarchy. + +When a listener throws an exception, it is wrapped in a `ListenerExecutionFailedException`. +Normally the message is rejected and requeued by the broker. +Setting `defaultRequeueRejected` to `false` causes messages to be discarded (or routed to a dead letter exchange). +As discussed in xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc#async-listeners[Message Listeners and the Asynchronous Case], the listener can throw an `AmqpRejectAndDontRequeueException` (or `ImmediateRequeueAmqpException`) to conditionally control this behavior. + +However, there is a class of errors where the listener cannot control the behavior. +When a message that cannot be converted is encountered (for example, an invalid `content_encoding` header), some exceptions are thrown before the message reaches user code. +With `defaultRequeueRejected` set to `true` (default) (or throwing an `ImmediateRequeueAmqpException`), such messages would be redelivered over and over. +Before version 1.3.2, users needed to write a custom `ErrorHandler`, as discussed in xref:amqp/exception-handling.adoc[Exception Handling], to avoid this situation. + +Starting with version 1.3.2, the default `ErrorHandler` is now a `ConditionalRejectingErrorHandler` that rejects (and does not requeue) messages that fail with an irrecoverable error. +Specifically, it rejects messages that fail with the following errors: + +* `o.s.amqp...MessageConversionException`: Can be thrown when converting the incoming message payload using a `MessageConverter`. +* `o.s.messaging...MessageConversionException`: Can be thrown by the conversion service if additional conversion is required when mapping to a `@RabbitListener` method. +* `o.s.messaging...MethodArgumentNotValidException`: Can be thrown if validation (for example, `@Valid`) is used in the listener and the validation fails. +* `o.s.messaging...MethodArgumentTypeMismatchException`: Can be thrown if the inbound message was converted to a type that is not correct for the target method. +For example, the parameter is declared as `Message` but `Message` is received. +* `java.lang.NoSuchMethodException`: Added in version 1.6.3. +* `java.lang.ClassCastException`: Added in version 1.6.3. + +You can configure an instance of this error handler with a `FatalExceptionStrategy` so that users can provide their own rules for conditional message rejection -- for example, a delegate implementation to the `BinaryExceptionClassifier` from Spring Retry (xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc#async-listeners[Message Listeners and the Asynchronous Case]). +In addition, the `ListenerExecutionFailedException` now has a `failedMessage` property that you can use in the decision. +If the `FatalExceptionStrategy.isFatal()` method returns `true`, the error handler throws an `AmqpRejectAndDontRequeueException`. +The default `FatalExceptionStrategy` logs a warning message when an exception is determined to be fatal. + +Since version 1.6.3, a convenient way to add user exceptions to the fatal list is to subclass `ConditionalRejectingErrorHandler.DefaultExceptionStrategy` and override the `isUserCauseFatal(Throwable cause)` method to return `true` for fatal exceptions. + +A common pattern for handling DLQ messages is to set a `time-to-live` on those messages as well as additional DLQ configuration such that these messages expire and are routed back to the main queue for retry. +The problem with this technique is that messages that cause fatal exceptions loop forever. +Starting with version 2.1, the `ConditionalRejectingErrorHandler` detects an `x-death` header on a message that causes a fatal exception to be thrown. +The message is logged and discarded. +You can revert to the previous behavior by setting the `discardFatalsWithXDeath` property on the `ConditionalRejectingErrorHandler` to `false`. + +IMPORTANT: Starting with version 2.1.9, messages with these fatal exceptions are rejected and NOT requeued by default, even if the container acknowledge mode is MANUAL. +These exceptions generally occur before the listener is invoked so the listener does not have a chance to ack or nack the message so it remained in the queue in an un-acked state. +To revert to the previous behavior, set the `rejectManual` property on the `ConditionalRejectingErrorHandler` to `false`. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/exclusive-consumer.adoc b/src/reference/antora/modules/ROOT/pages/amqp/exclusive-consumer.adoc new file mode 100644 index 00000000..23c1a36a --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/exclusive-consumer.adoc @@ -0,0 +1,10 @@ +[[exclusive-consumer]] += Exclusive Consumer +:page-section-summary-toc: 1 + +Starting with version 1.3, you can configure the listener container with a single exclusive consumer. +This prevents other containers from consuming from the queues until the current consumer is cancelled. +The concurrency of such a container must be `1`. + +When using exclusive consumers, other containers try to consume from the queues according to the `recoveryInterval` property and log a `WARN` message if the attempt fails. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/listener-concurrency.adoc b/src/reference/antora/modules/ROOT/pages/amqp/listener-concurrency.adoc new file mode 100644 index 00000000..8a76771a --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/listener-concurrency.adoc @@ -0,0 +1,46 @@ +[[listener-concurrency]] += Listener Concurrency + +[[simplemessagelistenercontainer]] +== SimpleMessageListenerContainer + +By default, the listener container starts a single consumer that receives messages from the queues. + +When examining the table in the previous section, you can see a number of properties and attributes that control concurrency. +The simplest is `concurrentConsumers`, which creates that (fixed) number of consumers that concurrently process messages. + +Prior to version 1.3.0, this was the only setting available and the container had to be stopped and started again to change the setting. + +Since version 1.3.0, you can now dynamically adjust the `concurrentConsumers` property. +If it is changed while the container is running, consumers are added or removed as necessary to adjust to the new setting. + +In addition, a new property called `maxConcurrentConsumers` has been added and the container dynamically adjusts the concurrency based on workload. +This works in conjunction with four additional properties: `consecutiveActiveTrigger`, `startConsumerMinInterval`, `consecutiveIdleTrigger`, and `stopConsumerMinInterval`. +With the default settings, the algorithm to increase consumers works as follows: + +If the `maxConcurrentConsumers` has not been reached and an existing consumer is active for ten consecutive cycles AND at least 10 seconds has elapsed since the last consumer was started, a new consumer is started. +A consumer is considered active if it received at least one message in `batchSize` * `receiveTimeout` milliseconds. + +With the default settings, the algorithm to decrease consumers works as follows: + +If there are more than `concurrentConsumers` running and a consumer detects ten consecutive timeouts (idle) AND the last consumer was stopped at least 60 seconds ago, a consumer is stopped. +The timeout depends on the `receiveTimeout` and the `batchSize` properties. +A consumer is considered idle if it receives no messages in `batchSize` * `receiveTimeout` milliseconds. +So, with the default timeout (one second) and a `batchSize` of four, stopping a consumer is considered after 40 seconds of idle time (four timeouts correspond to one idle detection). + +NOTE: Practically, consumers can be stopped only if the whole container is idle for some time. +This is because the broker shares its work across all the active consumers. + +Each consumer uses a single channel, regardless of the number of configured queues. + +Starting with version 2.0, the `concurrentConsumers` and `maxConcurrentConsumers` properties can be set with the `concurrency` property -- for example, `2-4`. + +[[using-directmessagelistenercontainer]] +== Using `DirectMessageListenerContainer` + +With this container, concurrency is based on the configured queues and `consumersPerQueue`. +Each consumer for each queue uses a separate channel, and the concurrency is controlled by the rabbit client library. +By default, at the time of writing, it uses a pool of `DEFAULT_NUM_THREADS = Runtime.getRuntime().availableProcessors() * 2` threads. + +You can configure a `taskExecutor` to provide the required maximum concurrency. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/listener-queues.adoc b/src/reference/antora/modules/ROOT/pages/amqp/listener-queues.adoc new file mode 100644 index 00000000..1e52cef4 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/listener-queues.adoc @@ -0,0 +1,19 @@ +[[listener-queues]] += Listener Container Queues +:page-section-summary-toc: 1 + +Version 1.3 introduced a number of improvements for handling multiple queues in a listener container. + +Container can be initially configured to listen on zero queues. +Queues can be added and removed at runtime. +The `SimpleMessageListenerContainer` recycles (cancels and re-creates) all consumers when any pre-fetched messages have been processed. +The `DirectMessageListenerContainer` creates/cancels individual consumer(s) for each queue without affecting consumers on other queues. +See the https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/rabbit/listener/AbstractMessageListenerContainer.html[Javadoc] for the `addQueues`, `addQueueNames`, `removeQueues` and `removeQueueNames` methods. + +If not all queues are available, the container tries to passively declare (and consume from) the missing queues every 60 seconds. + +Also, if a consumer receives a cancel from the broker (for example, if a queue is deleted) the consumer tries to recover, and the recovered consumer continues to process messages from any other configured queues. +Previously, a cancel on one queue cancelled the entire consumer and, eventually, the container would stop due to the missing queue. + +If you wish to permanently remove a queue, you should update the container before or after deleting to queue, to avoid future attempts trying to consume from it. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/management-rest-api.adoc b/src/reference/antora/modules/ROOT/pages/amqp/management-rest-api.adoc new file mode 100644 index 00000000..445b46c8 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/management-rest-api.adoc @@ -0,0 +1,14 @@ +[[management-rest-api]] += RabbitMQ REST API +:page-section-summary-toc: 1 + +When the management plugin is enabled, the RabbitMQ server exposes a REST API to monitor and configure the broker. +A https://github.com/rabbitmq/hop[Java Binding for the API] is now provided. +The `com.rabbitmq.http.client.Client` is a standard, immediate, and, therefore, blocking API. +It is based on the https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#spring-web[Spring Web] module and its `RestTemplate` implementation. +On the other hand, the `com.rabbitmq.http.client.ReactorNettyClient` is a reactive, non-blocking implementation based on the https://projectreactor.io/docs/netty/release/reference/docs/index.html[Reactor Netty] project. + +The hop dependency (`com.rabbitmq:http-client`) is now also `optional`. + +See their Javadoc for more information. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/message-converters.adoc b/src/reference/antora/modules/ROOT/pages/amqp/message-converters.adoc new file mode 100644 index 00000000..b828342c --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/message-converters.adoc @@ -0,0 +1,513 @@ +[[message-converters]] += Message Converters + +The `AmqpTemplate` also defines several methods for sending and receiving messages that delegate to a `MessageConverter`. +The `MessageConverter` provides a single method for each direction: one for converting *to* a `Message` and another for converting *from* a `Message`. +Notice that, when converting to a `Message`, you can also provide properties in addition to the object. +The `object` parameter typically corresponds to the Message body. +The following listing shows the `MessageConverter` interface definition: + +[source,java] +---- +public interface MessageConverter { + + Message toMessage(Object object, MessageProperties messageProperties) + throws MessageConversionException; + + Object fromMessage(Message message) throws MessageConversionException; + +} +---- + +The relevant `Message`-sending methods on the `AmqpTemplate` are simpler than the methods we discussed previously, because they do not require the `Message` instance. +Instead, the `MessageConverter` is responsible for "`creating`" each `Message` by converting the provided object to the byte array for the `Message` body and then adding any provided `MessageProperties`. +The following listing shows the definitions of the various methods: + +[source,java] +---- +void convertAndSend(Object message) throws AmqpException; + +void convertAndSend(String routingKey, Object message) throws AmqpException; + +void convertAndSend(String exchange, String routingKey, Object message) + throws AmqpException; + +void convertAndSend(Object message, MessagePostProcessor messagePostProcessor) + throws AmqpException; + +void convertAndSend(String routingKey, Object message, + MessagePostProcessor messagePostProcessor) throws AmqpException; + +void convertAndSend(String exchange, String routingKey, Object message, + MessagePostProcessor messagePostProcessor) throws AmqpException; +---- + +On the receiving side, there are only two methods: one that accepts the queue name and one that relies on the template's "`queue`" property having been set. +The following listing shows the definitions of the two methods: + +[source,java] +---- +Object receiveAndConvert() throws AmqpException; + +Object receiveAndConvert(String queueName) throws AmqpException; +---- + +NOTE: The `MessageListenerAdapter` mentioned in xref:amqp/receiving-messages/async-consumer.adoc[Asynchronous Consumer] also uses a `MessageConverter`. + +[[simple-message-converter]] +== `SimpleMessageConverter` + +The default implementation of the `MessageConverter` strategy is called `SimpleMessageConverter`. +This is the converter that is used by an instance of `RabbitTemplate` if you do not explicitly configure an alternative. +It handles text-based content, serialized Java objects, and byte arrays. + +[[converting-from-a-message]] +=== Converting From a `Message` + +If the content type of the input `Message` begins with "text" (for example, +"text/plain"), it also checks for the content-encoding property to determine the charset to be used when converting the `Message` body byte array to a Java `String`. +If no content-encoding property had been set on the input `Message`, it uses the UTF-8 charset by default. +If you need to override that default setting, you can configure an instance of `SimpleMessageConverter`, set its `defaultCharset` property, and inject that into a `RabbitTemplate` instance. + +If the content-type property value of the input `Message` is set to "application/x-java-serialized-object", the `SimpleMessageConverter` tries to deserialize (rehydrate) the byte array into a Java object. +While that might be useful for simple prototyping, we do not recommend relying on Java serialization, since it leads to tight coupling between the producer and the consumer. +Of course, it also rules out usage of non-Java systems on either side. +With AMQP being a wire-level protocol, it would be unfortunate to lose much of that advantage with such restrictions. +In the next two sections, we explore some alternatives for passing rich domain object content without relying on Java serialization. + +For all other content-types, the `SimpleMessageConverter` returns the `Message` body content directly as a byte array. + +See <> for important information. + +[[converting-to-a-message]] +=== Converting To a `Message` + +When converting to a `Message` from an arbitrary Java Object, the `SimpleMessageConverter` likewise deals with byte arrays, strings, and serializable instances. +It converts each of these to bytes (in the case of byte arrays, there is nothing to convert), and it sets the content-type property accordingly. +If the `Object` to be converted does not match one of those types, the `Message` body is null. + +[[serializer-message-converter]] +== `SerializerMessageConverter` + +This converter is similar to the `SimpleMessageConverter` except that it can be configured with other Spring Framework +`Serializer` and `Deserializer` implementations for `application/x-java-serialized-object` conversions. + +See <> for important information. + +[[json-message-converter]] +== Jackson2JsonMessageConverter + +This section covers using the `Jackson2JsonMessageConverter` to convert to and from a `Message`. +It has the following sections: + +* xref:amqp/message-converters.adoc#Jackson2JsonMessageConverter-to-message[Converting to a `Message`] +* xref:amqp/message-converters.adoc#Jackson2JsonMessageConverter-from-message[Converting from a `Message`] + +[[Jackson2JsonMessageConverter-to-message]] +=== Converting to a `Message` + +As mentioned in the previous section, relying on Java serialization is generally not recommended. +One rather common alternative that is more flexible and portable across different languages and platforms is JSON +(JavaScript Object Notation). +The converter can be configured on any `RabbitTemplate` instance to override its usage of the `SimpleMessageConverter` +default. +The `Jackson2JsonMessageConverter` uses the `com.fasterxml.jackson` 2.x library. +The following example configures a `Jackson2JsonMessageConverter`: + +[source,xml] +---- + + + + + + + + + +---- + +As shown above, `Jackson2JsonMessageConverter` uses a `DefaultClassMapper` by default. +Type information is added to (and retrieved from) `MessageProperties`. +If an inbound message does not contain type information in `MessageProperties`, but you know the expected type, you +can configure a static type by using the `defaultType` property, as the following example shows: + +[source,xml] +---- + + + + + + + +---- + +In addition, you can provide custom mappings from the value in the `__TypeId__` header. +The following example shows how to do so: + +[source, java] +---- +@Bean +public Jackson2JsonMessageConverter jsonMessageConverter() { + Jackson2JsonMessageConverter jsonConverter = new Jackson2JsonMessageConverter(); + jsonConverter.setClassMapper(classMapper()); + return jsonConverter; +} + +@Bean +public DefaultClassMapper classMapper() { + DefaultClassMapper classMapper = new DefaultClassMapper(); + Map> idClassMapping = new HashMap<>(); + idClassMapping.put("thing1", Thing1.class); + idClassMapping.put("thing2", Thing2.class); + classMapper.setIdClassMapping(idClassMapping); + return classMapper; +} +---- + +Now, if the sending system sets the header to `thing1`, the converter creates a `Thing1` object, and so on. +See the xref:sample-apps.adoc#spring-rabbit-json[Receiving JSON from Non-Spring Applications] sample application for a complete discussion about converting messages from non-Spring applications. + +Starting with version 2.4.3, the converter will not add a `contentEncoding` message property if the `supportedMediaType` has a `charset` parameter; this is also used for the encoding. +A new method `setSupportedMediaType` has been added: + +[source, java] +---- +String utf16 = "application/json; charset=utf-16"; +converter.setSupportedContentType(MimeTypeUtils.parseMimeType(utf16)); +---- + +[[Jackson2JsonMessageConverter-from-message]] +=== Converting from a `Message` + +Inbound messages are converted to objects according to the type information added to headers by the sending system. + +Starting with version 2.4.3, if there is no `contentEncoding` message property, the converter will attempt to detect a `charset` parameter in the `contentType` message property and use that. +If neither exist, if the `supportedMediaType` has a `charset` parameter, it will be used for decoding, with a final fallback to the `defaultCharset` property. +A new method `setSupportedMediaType` has been added: + +[source, java] +---- +String utf16 = "application/json; charset=utf-16"; +converter.setSupportedContentType(MimeTypeUtils.parseMimeType(utf16)); +---- + +In versions prior to 1.6, if type information is not present, conversion would fail. +Starting with version 1.6, if type information is missing, the converter converts the JSON by using Jackson defaults (usually a map). + +Also, starting with version 1.6, when you use `@RabbitListener` annotations (on methods), the inferred type information is added to the `MessageProperties`. +This lets the converter convert to the argument type of the target method. +This only applies if there is one parameter with no annotations or a single parameter with the `@Payload` annotation. +Parameters of type `Message` are ignored during the analysis. + +IMPORTANT: By default, the inferred type information will override the inbound `__TypeId__` and related headers created +by the sending system. +This lets the receiving system automatically convert to a different domain object. +This applies only if the parameter type is concrete (not abstract or an interface) or it is from the `java.util` +package. +In all other cases, the `__TypeId__` and related headers is used. +There are cases where you might wish to override the default behavior and always use the `__TypeId__` information. +For example, suppose you have a `@RabbitListener` that takes a `Thing1` argument but the message contains a `Thing2` that +is a subclass of `Thing1` (which is concrete). +The inferred type would be incorrect. +To handle this situation, set the `TypePrecedence` property on the `Jackson2JsonMessageConverter` to `TYPE_ID` instead +of the default `INFERRED`. +(The property is actually on the converter's `DefaultJackson2JavaTypeMapper`, but a setter is provided on the converter +for convenience.) +If you inject a custom type mapper, you should set the property on the mapper instead. + +NOTE: When converting from the `Message`, an incoming `MessageProperties.getContentType()` must be JSON-compliant (`contentType.contains("json")` is used to check). +Starting with version 2.2, `application/json` is assumed if there is no `contentType` property, or it has the default value `application/octet-stream`. +To revert to the previous behavior (return an unconverted `byte[]`), set the converter's `assumeSupportedContentType` property to `false`. +If the content type is not supported, a `WARN` log message `Could not convert incoming message with content-type [...]`, is emitted and `message.getBody()` is returned as is -- as a `byte[]`. +So, to meet the `Jackson2JsonMessageConverter` requirements on the consumer side, the producer must add the `contentType` message property -- for example, as `application/json` or `text/x-json` or by using the `Jackson2JsonMessageConverter`, which sets the header automatically. +The following listing shows a number of converter calls: + +[source, java] +---- +@RabbitListener +public void thing1(Thing1 thing1) {...} + +@RabbitListener +public void thing1(@Payload Thing1 thing1, @Header("amqp_consumerQueue") String queue) {...} + +@RabbitListener +public void thing1(Thing1 thing1, o.s.amqp.core.Message message) {...} + +@RabbitListener +public void thing1(Thing1 thing1, o.s.messaging.Message message) {...} + +@RabbitListener +public void thing1(Thing1 thing1, String bar) {...} + +@RabbitListener +public void thing1(Thing1 thing1, o.s.messaging.Message message) {...} +---- + +In the first four cases in the preceding listing, the converter tries to convert to the `Thing1` type. +The fifth example is invalid because we cannot determine which argument should receive the message payload. +With the sixth example, the Jackson defaults apply due to the generic type being a `WildcardType`. + +You can, however, create a custom converter and use the `targetMethod` message property to decide which type to convert +the JSON to. + +NOTE: This type inference can only be achieved when the `@RabbitListener` annotation is declared at the method level. +With class-level `@RabbitListener`, the converted type is used to select which `@RabbitHandler` method to invoke. +For this reason, the infrastructure provides the `targetObject` message property, which you can use in a custom +converter to determine the type. + +IMPORTANT: Starting with version 1.6.11, `Jackson2JsonMessageConverter` and, therefore, `DefaultJackson2JavaTypeMapper` (`DefaultClassMapper`) provide the `trustedPackages` option to overcome https://pivotal.io/security/cve-2017-4995[Serialization Gadgets] vulnerability. +By default and for backward compatibility, the `Jackson2JsonMessageConverter` trusts all packages -- that is, it uses `*` for the option. + +Starting with version 2.4.7, the converter can be configured to return `Optional.empty()` if Jackson returns `null` after deserializing the message body. +This facilitates `@RabbitListener` s to receive null payloads, in two ways: + +[source, java] +---- +@RabbitListener(queues = "op.1") +void listen(@Payload(required = false) Thing payload) { + handleOptional(payload); // payload might be null +} + +@RabbitListener(queues = "op.2") +void listen(Optional optional) { + handleOptional(optional.orElse(this.emptyThing)); +} +---- + +To enable this feature, set `setNullAsOptionalEmpty` to `true`; when `false` (default), the converter falls back to the raw message body (`byte[]`). + +[source, java] +---- +@Bean +Jackson2JsonMessageConverter converter() { + Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(); + converter.setNullAsOptionalEmpty(true); + return converter; +} +---- + +[[jackson-abstract]] +=== Deserializing Abstract Classes + +Prior to version 2.2.8, if the inferred type of a `@RabbitListener` was an abstract class (including interfaces), the converter would fall back to looking for type information in the headers and, if present, used that information; if that was not present, it would try to create the abstract class. +This caused a problem when a custom `ObjectMapper` that is configured with a custom deserializer to handle the abstract class is used, but the incoming message has invalid type headers. + +Starting with version 2.2.8, the previous behavior is retained by default. If you have such a custom `ObjectMapper` and you want to ignore type headers, and always use the inferred type for conversion, set the `alwaysConvertToInferredType` to `true`. +This is needed for backwards compatibility and to avoid the overhead of an attempted conversion when it would fail (with a standard `ObjectMapper`). + +[[data-projection]] +=== Using Spring Data Projection Interfaces + +Starting with version 2.2, you can convert JSON to a Spring Data Projection interface instead of a concrete type. +This allows very selective, and low-coupled bindings to data, including the lookup of values from multiple places inside the JSON document. +For example the following interface can be defined as message payload type: + +[source, java] +---- +interface SomeSample { + + @JsonPath({ "$.username", "$.user.name" }) + String getUsername(); + +} +---- + +[source, java] +---- +@RabbitListener(queues = "projection") +public void projection(SomeSample in) { + String username = in.getUsername(); + ... +} +---- + +Accessor methods will be used to lookup the property name as field in the received JSON document by default. +The `@JsonPath` expression allows customization of the value lookup, and even to define multiple JSON path expressions, to lookup values from multiple places until an expression returns an actual value. + +To enable this feature, set the `useProjectionForInterfaces` to `true` on the message converter. +You must also add `spring-data:spring-data-commons` and `com.jayway.jsonpath:json-path` to the class path. + +When used as the parameter to a `@RabbitListener` method, the interface type is automatically passed to the converter as normal. + +[[json-complex]] +=== Converting From a `Message` With `RabbitTemplate` + +As mentioned earlier, type information is conveyed in message headers to assist the converter when converting from a message. +This works fine in most cases. +However, when using generic types, it can only convert simple objects and known "`container`" objects (lists, arrays, and maps). +Starting with version 2.0, the `Jackson2JsonMessageConverter` implements `SmartMessageConverter`, which lets it be used with the new `RabbitTemplate` methods that take a `ParameterizedTypeReference` argument. +This allows conversion of complex generic types, as shown in the following example: + +[source, java] +---- +Thing1> thing1 = + rabbitTemplate.receiveAndConvert(new ParameterizedTypeReference>>() { }); +---- + +NOTE: Starting with version 2.1, the `AbstractJsonMessageConverter` class has been removed. +It is no longer the base class for `Jackson2JsonMessageConverter`. +It has been replaced by `AbstractJackson2MessageConverter`. + +[[marshallingmessageconverter]] +== `MarshallingMessageConverter` + +Yet another option is the `MarshallingMessageConverter`. +It delegates to the Spring OXM library's implementations of the `Marshaller` and `Unmarshaller` strategy interfaces. +You can read more about that library https://docs.spring.io/spring/docs/current/spring-framework-reference/html/oxm.html[here]. +In terms of configuration, it is most common to provide only the constructor argument, since most implementations of `Marshaller` also implement `Unmarshaller`. +The following example shows how to configure a `MarshallingMessageConverter`: + +[source,xml] +---- + + + + + + + + +---- + +[[jackson2xml]] +== `Jackson2XmlMessageConverter` + +This class was introduced in version 2.1 and can be used to convert messages from and to XML. + +Both `Jackson2XmlMessageConverter` and `Jackson2JsonMessageConverter` have the same base class: `AbstractJackson2MessageConverter`. + +NOTE: The `AbstractJackson2MessageConverter` class is introduced to replace a removed class: `AbstractJsonMessageConverter`. + +The `Jackson2XmlMessageConverter` uses the `com.fasterxml.jackson` 2.x library. + +You can use it the same way as `Jackson2JsonMessageConverter`, except it supports XML instead of JSON. +The following example configures a `Jackson2JsonMessageConverter`: + +[source,xml] +---- + + + + + + + +---- +See <> for more information. + +NOTE: Starting with version 2.2, `application/xml` is assumed if there is no `contentType` property, or it has the default value `application/octet-stream`. +To revert to the previous behavior (return an unconverted `byte[]`), set the converter's `assumeSupportedContentType` property to `false`. + +[[contenttypedelegatingmessageconverter]] +== `ContentTypeDelegatingMessageConverter` + +This class was introduced in version 1.4.2 and allows delegation to a specific `MessageConverter` based on the content type property in the `MessageProperties`. +By default, it delegates to a `SimpleMessageConverter` if there is no `contentType` property or there is a value that matches none of the configured converters. +The following example configures a `ContentTypeDelegatingMessageConverter`: + +[source,xml] +---- + + + + + + + + +---- + +[[java-deserialization]] +== Java Deserialization + +This section covers how to deserialize Java objects. + +[IMPORTANT] +==== +There is a possible vulnerability when deserializing java objects from untrusted sources. + +If you accept messages from untrusted sources with a `content-type` of `application/x-java-serialized-object`, you should +consider configuring which packages and classes are allowed to be deserialized. +This applies to both the `SimpleMessageConverter` and `SerializerMessageConverter` when it is configured to use a +`DefaultDeserializer` either implicitly or via configuration. + +By default, the allowed list is empty, meaning no classes will be deserialized. + +You can set a list of patterns, such as `thing1.*`, `thing1.thing2.Cat` or `*.MySafeClass`. + +The patterns are checked in order until a match is found. +If there is no match, a `SecurityException` is thrown. + +You can set the patterns using the `allowedListPatterns` property on these converters. +Alternatively, if you trust all message originators, you can set the environment variable `SPRING_AMQP_DESERIALIZATION_TRUST_ALL` or system property `spring.amqp.deserialization.trust.all` to `true`. +==== + +[[message-properties-converters]] +== Message Properties Converters + +The `MessagePropertiesConverter` strategy interface is used to convert between the Rabbit Client `BasicProperties` and Spring AMQP `MessageProperties`. +The default implementation (`DefaultMessagePropertiesConverter`) is usually sufficient for most purposes, but you can implement your own if needed. +The default properties converter converts `BasicProperties` elements of type `LongString` to `String` instances when the size is not greater than `1024` bytes. +Larger `LongString` instances are not converted (see the next paragraph). +This limit can be overridden with a constructor argument. + +Starting with version 1.6, headers longer than the long string limit (default: 1024) are now left as +`LongString` instances by default by the `DefaultMessagePropertiesConverter`. +You can access the contents through the `getBytes[]`, `toString()`, or `getStream()` methods. + +Previously, the `DefaultMessagePropertiesConverter` "`converted`" such headers to a `DataInputStream` (actually it just referenced the `LongString` instance's `DataInputStream`). +On output, this header was not converted (except to a String -- for example, `java.io.DataInputStream@1d057a39` by calling `toString()` on the stream). + +Large incoming `LongString` headers are now correctly "`converted`" on output, too (by default). + +A new constructor is provided to let you configure the converter to work as before. +The following listing shows the Javadoc comment and declaration of the method: + +[source, java] +---- +/** + * Construct an instance where LongStrings will be returned + * unconverted or as a java.io.DataInputStream when longer than this limit. + * Use this constructor with 'true' to restore pre-1.6 behavior. + * @param longStringLimit the limit. + * @param convertLongLongStrings LongString when false, + * DataInputStream when true. + * @since 1.6 + */ +public DefaultMessagePropertiesConverter(int longStringLimit, boolean convertLongLongStrings) { ... } +---- + +Also starting with version 1.6, a new property called `correlationIdString` has been added to `MessageProperties`. +Previously, when converting to and from `BasicProperties` used by the RabbitMQ client, an unnecessary `byte[] <-> String` conversion was performed because `MessageProperties.correlationId` is a `byte[]`, but `BasicProperties` uses a `String`. +(Ultimately, the RabbitMQ client uses UTF-8 to convert the `String` to bytes to put in the protocol message). + +To provide maximum backwards compatibility, a new property called `correlationIdPolicy` has been added to the +`DefaultMessagePropertiesConverter`. +This takes a `DefaultMessagePropertiesConverter.CorrelationIdPolicy` enum argument. +By default it is set to `BYTES`, which replicates the previous behavior. + +For inbound messages: + +* `STRING`: Only the `correlationIdString` property is mapped +* `BYTES`: Only the `correlationId` property is mapped +* `BOTH`: Both properties are mapped + +For outbound messages: + +* `STRING`: Only the `correlationIdString` property is mapped +* `BYTES`: Only the `correlationId` property is mapped +* `BOTH`: Both properties are considered, with the `String` property taking precedence + +Also starting with version 1.6, the inbound `deliveryMode` property is no longer mapped to `MessageProperties.deliveryMode`. +It is mapped to `MessageProperties.receivedDeliveryMode` instead. +Also, the inbound `userId` property is no longer mapped to `MessageProperties.userId`. +It is mapped to `MessageProperties.receivedUserId` instead. +These changes are to avoid unexpected propagation of these properties if the same `MessageProperties` object is used for an outbound message. + +Starting with version 2.2, the `DefaultMessagePropertiesConverter` converts any custom headers with values of type `Class` using `getName()` instead of `toString()`; this avoids consuming application having to parse the class name out of the `toString()` representation. +For rolling upgrades, you may need to change your consumers to understand both formats until all producers are upgraded. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/multi-rabbit.adoc b/src/reference/antora/modules/ROOT/pages/amqp/multi-rabbit.adoc new file mode 100644 index 00000000..9d1a97d9 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/multi-rabbit.adoc @@ -0,0 +1,149 @@ +[[multi-rabbit]] += Multiple Broker (or Cluster) Support + +Version 2.3 added more convenience when communicating between a single application and multiple brokers or broker clusters. +The main benefit, on the consumer side, is that the infrastructure can automatically associate auto-declared queues with the appropriate broker. + +This is best illustrated with an example: + +[source, java] +---- +@SpringBootApplication(exclude = RabbitAutoConfiguration.class) +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Bean + CachingConnectionFactory cf1() { + return new CachingConnectionFactory("localhost"); + } + + @Bean + CachingConnectionFactory cf2() { + return new CachingConnectionFactory("otherHost"); + } + + @Bean + CachingConnectionFactory cf3() { + return new CachingConnectionFactory("thirdHost"); + } + + @Bean + SimpleRoutingConnectionFactory rcf(CachingConnectionFactory cf1, + CachingConnectionFactory cf2, CachingConnectionFactory cf3) { + + SimpleRoutingConnectionFactory rcf = new SimpleRoutingConnectionFactory(); + rcf.setDefaultTargetConnectionFactory(cf1); + rcf.setTargetConnectionFactories(Map.of("one", cf1, "two", cf2, "three", cf3)); + return rcf; + } + + @Bean("factory1-admin") + RabbitAdmin admin1(CachingConnectionFactory cf1) { + return new RabbitAdmin(cf1); + } + + @Bean("factory2-admin") + RabbitAdmin admin2(CachingConnectionFactory cf2) { + return new RabbitAdmin(cf2); + } + + @Bean("factory3-admin") + RabbitAdmin admin3(CachingConnectionFactory cf3) { + return new RabbitAdmin(cf3); + } + + @Bean + public RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry() { + return new RabbitListenerEndpointRegistry(); + } + + @Bean + public RabbitListenerAnnotationBeanPostProcessor postProcessor(RabbitListenerEndpointRegistry registry) { + MultiRabbitListenerAnnotationBeanPostProcessor postProcessor + = new MultiRabbitListenerAnnotationBeanPostProcessor(); + postProcessor.setEndpointRegistry(registry); + postProcessor.setContainerFactoryBeanName("defaultContainerFactory"); + return postProcessor; + } + + @Bean + public SimpleRabbitListenerContainerFactory factory1(CachingConnectionFactory cf1) { + SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); + factory.setConnectionFactory(cf1); + return factory; + } + + @Bean + public SimpleRabbitListenerContainerFactory factory2(CachingConnectionFactory cf2) { + SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); + factory.setConnectionFactory(cf2); + return factory; + } + + @Bean + public SimpleRabbitListenerContainerFactory factory3(CachingConnectionFactory cf3) { + SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); + factory.setConnectionFactory(cf3); + return factory; + } + + @Bean + RabbitTemplate template(SimpleRoutingConnectionFactory rcf) { + return new RabbitTemplate(rcf); + } + + @Bean + ConnectionFactoryContextWrapper wrapper(SimpleRoutingConnectionFactory rcf) { + return new ConnectionFactoryContextWrapper(rcf); + } + +} + +@Component +class Listeners { + + @RabbitListener(queuesToDeclare = @Queue("q1"), containerFactory = "factory1") + public void listen1(String in) { + + } + + @RabbitListener(queuesToDeclare = @Queue("q2"), containerFactory = "factory2") + public void listen2(String in) { + + } + + @RabbitListener(queuesToDeclare = @Queue("q3"), containerFactory = "factory3") + public void listen3(String in) { + + } + +} +---- + +As you can see, we have declared 3 sets of infrastructure (connection factories, admins, container factories). +As discussed earlier, `@RabbitListener` can define which container factory to use; in this case, they also use `queuesToDeclare` which causes the queue(s) to be declared on the broker, if it doesn't exist. +By naming the `RabbitAdmin` beans with the convention `-admin`, the infrastructure is able to determine which admin should declare the queue. +This will also work with `bindings = @QueueBinding(...)` whereby the exchange and binding will also be declared. +It will NOT work with `queues`, since that expects the queue(s) to already exist. + +On the producer side, a convenient `ConnectionFactoryContextWrapper` class is provided, to make using the `RoutingConnectionFactory` (see <>) simpler. + +As you can see above, a `SimpleRoutingConnectionFactory` bean has been added with routing keys `one`, `two` and `three`. +There is also a `RabbitTemplate` that uses that factory. +Here is an example of using that template with the wrapper to route to one of the broker clusters. + +[source, java] +---- +@Bean +public ApplicationRunner runner(RabbitTemplate template, ConnectionFactoryContextWrapper wrapper) { + return args -> { + wrapper.run("one", () -> template.convertAndSend("q1", "toCluster1")); + wrapper.run("two", () -> template.convertAndSend("q2", "toCluster2")); + wrapper.run("three", () -> template.convertAndSend("q3", "toCluster3")); + }; +} +---- + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/post-processing.adoc b/src/reference/antora/modules/ROOT/pages/amqp/post-processing.adoc new file mode 100644 index 00000000..7f01e783 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/post-processing.adoc @@ -0,0 +1,35 @@ +[[post-processing]] += Modifying Messages - Compression and More + +A number of extension points exist. +They let you perform some processing on a message, either before it is sent to RabbitMQ or immediately after it is received. + +As can be seen in xref:amqp/message-converters.adoc[Message Converters], one such extension point is in the `AmqpTemplate` `convertAndReceive` operations, where you can provide a `MessagePostProcessor`. +For example, after your POJO has been converted, the `MessagePostProcessor` lets you set custom headers or properties on the `Message`. + +Starting with version 1.4.2, additional extension points have been added to the `RabbitTemplate` - `setBeforePublishPostProcessors()` and `setAfterReceivePostProcessors()`. +The first enables a post processor to run immediately before sending to RabbitMQ. +When using batching (see xref:amqp/sending-messages.adoc#template-batching[Batching]), this is invoked after the batch is assembled and before the batch is sent. +The second is invoked immediately after a message is received. + +These extension points are used for such features as compression and, for this purpose, several `MessagePostProcessor` implementations are provided. +`GZipPostProcessor`, `ZipPostProcessor` and `DeflaterPostProcessor` compress messages before sending, and `GUnzipPostProcessor`, `UnzipPostProcessor` and `InflaterPostProcessor` decompress received messages. + +NOTE: Starting with version 2.1.5, the `GZipPostProcessor` can be configured with the `copyProperties = true` option to make a copy of the original message properties. +By default, these properties are reused for performance reasons, and modified with compression content encoding and the optional `MessageProperties.SPRING_AUTO_DECOMPRESS` header. +If you retain a reference to the original outbound message, its properties will change as well. +So, if your application retains a copy of an outbound message with these message post processors, consider turning the `copyProperties` option on. + +IMPORTANT: Starting with version 2.2.12, you can configure the delimiter that the compressing post processors use between content encoding elements. +With versions 2.2.11 and before, this was hard-coded as `:`, it is now set to `, ` by default. +The decompressors will work with both delimiters. +However, if you publish messages with 2.3 or later and consume with 2.2.11 or earlier, you MUST set the `encodingDelimiter` property on the compressor(s) to `:`. +When your consumers are upgraded to 2.2.11 or later, you can revert to the default of `, `. + +Similarly, the `SimpleMessageListenerContainer` also has a `setAfterReceivePostProcessors()` method, letting the decompression be performed after messages are received by the container. + +Starting with version 2.1.4, `addBeforePublishPostProcessors()` and `addAfterReceivePostProcessors()` have been added to the `RabbitTemplate` to allow appending new post processors to the list of before publish and after receive post processors respectively. +Also there are methods provided to remove the post processors. +Similarly, `AbstractMessageListenerContainer` also has `addAfterReceivePostProcessors()` and `removeAfterReceivePostProcessor()` methods added. +See the Javadoc of `RabbitTemplate` and `AbstractMessageListenerContainer` for more detail. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages.adoc new file mode 100644 index 00000000..271e10a6 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages.adoc @@ -0,0 +1,10 @@ +[[receiving-messages]] += Receiving Messages +:page-section-summary-toc: 1 + +Message reception is always a little more complicated than sending. +There are two ways to receive a `Message`. +The simpler option is to poll for one `Message` at a time with a polling method call. +The more complicated yet more common approach is to register a listener that receives `Messages` on-demand, asynchronously. +We consider an example of each approach in the next two sub-sections. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven.adoc new file mode 100644 index 00000000..35c6691d --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven.adoc @@ -0,0 +1,124 @@ +[[async-annotation-driven]] += Annotation-driven Listener Endpoints + +The easiest way to receive a message asynchronously is to use the annotated listener endpoint infrastructure. +In a nutshell, it lets you expose a method of a managed bean as a Rabbit listener endpoint. +The following example shows how to use the `@RabbitListener` annotation: + +[source,java] +---- + +@Component +public class MyService { + + @RabbitListener(queues = "myQueue") + public void processOrder(String data) { + ... + } + +} +---- + +The idea of the preceding example is that, whenever a message is available on the queue named `myQueue`, the `processOrder` method is invoked accordingly (in this case, with the payload of the message). + +The annotated endpoint infrastructure creates a message listener container behind the scenes for each annotated method, by using a `RabbitListenerContainerFactory`. + +In the preceding example, `myQueue` must already exist and be bound to some exchange. +The queue can be declared and bound automatically, as long as a `RabbitAdmin` exists in the application context. + +NOTE: Property placeholders (`${some.property}`) or SpEL expressions (`+#{someExpression}+`) can be specified for the annotation properties (`queues` etc). +See xref:amqp/receiving-messages/async-annotation-driven/multiple-queues.adoc[Listening to Multiple Queues] for an example of why you might use SpEL instead of a property placeholder. +The following listing shows three examples of how to declare a Rabbit listener: + +[source,java] +---- + +@Component +public class MyService { + + @RabbitListener(bindings = @QueueBinding( + value = @Queue(value = "myQueue", durable = "true"), + exchange = @Exchange(value = "auto.exch", ignoreDeclarationExceptions = "true"), + key = "orderRoutingKey") + ) + public void processOrder(Order order) { + ... + } + + @RabbitListener(bindings = @QueueBinding( + value = @Queue, + exchange = @Exchange(value = "auto.exch"), + key = "invoiceRoutingKey") + ) + public void processInvoice(Invoice invoice) { + ... + } + + @RabbitListener(queuesToDeclare = @Queue(name = "${my.queue}", durable = "true")) + public String handleWithSimpleDeclare(String data) { + ... + } + +} +---- + +In the first example, a queue `myQueue` is declared automatically (durable) together with the exchange, if needed, +and bound to the exchange with the routing key. +In the second example, an anonymous (exclusive, auto-delete) queue is declared and bound; the queue name is created by the framework using the `Base64UrlNamingStrategy`. +You cannot declare broker-named queues using this technique; they need to be declared as bean definitions; see xref:amqp/containers-and-broker-named-queues.adoc[Containers and Broker-Named queues]. +Multiple `QueueBinding` entries can be provided, letting the listener listen to multiple queues. +In the third example, a queue with the name retrieved from property `my.queue` is declared, if necessary, with the default binding to the default exchange using the queue name as the routing key. + +Since version 2.0, the `@Exchange` annotation supports any exchange types, including custom. +For more information, see https://www.rabbitmq.com/tutorials/amqp-concepts.html[AMQP Concepts]. + +You can use normal `@Bean` definitions when you need more advanced configuration. + +Notice `ignoreDeclarationExceptions` on the exchange in the first example. +This allows, for example, binding to an existing exchange that might have different settings (such as `internal`). +By default, the properties of an existing exchange must match. + +Starting with version 2.0, you can now bind a queue to an exchange with multiple routing keys, as the following example shows: + +[source, java] +---- +... + key = { "red", "yellow" } +... +---- + +You can also specify arguments within `@QueueBinding` annotations for queues, exchanges, +and bindings, as the following example shows: + +[source, java] +---- +@RabbitListener(bindings = @QueueBinding( + value = @Queue(value = "auto.headers", autoDelete = "true", + arguments = @Argument(name = "x-message-ttl", value = "10000", + type = "java.lang.Integer")), + exchange = @Exchange(value = "auto.headers", type = ExchangeTypes.HEADERS, autoDelete = "true"), + arguments = { + @Argument(name = "x-match", value = "all"), + @Argument(name = "thing1", value = "somevalue"), + @Argument(name = "thing2") + }) +) +public String handleWithHeadersExchange(String foo) { + ... +} +---- + +Notice that the `x-message-ttl` argument is set to 10 seconds for the queue. +Since the argument type is not `String`, we have to specify its type -- in this case, `Integer`. +As with all such declarations, if the queue already exists, the arguments must match those on the queue. +For the header exchange, we set the binding arguments to match messages that have the `thing1` header set to `somevalue`, and +the `thing2` header must be present with any value. +The `x-match` argument means both conditions must be satisfied. + +The argument name, value, and type can be property placeholders (`${...}`) or SpEL expressions (`#{...}`). +The `name` must resolve to a `String`. +The expression for `type` must resolve to a `Class` or the fully-qualified name of a class. +The `value` must resolve to something that can be converted by the `DefaultConversionService` to the type (such as the `x-message-ttl` in the preceding example). + +If a name resolves to `null` or an empty `String`, that `@Argument` is ignored. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/container-management.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/container-management.adoc new file mode 100644 index 00000000..2b4a69c8 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/container-management.adoc @@ -0,0 +1,23 @@ +[[container-management]] += Container Management +:page-section-summary-toc: 1 + +Containers created for annotations are not registered with the application context. +You can obtain a collection of all containers by invoking `getListenerContainers()` on the +`RabbitListenerEndpointRegistry` bean. +You can then iterate over this collection, for example, to stop or start all containers or invoke the `Lifecycle` methods +on the registry itself, which will invoke the operations on each container. + +You can also get a reference to an individual container by using its `id`, using `getListenerContainer(String id)` -- for +example, `registry.getListenerContainer("multi")` for the container created by the snippet above. + +Starting with version 1.5.2, you can obtain the `id` values of the registered containers with `getListenerContainerIds()`. + +Starting with version 1.5, you can now assign a `group` to the container on the `RabbitListener` endpoint. +This provides a mechanism to get a reference to a subset of containers. +Adding a `group` attribute causes a bean of type `Collection` to be registered with the context with the group name. + +By default, stopping a container will cancel the consumer and process all prefetched messages before stopping. +Starting with versions 2.4.14, 3.0.6, you can set the <> container property to true to stop immediately after the current message is processed, causing any prefetched messages to be requeued. +This is useful, for example, if exclusive or single-active consumers are being used. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/conversion.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/conversion.adoc new file mode 100644 index 00000000..8f2adbe6 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/conversion.adoc @@ -0,0 +1,101 @@ +[[async-annotation-conversion]] += Message Conversion for Annotated Methods + +There are two conversion steps in the pipeline before invoking the listener. +The first step uses a `MessageConverter` to convert the incoming Spring AMQP `Message` to a Spring-messaging `Message`. +When the target method is invoked, the message payload is converted, if necessary, to the method parameter type. + +The default `MessageConverter` for the first step is a Spring AMQP `SimpleMessageConverter` that handles conversion to +`String` and `java.io.Serializable` objects. +All others remain as a `byte[]`. +In the following discussion, we call this the "`message converter`". + +The default converter for the second step is a `GenericMessageConverter`, which delegates to a conversion service +(an instance of `DefaultFormattingConversionService`). +In the following discussion, we call this the "`method argument converter`". + +To change the message converter, you can add it as a property to the container factory bean. +The following example shows how to do so: + +[source, java] +---- +@Bean +public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() { + SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); + ... + factory.setMessageConverter(new Jackson2JsonMessageConverter()); + ... + return factory; +} +---- + +This configures a Jackson2 converter that expects header information to be present to guide the conversion. + +You can also use a `ContentTypeDelegatingMessageConverter`, which can handle conversion of different content types. + +Starting with version 2.3, you can override the factory converter by specifying a bean name in the `messageConverter` property. + +[source, java] +---- +@Bean +public Jackson2JsonMessageConverter jsonConverter() { + return new Jackson2JsonMessageConverter(); +} + +@RabbitListener(..., messageConverter = "jsonConverter") +public void listen(String in) { + ... +} +---- + +This avoids having to declare a different container factory just to change the converter. + +In most cases, it is not necessary to customize the method argument converter unless, for example, you want to use +a custom `ConversionService`. + +In versions prior to 1.6, the type information to convert the JSON had to be provided in message headers, or a +custom `ClassMapper` was required. +Starting with version 1.6, if there are no type information headers, the type can be inferred from the target +method arguments. + +NOTE: This type inference works only for `@RabbitListener` at the method level. + +See <> for more information. + +If you wish to customize the method argument converter, you can do so as follows: + +[source, java] +---- +@Configuration +@EnableRabbit +public class AppConfig implements RabbitListenerConfigurer { + + ... + + @Bean + public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() { + DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); + factory.setMessageConverter(new GenericMessageConverter(myConversionService())); + return factory; + } + + @Bean + public DefaultConversionService myConversionService() { + DefaultConversionService conv = new DefaultConversionService(); + conv.addConverter(mySpecialConverter()); + return conv; + } + + @Override + public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) { + registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory()); + } + + ... + +} +---- + +IMPORTANT: For multi-method listeners (see xref:amqp/receiving-messages/async-annotation-driven/method-selection.adoc[Multi-method Listeners]), the method selection is based on the payload of the message *after the message conversion*. +The method argument converter is called only after the method has been selected. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/custom-argument-resolver.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/custom-argument-resolver.adoc new file mode 100644 index 00000000..8ee48932 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/custom-argument-resolver.adoc @@ -0,0 +1,36 @@ +[[custom-argument-resolver]] += Adding a Custom `HandlerMethodArgumentResolver` to @RabbitListener + +Starting with version 2.3.7 you are able to add your own `HandlerMethodArgumentResolver` and resolve custom method parameters. +All you need is to implement `RabbitListenerConfigurer` and use method `setCustomMethodArgumentResolvers()` from class `RabbitListenerEndpointRegistrar`. + +[source, java] +---- +@Configuration +class CustomRabbitConfig implements RabbitListenerConfigurer { + + @Override + public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) { + registrar.setCustomMethodArgumentResolvers( + new HandlerMethodArgumentResolver() { + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return CustomMethodArgument.class.isAssignableFrom(parameter.getParameterType()); + } + + @Override + public Object resolveArgument(MethodParameter parameter, org.springframework.messaging.Message message) { + return new CustomMethodArgument( + (String) message.getPayload(), + message.getHeaders().get("customHeader", String.class) + ); + } + + } + ); + } + +} +---- + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/enable-signature.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/enable-signature.adoc new file mode 100644 index 00000000..ed5464b8 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/enable-signature.adoc @@ -0,0 +1,71 @@ +[[async-annotation-driven-enable-signature]] += Annotated Endpoint Method Signature + +So far, we have been injecting a simple `String` in our endpoint, but it can actually have a very flexible method signature. +The following example rewrites it to inject the `Order` with a custom header: + +[source,java] +---- +@Component +public class MyService { + + @RabbitListener(queues = "myQueue") + public void processOrder(Order order, @Header("order_type") String orderType) { + ... + } +} +---- + +The following list shows the arguments that are available to be matched with parameters in listener endpoints: + +* The raw `org.springframework.amqp.core.Message`. +* The `MessageProperties` from the raw `Message`. +* The `com.rabbitmq.client.Channel` on which the message was received. +* The `org.springframework.messaging.Message` converted from the incoming AMQP message. +* `@Header`-annotated method arguments to extract a specific header value, including standard AMQP headers. +* `@Headers`-annotated argument that must also be assignable to `java.util.Map` for getting access to all headers. +* The converted payload + +A non-annotated element that is not one of the supported types (that is, +`Message`, `MessageProperties`, `Message` and `Channel`) is matched with the payload. +You can make that explicit by annotating the parameter with `@Payload`. +You can also turn on validation by adding an extra `@Valid`. + +The ability to inject Spring’s message abstraction is particularly useful to benefit from all the information stored in the transport-specific message without relying on the transport-specific API. +The following example shows how to do so: + +[source,java] +---- + +@RabbitListener(queues = "myQueue") +public void processOrder(Message order) { ... +} + +---- + +Handling of method arguments is provided by `DefaultMessageHandlerMethodFactory`, which you can further customize to support additional method arguments. +The conversion and validation support can be customized there as well. + +For instance, if we want to make sure our `Order` is valid before processing it, we can annotate the payload with `@Valid` and configure the necessary validator, as follows: + +[source,java] +---- + +@Configuration +@EnableRabbit +public class AppConfig implements RabbitListenerConfigurer { + + @Override + public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) { + registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory()); + } + + @Bean + public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() { + DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); + factory.setValidator(myValidator()); + return factory; + } +} +---- + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/enable.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/enable.adoc new file mode 100644 index 00000000..98890bf8 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/enable.adoc @@ -0,0 +1,121 @@ +[[async-annotation-driven-enable]] += Enable Listener Endpoint Annotations + +To enable support for `@RabbitListener` annotations, you can add `@EnableRabbit` to one of your `@Configuration` classes. +The following example shows how to do so: + +[source,java] +---- +@Configuration +@EnableRabbit +public class AppConfig { + + @Bean + public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() { + SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); + factory.setConnectionFactory(connectionFactory()); + factory.setConcurrentConsumers(3); + factory.setMaxConcurrentConsumers(10); + factory.setContainerCustomizer(container -> /* customize the container */); + return factory; + } +} +---- + +Since version 2.0, a `DirectMessageListenerContainerFactory` is also available. +It creates `DirectMessageListenerContainer` instances. + +NOTE: For information to help you choose between `SimpleRabbitListenerContainerFactory` and `DirectRabbitListenerContainerFactory`, see xref:amqp/receiving-messages/choose-container.adoc[Choosing a Container]. + +Starting with version 2.2.2, you can provide a `ContainerCustomizer` implementation (as shown above). +This can be used to further configure the container after it has been created and configured; you can use this, for example, to set properties that are not exposed by the container factory. + +Version 2.4.8 provides the `CompositeContainerCustomizer` for situations where you wish to apply multiple customizers. + +By default, the infrastructure looks for a bean named `rabbitListenerContainerFactory` as the source for the factory to use to create message listener containers. +In this case, and ignoring the RabbitMQ infrastructure setup, the `processOrder` method can be invoked with a core poll size of three threads and a maximum pool size of ten threads. + +You can customize the listener container factory to use for each annotation, or you can configure an explicit default by implementing the `RabbitListenerConfigurer` interface. +The default is required only if at least one endpoint is registered without a specific container factory. +See the https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/rabbit/annotation/RabbitListenerConfigurer.html[Javadoc] for full details and examples. + +The container factories provide methods for adding `MessagePostProcessor` instances that are applied after receiving messages (before invoking the listener) and before sending replies. + +See xref:amqp/receiving-messages/async-annotation-driven/reply.adoc[Reply Management] for information about replies. + +Starting with version 2.0.6, you can add a `RetryTemplate` and `RecoveryCallback` to the listener container factory. +It is used when sending replies. +The `RecoveryCallback` is invoked when retries are exhausted. +You can use a `SendRetryContextAccessor` to get information from the context. +The following example shows how to do so: + +[source, java] +---- +factory.setRetryTemplate(retryTemplate); +factory.setReplyRecoveryCallback(ctx -> { + Message failed = SendRetryContextAccessor.getMessage(ctx); + Address replyTo = SendRetryContextAccessor.getAddress(ctx); + Throwable t = ctx.getLastThrowable(); + ... + return null; +}); +---- + +If you prefer XML configuration, you can use the `` element. +Any beans annotated with `@RabbitListener` are detected. + +For `SimpleRabbitListenerContainer` instances, you can use XML similar to the following: + +[source,xml] +---- + + + + + + + +---- + +For `DirectMessageListenerContainer` instances, you can use XML similar to the following: + +[source,xml] +---- + + + + + + +---- + + +[[listener-property-overrides]] +Starting with version 2.0, the `@RabbitListener` annotation has a `concurrency` property. +It supports SpEL expressions (`#{...}`) and property placeholders (`${...}`). +Its meaning and allowed values depend on the container type, as follows: + +* For the `DirectMessageListenerContainer`, the value must be a single integer value, which sets the `consumersPerQueue` property on the container. +* For the `SimpleRabbitListenerContainer`, the value can be a single integer value, which sets the `concurrentConsumers` property on the container, or it can have the form, `m-n`, where `m` is the `concurrentConsumers` property and `n` is the `maxConcurrentConsumers` property. + +In either case, this setting overrides the settings on the factory. +Previously you had to define different container factories if you had listeners that required different concurrency. + +The annotation also allows overriding the factory `autoStartup` and `taskExecutor` properties via the `autoStartup` and `executor` (since 2.2) annotation properties. +Using a different executor for each might help with identifying threads associated with each listener in logs and thread dumps. + +Version 2.2 also added the `ackMode` property, which allows you to override the container factory's `acknowledgeMode` property. + +[source, java] +---- +@RabbitListener(id = "manual.acks.1", queues = "manual.acks.1", ackMode = "MANUAL") +public void manual1(String in, Channel channel, + @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException { + + ... + channel.basicAck(tag, false); +} +---- + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/error-handling.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/error-handling.adoc new file mode 100644 index 00000000..9c26c69d --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/error-handling.adoc @@ -0,0 +1,55 @@ +[[annotation-error-handling]] += Handling Exceptions + +By default, if an annotated listener method throws an exception, it is thrown to the container and the message are requeued and redelivered, discarded, or routed to a dead letter exchange, depending on the container and broker configuration. +Nothing is returned to the sender. + +Starting with version 2.0, the `@RabbitListener` annotation has two new attributes: `errorHandler` and `returnExceptions`. + +These are not configured by default. + +You can use the `errorHandler` to provide the bean name of a `RabbitListenerErrorHandler` implementation. +This functional interface has one method, as follows: + +[source, java] +---- +@FunctionalInterface +public interface RabbitListenerErrorHandler { + + Object handleError(Message amqpMessage, org.springframework.messaging.Message message, + ListenerExecutionFailedException exception) throws Exception; + +} +---- + +As you can see, you have access to the raw message received from the container, the spring-messaging `Message` object produced by the message converter, and the exception that was thrown by the listener (wrapped in a `ListenerExecutionFailedException`). +The error handler can either return some result (which is sent as the reply) or throw the original or a new exception (which is thrown to the container or returned to the sender, depending on the `returnExceptions` setting). + +The `returnExceptions` attribute, when `true`, causes exceptions to be returned to the sender. +The exception is wrapped in a `RemoteInvocationResult` object. +On the sender side, there is an available `RemoteInvocationAwareMessageConverterAdapter`, which, if configured into the `RabbitTemplate`, re-throws the server-side exception, wrapped in an `AmqpRemoteException`. +The stack trace of the server exception is synthesized by merging the server and client stack traces. + +IMPORTANT: This mechanism generally works only with the default `SimpleMessageConverter`, which uses Java serialization. +Exceptions are generally not "`Jackson-friendly`" and cannot be serialized to JSON. +If you use JSON, consider using an `errorHandler` to return some other Jackson-friendly `Error` object when an exception is thrown. + +IMPORTANT: In version 2.1, this interface moved from package `o.s.amqp.rabbit.listener` to `o.s.amqp.rabbit.listener.api`. + +Starting with version 2.1.7, the `Channel` is available in a messaging message header; this allows you to ack or nack the failed messasge when using `AcknowledgeMode.MANUAL`: + +[source, java] +---- +public Object handleError(Message amqpMessage, org.springframework.messaging.Message message, + ListenerExecutionFailedException exception) { + ... + message.getHeaders().get(AmqpHeaders.CHANNEL, Channel.class) + .basicReject(message.getHeaders().get(AmqpHeaders.DELIVERY_TAG, Long.class), + true); + } +---- + +Starting with version 2.2.18, if a message conversion exception is thrown, the error handler will be called, with `null` in the `message` argument. +This allows the application to send some result to the caller, indicating that a badly-formed message was received. +Previously, such errors were thrown and handled by the container. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/meta.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/meta.adoc new file mode 100644 index 00000000..9ef2318b --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/meta.adoc @@ -0,0 +1,71 @@ +[[meta-annotation-driven]] += Meta-annotations + +Sometimes you may want to use the same configuration for multiple listeners. +To reduce the boilerplate configuration, you can use meta-annotations to create your own listener annotation. +The following example shows how to do so: + +[source, java] +---- +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@RabbitListener(bindings = @QueueBinding( + value = @Queue, + exchange = @Exchange(value = "metaFanout", type = ExchangeTypes.FANOUT))) +public @interface MyAnonFanoutListener { +} + +public class MetaListener { + + @MyAnonFanoutListener + public void handle1(String foo) { + ... + } + + @MyAnonFanoutListener + public void handle2(String foo) { + ... + } + +} +---- + +In the preceding example, each listener created by the `@MyAnonFanoutListener` annotation binds an anonymous, auto-delete +queue to the fanout exchange, `metaFanout`. +Starting with version 2.2.3, `@AliasFor` is supported to allow overriding properties on the meta-annotated annotation. +Also, user annotations can now be `@Repeatable`, allowing multiple containers to be created for a method. + +[source, java] +---- +@Component +static class MetaAnnotationTestBean { + + @MyListener("queue1") + @MyListener("queue2") + public void handleIt(String body) { + } + +} + + +@RabbitListener +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(MyListeners.class) +static @interface MyListener { + + @AliasFor(annotation = RabbitListener.class, attribute = "queues") + String[] value() default {}; + +} + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +static @interface MyListeners { + + MyListener[] value(); + +} +---- + + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/method-selection.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/method-selection.adoc new file mode 100644 index 00000000..0fba2986 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/method-selection.adoc @@ -0,0 +1,47 @@ +[[annotation-method-selection]] += Multi-method Listeners + +Starting with version 1.5.0, you can specify the `@RabbitListener` annotation at the class level. +Together with the new `@RabbitHandler` annotation, this lets a single listener invoke different methods, based on +the payload type of the incoming message. +This is best described using an example: + +[source, java] +---- +@RabbitListener(id="multi", queues = "someQueue") +@SendTo("my.reply.queue") +public class MultiListenerBean { + + @RabbitHandler + public String thing2(Thing2 thing2) { + ... + } + + @RabbitHandler + public String cat(Cat cat) { + ... + } + + @RabbitHandler + public String hat(@Header("amqp_receivedRoutingKey") String rk, @Payload Hat hat) { + ... + } + + @RabbitHandler(isDefault = true) + public String defaultMethod(Object object) { + ... + } + +} +---- + +In this case, the individual `@RabbitHandler` methods are invoked if the converted payload is a `Thing2`, a `Cat`, or a `Hat`. +You should understand that the system must be able to identify a unique method based on the payload type. +The type is checked for assignability to a single parameter that has no annotations or that is annotated with the `@Payload` annotation. +Notice that the same method signatures apply, as discussed in the method-level `@RabbitListener` (xref:amqp/receiving-messages/async-consumer.adoc#message-listener-adapter[described earlier]). + +Starting with version 2.0.3, a `@RabbitHandler` method can be designated as the default method, which is invoked if there is no match on other methods. +At most, one method can be so designated. + +IMPORTANT: `@RabbitHandler` is intended only for processing message payloads after conversion, if you wish to receive the unconverted raw `Message` object, you must use `@RabbitListener` on the method, not the class. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/multiple-queues.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/multiple-queues.adoc new file mode 100644 index 00000000..8ac68796 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/multiple-queues.adoc @@ -0,0 +1,40 @@ +[[annotation-multiple-queues]] += Listening to Multiple Queues + +When you use the `queues` attribute, you can specify that the associated container can listen to multiple queues. +You can use a `@Header` annotation to make the queue name from which a message was received available to the POJO +method. +The following example shows how to do so: + +[source, java] +---- +@Component +public class MyService { + + @RabbitListener(queues = { "queue1", "queue2" } ) + public void processOrder(String data, @Header(AmqpHeaders.CONSUMER_QUEUE) String queue) { + ... + } + +} +---- + +Starting with version 1.5, you can externalize the queue names by using property placeholders and SpEL. +The following example shows how to do so: + +[source, java] +---- +@Component +public class MyService { + + @RabbitListener(queues = "#{'${property.with.comma.delimited.queue.names}'.split(',')}" ) + public void processOrder(String data, @Header(AmqpHeaders.CONSUMER_QUEUE) String queue) { + ... + } + +} +---- + +Prior to version 1.5, only a single queue could be specified this way. +Each queue needed a separate property. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/proxy-rabbitlistener-and-generics.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/proxy-rabbitlistener-and-generics.adoc new file mode 100644 index 00000000..0f625de9 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/proxy-rabbitlistener-and-generics.adoc @@ -0,0 +1,46 @@ +[[proxy-rabbitlistener-and-generics]] += Proxy `@RabbitListener` and Generics + +If your service is intended to be proxied (for example, in the case of `@Transactional`), you should keep in mind some considerations when +the interface has generic parameters. +Consider the following example: + +[source, java] +---- +interface TxService

{ + + String handle(P payload, String header); + +} + +static class TxServiceImpl implements TxService { + + @Override + @RabbitListener(...) + public String handle(Thing thing, String rk) { + ... + } + +} +---- + +With a generic interface and a particular implementation, you are forced to switch to the CGLIB target class proxy because the actual implementation of the interface +`handle` method is a bridge method. +In the case of transaction management, the use of CGLIB is configured by using +an annotation option: `@EnableTransactionManagement(proxyTargetClass = true)`. +And in this case, all annotations have to be declared on the target method in the implementation, as the following example shows: + +[source, java] +---- +static class TxServiceImpl implements TxService { + + @Override + @Transactional + @RabbitListener(...) + public String handle(@Payload Foo foo, @Header("amqp_receivedRoutingKey") String rk) { + ... + } + +} +---- + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/rabbit-validation.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/rabbit-validation.adoc new file mode 100644 index 00000000..784c8c83 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/rabbit-validation.adoc @@ -0,0 +1,69 @@ +[[rabbit-validation]] += @RabbitListener @Payload Validation + +Starting with version 2.3.7, it is now easier to add a `Validator` to validate `@RabbitListener` and `@RabbitHandler` `@Payload` arguments. +Now, you can simply add the validator to the registrar itself. + +[source, java] +---- +@Configuration +@EnableRabbit +public class Config implements RabbitListenerConfigurer { + ... + @Override + public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) { + registrar.setValidator(new MyValidator()); + } +} +---- + +NOTE: When using Spring Boot with the validation starter, a `LocalValidatorFactoryBean` is auto-configured: + +[source, java] +---- +@Configuration +@EnableRabbit +public class Config implements RabbitListenerConfigurer { + @Autowired + private LocalValidatorFactoryBean validator; + ... + @Override + public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) { + registrar.setValidator(this.validator); + } +} +---- + +To validate: + +[source, java] +---- +public static class ValidatedClass { + @Max(10) + private int bar; + public int getBar() { + return this.bar; + } + public void setBar(int bar) { + this.bar = bar; + } +} +---- + +and + +[source, java] +---- +@RabbitListener(id="validated", queues = "queue1", errorHandler = "validationErrorHandler", + containerFactory = "jsonListenerContainerFactory") +public void validatedListener(@Payload @Valid ValidatedClass val) { + ... +} +@Bean +public RabbitListenerErrorHandler validationErrorHandler() { + return (m, e) -> { + ... + }; +} +---- + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/registration.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/registration.adoc new file mode 100644 index 00000000..d1458af8 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/registration.adoc @@ -0,0 +1,29 @@ +[[async-annotation-driven-registration]] += Programmatic Endpoint Registration + +`RabbitListenerEndpoint` provides a model of a Rabbit endpoint and is responsible for configuring the container for that model. +The infrastructure lets you configure endpoints programmatically in addition to the ones that are detected by the `RabbitListener` annotation. +The following example shows how to do so: + +[source,java] +---- +@Configuration +@EnableRabbit +public class AppConfig implements RabbitListenerConfigurer { + + @Override + public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) { + SimpleRabbitListenerEndpoint endpoint = new SimpleRabbitListenerEndpoint(); + endpoint.setQueueNames("anotherQueue"); + endpoint.setMessageListener(message -> { + // processing + }); + registrar.registerEndpoint(endpoint); + } +} +---- + +In the preceding example, we used `SimpleRabbitListenerEndpoint`, which provides the actual `MessageListener` to invoke, but you could just as well build your own endpoint variant to describe a custom invocation mechanism. + +It should be noted that you could just as well skip the use of `@RabbitListener` altogether and register your endpoints programmatically through `RabbitListenerConfigurer`. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/repeatable-rabbit-listener.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/repeatable-rabbit-listener.adoc new file mode 100644 index 00000000..cac86f71 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/repeatable-rabbit-listener.adoc @@ -0,0 +1,10 @@ +[[repeatable-rabbit-listener]] += `@Repeatable` `@RabbitListener` +:page-section-summary-toc: 1 + +Starting with version 1.6, the `@RabbitListener` annotation is marked with `@Repeatable`. +This means that the annotation can appear on the same annotated element (method or class) multiple times. +In this case, a separate listener container is created for each annotation, each of which invokes the same listener +`@Bean`. +Repeatable annotations can be used with Java 8 or above. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/reply-content-type.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/reply-content-type.adoc new file mode 100644 index 00000000..b8fc03bd --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/reply-content-type.adoc @@ -0,0 +1,51 @@ +[[reply-content-type]] += Reply ContentType + +If you are using a sophisticated message converter, such as the `ContentTypeDelegatingMessageConverter`, you can control the content type of the reply by setting the `replyContentType` property on the listener. +This allows the converter to select the appropriate delegate converter for the reply. + +[source, java] +---- +@RabbitListener(queues = "q1", messageConverter = "delegating", + replyContentType = "application/json") +public Thing2 listen(Thing1 in) { + ... +} +---- + +By default, for backwards compatibility, any content type property set by the converter will be overwritten by this value after conversion. +Converters such as the `SimpleMessageConverter` use the reply type rather than the content type to determine the conversion needed and sets the content type in the reply message appropriately. +This may not be the desired action and can be overridden by setting the `converterWinsContentType` property to `false`. +For example, if you return a `String` containing JSON, the `SimpleMessageConverter` will set the content type in the reply to `text/plain`. +The following configuration will ensure the content type is set properly, even if the `SimpleMessageConverter` is used. + +[source, java] +---- +@RabbitListener(queues = "q1", replyContentType = "application/json", + converterWinsContentType = "false") +public String listen(Thing in) { + ... + return someJsonString; +} +---- + +These properties (`replyContentType` and `converterWinsContentType`) do not apply when the return type is a Spring AMQP `Message` or a Spring Messaging `Message`. +In the first case, there is no conversion involved; simply set the `contentType` message property. +In the second case, the behavior is controlled using message headers: + +[source, java] +---- +@RabbitListener(queues = "q1", messageConverter = "delegating") +@SendTo("q2") +public Message listen(String in) { + ... + return MessageBuilder.withPayload(in.toUpperCase()) + .setHeader(MessageHeaders.CONTENT_TYPE, "application/xml") + .build(); +} +---- + +This content type will be passed in the `MessageProperties` to the converter. +By default, for backwards compatibility, any content type property set by the converter will be overwritten by this value after conversion. +If you wish to override that behavior, also set the `AmqpHeaders.CONTENT_TYPE_CONVERTER_WINS` to `true` and any value set by the converter will be retained. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/reply.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/reply.adoc new file mode 100644 index 00000000..6c9c5846 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/reply.adoc @@ -0,0 +1,158 @@ +[[async-annotation-driven-reply]] += Reply Management + +The existing support in `MessageListenerAdapter` already lets your method have a non-void return type. +When that is the case, the result of the invocation is encapsulated in a message sent to the address specified in the `ReplyToAddress` header of the original message, or to the default address configured on the listener. +You can set that default address by using the `@SendTo` annotation of the messaging abstraction. + +Assuming our `processOrder` method should now return an `OrderStatus`, we can write it as follows to automatically send a reply: + +[source,java] +---- +@RabbitListener(destination = "myQueue") +@SendTo("status") +public OrderStatus processOrder(Order order) { + // order processing + return status; +} +---- + +If you need to set additional headers in a transport-independent manner, you could return a `Message` instead, something like the following: + +[source,java] +---- + +@RabbitListener(destination = "myQueue") +@SendTo("status") +public Message processOrder(Order order) { + // order processing + return MessageBuilder + .withPayload(status) + .setHeader("code", 1234) + .build(); +} +---- + +Alternatively, you can use a `MessagePostProcessor` in the `beforeSendReplyMessagePostProcessors` container factory property to add more headers. +Starting with version 2.2.3, the called bean/method is made available in the reply message, which can be used in a message post processor to communicate the information back to the caller: + +[source, java] +---- +factory.setBeforeSendReplyPostProcessors(msg -> { + msg.getMessageProperties().setHeader("calledBean", + msg.getMessageProperties().getTargetBean().getClass().getSimpleName()); + msg.getMessageProperties().setHeader("calledMethod", + msg.getMessageProperties().getTargetMethod().getName()); + return m; +}); +---- + +Starting with version 2.2.5, you can configure a `ReplyPostProcessor` to modify the reply message before it is sent; it is called after the `correlationId` header has been set up to match the request. + +[source, java] +---- +@RabbitListener(queues = "test.header", group = "testGroup", replyPostProcessor = "echoCustomHeader") +public String capitalizeWithHeader(String in) { + return in.toUpperCase(); +} + +@Bean +public ReplyPostProcessor echoCustomHeader() { + return (req, resp) -> { + resp.getMessageProperties().setHeader("myHeader", req.getMessageProperties().getHeader("myHeader")); + return resp; + }; +} +---- + +Starting with version 3.0, you can configure the post processor on the container factory instead of on the annotation. + +[source, java] +---- +factory.setReplyPostProcessorProvider(id -> (req, resp) -> { + resp.getMessageProperties().setHeader("myHeader", req.getMessageProperties().getHeader("myHeader")); + return resp; +}); +---- + +The `id` parameter is the listener id. + +A setting on the annotation will supersede the factory setting. + +The `@SendTo` value is assumed as a reply `exchange` and `routingKey` pair that follows the `exchange/routingKey` pattern, +where one of those parts can be omitted. +The valid values are as follows: + +* `thing1/thing2`: The `replyTo` exchange and the `routingKey`. +`thing1/`: The `replyTo` exchange and the default (empty) `routingKey`. +`thing2` or `/thing2`: The `replyTo` `routingKey` and the default (empty) exchange. +`/` or empty: The `replyTo` default exchange and the default `routingKey`. + +Also, you can use `@SendTo` without a `value` attribute. +This case is equal to an empty `sendTo` pattern. +`@SendTo` is used only if the inbound message does not have a `replyToAddress` property. + +Starting with version 1.5, the `@SendTo` value can be a bean initialization SpEL Expression, as shown in the following example: + +[source, java] +---- +@RabbitListener(queues = "test.sendTo.spel") +@SendTo("#{spelReplyTo}") +public String capitalizeWithSendToSpel(String foo) { + return foo.toUpperCase(); +} +... +@Bean +public String spelReplyTo() { + return "test.sendTo.reply.spel"; +} +---- + +The expression must evaluate to a `String`, which can be a simple queue name (sent to the default exchange) or with +the form `exchange/routingKey` as discussed prior to the preceding example. + +NOTE: The `#{...}` expression is evaluated once, during initialization. + +For dynamic reply routing, the message sender should include a `reply_to` message property or use the alternate +runtime SpEL expression (described after the next example). + +Starting with version 1.6, the `@SendTo` can be a SpEL expression that is evaluated at runtime against the request +and reply, as the following example shows: + +[source, java] +---- +@RabbitListener(queues = "test.sendTo.spel") +@SendTo("!{'some.reply.queue.with.' + result.queueName}") +public Bar capitalizeWithSendToSpel(Foo foo) { + return processTheFooAndReturnABar(foo); +} +---- + +The runtime nature of the SpEL expression is indicated with `!{...}` delimiters. +The evaluation context `#root` object for the expression has three properties: + +* `request`: The `o.s.amqp.core.Message` request object. +* `source`: The `o.s.messaging.Message` after conversion. +* `result`: The method result. + +The context has a map property accessor, a standard type converter, and a bean resolver, which lets other beans in the +context be referenced (for example, `@someBeanName.determineReplyQ(request, result)`). + +In summary, `#{...}` is evaluated once during initialization, with the `#root` object being the application context. +Beans are referenced by their names. +`!{...}` is evaluated at runtime for each message, with the root object having the properties listed earlier. +Beans are referenced with their names, prefixed by `@`. + +Starting with version 2.1, simple property placeholders are also supported (for example, `${some.reply.to}`). +With earlier versions, the following can be used as a work around, as the following example shows: + +[source, java] +---- +@RabbitListener(queues = "foo") +@SendTo("#{environment['my.send.to']}") +public String listen(Message in) { + ... + return ... +} +---- + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-consumer.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-consumer.adoc new file mode 100644 index 00000000..5c11273f --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-consumer.adoc @@ -0,0 +1,275 @@ +[[async-consumer]] += Asynchronous Consumer + +IMPORTANT: Spring AMQP also supports annotated listener endpoints through the use of the `@RabbitListener` annotation and provides an open infrastructure to register endpoints programmatically. +This is by far the most convenient way to setup an asynchronous consumer. +See xref:amqp/receiving-messages/async-annotation-driven.adoc[Annotation-driven Listener Endpoints] for more details. + +[IMPORTANT] +==== +The prefetch default value used to be 1, which could lead to under-utilization of efficient consumers. +Starting with version 2.0, the default prefetch value is now 250, which should keep consumers busy in most common scenarios and +thus improve throughput. + +There are, nevertheless, scenarios where the prefetch value should be low: + +* For large messages, especially if the processing is slow (messages could add up to a large amount of memory in the client process) +* When strict message ordering is necessary (the prefetch value should be set back to 1 in this case) +* Other special cases + +Also, with low-volume messaging and multiple consumers (including concurrency within a single listener container instance), you may wish to reduce the prefetch to get a more even distribution of messages across consumers. + +See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration]. + +For more background about prefetch, see this post about https://www.rabbitmq.com/blog/2014/04/14/finding-bottlenecks-with-rabbitmq-3-3/[consumer utilization in RabbitMQ] +and this post about https://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/[queuing theory]. +==== + +[[message-listener]] +== Message Listener + +For asynchronous `Message` reception, a dedicated component (not the `AmqpTemplate`) is involved. +That component is a container for a `Message`-consuming callback. +We consider the container and its properties later in this section. +First, though, we should look at the callback, since that is where your application code is integrated with the messaging system. +There are a few options for the callback, starting with an implementation of the `MessageListener` interface, which the following listing shows: + +[source,java] +---- +public interface MessageListener { + void onMessage(Message message); +} +---- + +If your callback logic depends on the AMQP Channel instance for any reason, you may instead use the `ChannelAwareMessageListener`. +It looks similar but has an extra parameter. +The following listing shows the `ChannelAwareMessageListener` interface definition: + +[source,java] +---- +public interface ChannelAwareMessageListener { + void onMessage(Message message, Channel channel) throws Exception; +} +---- + +IMPORTANT: In version 2.1, this interface moved from package `o.s.amqp.rabbit.core` to `o.s.amqp.rabbit.listener.api`. + +[[message-listener-adapter]] +== `MessageListenerAdapter` + +If you prefer to maintain a stricter separation between your application logic and the messaging API, you can rely upon an adapter implementation that is provided by the framework. +This is often referred to as "`Message-driven POJO`" support. + +NOTE: Version 1.5 introduced a more flexible mechanism for POJO messaging, the `@RabbitListener` annotation. +See xref:amqp/receiving-messages/async-annotation-driven.adoc[Annotation-driven Listener Endpoints] for more information. + +When using the adapter, you need to provide only a reference to the instance that the adapter itself should invoke. +The following example shows how to do so: + +[source,java] +---- +MessageListenerAdapter listener = new MessageListenerAdapter(somePojo); +listener.setDefaultListenerMethod("myMethod"); +---- + +You can subclass the adapter and provide an implementation of `getListenerMethodName()` to dynamically select different methods based on the message. +This method has two parameters, `originalMessage` and `extractedMessage`, the latter being the result of any conversion. +By default, a `SimpleMessageConverter` is configured. +See xref:amqp/message-converters.adoc#simple-message-converter[`SimpleMessageConverter`] for more information and information about other converters available. + +Starting with version 1.4.2, the original message has `consumerQueue` and `consumerTag` properties, which can be used to determine the queue from which a message was received. + +Starting with version 1.5, you can configure a map of consumer queue or tag to method name, to dynamically select the method to call. +If no entry is in the map, we fall back to the default listener method. +The default listener method (if not set) is `handleMessage`. + +Starting with version 2.0, a convenient `FunctionalInterface` has been provided. +The following listing shows the definition of `FunctionalInterface`: + +[source, java] +---- +@FunctionalInterface +public interface ReplyingMessageListener { + + R handleMessage(T t); + +} +---- + +This interface facilitates convenient configuration of the adapter by using Java 8 lambdas, as the following example shows: + +[source, java] +---- +new MessageListenerAdapter((ReplyingMessageListener) data -> { + ... + return result; +})); +---- + +Starting with version 2.2, the `buildListenerArguments(Object)` has been deprecated and new `buildListenerArguments(Object, Channel, Message)` one has been introduced instead. +The new method helps listener to get `Channel` and `Message` arguments to do more, such as calling `channel.basicReject(long, boolean)` in manual acknowledge mode. +The following listing shows the most basic example: + +[source,java] +---- +public class ExtendedListenerAdapter extends MessageListenerAdapter { + + @Override + protected Object[] buildListenerArguments(Object extractedMessage, Channel channel, Message message) { + return new Object[]{extractedMessage, channel, message}; + } + +} +---- + +Now you could configure `ExtendedListenerAdapter` as same as `MessageListenerAdapter` if you need to receive "`channel`" and "`message`". +Parameters of listener should be set as `buildListenerArguments(Object, Channel, Message)` returned, as the following example of listener shows: + +[source,java] +---- +public void handleMessage(Object object, Channel channel, Message message) throws IOException { + ... +} +---- + +[[container]] +== Container + +Now that you have seen the various options for the `Message`-listening callback, we can turn our attention to the container. +Basically, the container handles the "`active`" responsibilities so that the listener callback can remain passive. +The container is an example of a "`lifecycle`" component. +It provides methods for starting and stopping. +When configuring the container, you essentially bridge the gap between an AMQP Queue and the `MessageListener` instance. +You must provide a reference to the `ConnectionFactory` and the queue names or Queue instances from which that listener should consume messages. + +Prior to version 2.0, there was one listener container, the `SimpleMessageListenerContainer`. +There is now a second container, the `DirectMessageListenerContainer`. +The differences between the containers and criteria you might apply when choosing which to use are described in xref:amqp/receiving-messages/choose-container.adoc[Choosing a Container]. + +The following listing shows the most basic example, which works by using the, `SimpleMessageListenerContainer`: + +[source,java] +---- +SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); +container.setConnectionFactory(rabbitConnectionFactory); +container.setQueueNames("some.queue"); +container.setMessageListener(new MessageListenerAdapter(somePojo)); +---- + +As an "`active`" component, it is most common to create the listener container with a bean definition so that it can run in the background. +The following example shows one way to do so with XML: + +[source,xml] +---- + + + +---- + +The following listing shows another way to do so with XML: + +[source,xml] +---- + + + +---- + +Both of the preceding examples create a `DirectMessageListenerContainer` (notice the `type` attribute -- it defaults to `simple`). + +Alternately, you may prefer to use Java configuration, which looks similar to the preceding code snippet: + +[source,java] +---- +@Configuration +public class ExampleAmqpConfiguration { + + @Bean + public SimpleMessageListenerContainer messageListenerContainer() { + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); + container.setConnectionFactory(rabbitConnectionFactory()); + container.setQueueName("some.queue"); + container.setMessageListener(exampleListener()); + return container; + } + + @Bean + public CachingConnectionFactory rabbitConnectionFactory() { + CachingConnectionFactory connectionFactory = + new CachingConnectionFactory("localhost"); + connectionFactory.setUsername("guest"); + connectionFactory.setPassword("guest"); + return connectionFactory; + } + + @Bean + public MessageListener exampleListener() { + return new MessageListener() { + public void onMessage(Message message) { + System.out.println("received: " + message); + } + }; + } +} +---- + +[[consumer-priority]] +== Consumer Priority + +Starting with RabbitMQ Version 3.2, the broker now supports consumer priority (see https://www.rabbitmq.com/blog/2013/12/16/using-consumer-priorities-with-rabbitmq/[Using Consumer Priorities with RabbitMQ]). +This is enabled by setting the `x-priority` argument on the consumer. +The `SimpleMessageListenerContainer` now supports setting consumer arguments, as the following example shows: + +[source,java] +---- + +container.setConsumerArguments(Collections. + singletonMap("x-priority", Integer.valueOf(10))); +---- + +For convenience, the namespace provides the `priority` attribute on the `listener` element, as the following example shows: + +[source,xml] +---- + + + +---- + +Starting with version 1.3, you can modify the queues on which the container listens at runtime. +See <>. + +[[lc-auto-delete]] +== `auto-delete` Queues + +When a container is configured to listen to `auto-delete` queues, the queue has an `x-expires` option, or the https://www.rabbitmq.com/ttl.html[Time-To-Live] policy is configured on the Broker, the queue is removed by the broker when the container is stopped (that is, when the last consumer is cancelled). +Before version 1.3, the container could not be restarted because the queue was missing. +The `RabbitAdmin` only automatically redeclares queues and so on when the connection is closed or when it opens, which does not happen when the container is stopped and started. + +Starting with version 1.3, the container uses a `RabbitAdmin` to redeclare any missing queues during startup. + +You can also use conditional declaration (see xref:amqp/broker-configuration.adoc#conditional-declaration[Conditional Declaration]) together with an `auto-startup="false"` admin to defer queue declaration until the container is started. +The following example shows how to do so: + +[source,xml] +---- + + + + + + + + + + + + + +---- + +In this case, the queue and exchange are declared by `containerAdmin`, which has `auto-startup="false"` so that the elements are not declared during context initialization. +Also, the container is not started for the same reason. +When the container is later started, it uses its reference to `containerAdmin` to declare the elements. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-returns.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-returns.adoc new file mode 100644 index 00000000..a48bcd2f --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-returns.adoc @@ -0,0 +1,24 @@ +[[async-returns]] += Asynchronous `@RabbitListener` Return Types +:page-section-summary-toc: 1 + +`@RabbitListener` (and `@RabbitHandler`) methods can be specified with asynchronous return types `CompletableFuture` and `Mono`, letting the reply be sent asynchronously. +`ListenableFuture` is no longer supported; it has been deprecated by Spring Framework. + +IMPORTANT: The listener container factory must be configured with `AcknowledgeMode.MANUAL` so that the consumer thread will not ack the message; instead, the asynchronous completion will ack or nack the message when the async operation completes. +When the async result is completed with an error, whether the message is requeued or not depends on the exception type thrown, the container configuration, and the container error handler. +By default, the message will be requeued, unless the container's `defaultRequeueRejected` property is set to `false` (it is `true` by default). +If the async result is completed with an `AmqpRejectAndDontRequeueException`, the message will not be requeued. +If the container's `defaultRequeueRejected` property is `false`, you can override that by setting the future's exception to a `ImmediateRequeueException` and the message will be requeued. +If some exception occurs within the listener method that prevents creation of the async result object, you MUST catch that exception and return an appropriate return object that will cause the message to be acknowledged or requeued. + +Starting with versions 2.2.21, 2.3.13, 2.4.1, the `AcknowledgeMode` will be automatically set the `MANUAL` when async return types are detected. +In addition, incoming messages with fatal exceptions will be negatively acknowledged individually, previously any prior unacknowledged message were also negatively acknowledged. + +Starting with version 3.0.5, the `@RabbitListener` (and `@RabbitHandler`) methods can be marked with Kotlin `suspend` and the whole handling process and reply producing (optional) happens on respective Kotlin coroutine. +All the mentioned rules about `AcknowledgeMode.MANUAL` are still apply. +The `org.jetbrains.kotlinx:kotlinx-coroutines-reactor` dependency must be present in classpath to allow `suspend` function invocations. + +Also starting with version 3.0.5, if a `RabbitListenerErrorHandler` is configured on a listener with an async return type (including Kotlin suspend functions), the error handler is invoked after a failure. +See xref:amqp/receiving-messages/async-annotation-driven/error-handling.adoc[Handling Exceptions] for more information about this error handler and its purpose. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/batch.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/batch.adoc new file mode 100644 index 00000000..80ad49c2 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/batch.adoc @@ -0,0 +1,90 @@ +[[receiving-batch]] += @RabbitListener with Batching + +When receiving a xref:amqp/sending-messages.adoc#template-batching[a batch] of messages, the de-batching is normally performed by the container and the listener is invoked with one message at at time. +Starting with version 2.2, you can configure the listener container factory and listener to receive the entire batch in one call, simply set the factory's `batchListener` property, and make the method payload parameter a `List` or `Collection`: + +[source, java] +---- +@Bean +public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() { + SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); + factory.setConnectionFactory(connectionFactory()); + factory.setBatchListener(true); + return factory; +} + +@RabbitListener(queues = "batch.1") +public void listen1(List in) { + ... +} + +// or + +@RabbitListener(queues = "batch.2") +public void listen2(List> in) { + ... +} +---- + +Setting the `batchListener` property to true automatically turns off the `deBatchingEnabled` container property in containers that the factory creates (unless `consumerBatchEnabled` is `true` - see below). Effectively, the debatching is moved from the container to the listener adapter and the adapter creates the list that is passed to the listener. + +A batch-enabled factory cannot be used with a xref:amqp/receiving-messages/async-annotation-driven/method-selection.adoc[multi-method listener]. + +Also starting with version 2.2. when receiving batched messages one-at-a-time, the last message contains a boolean header set to `true`. +This header can be obtained by adding the `@Header(AmqpHeaders.LAST_IN_BATCH)` boolean last` parameter to your listener method. +The header is mapped from `MessageProperties.isLastInBatch()`. +In addition, `AmqpHeaders.BATCH_SIZE` is populated with the size of the batch in every message fragment. + +In addition, a new property `consumerBatchEnabled` has been added to the `SimpleMessageListenerContainer`. +When this is true, the container will create a batch of messages, up to `batchSize`; a partial batch is delivered if `receiveTimeout` elapses with no new messages arriving. +If a producer-created batch is received, it is debatched and added to the consumer-side batch; therefore the actual number of messages delivered may exceed `batchSize`, which represents the number of messages received from the broker. +`deBatchingEnabled` must be true when `consumerBatchEnabled` is true; the container factory will enforce this requirement. + +[source, java] +---- +@Bean +public SimpleRabbitListenerContainerFactory consumerBatchContainerFactory() { + SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); + factory.setConnectionFactory(rabbitConnectionFactory()); + factory.setConsumerTagStrategy(consumerTagStrategy()); + factory.setBatchListener(true); // configures a BatchMessageListenerAdapter + factory.setBatchSize(2); + factory.setConsumerBatchEnabled(true); + return factory; +} +---- + +When using `consumerBatchEnabled` with `@RabbitListener`: + +[source, java] +---- +@RabbitListener(queues = "batch.1", containerFactory = "consumerBatchContainerFactory") +public void consumerBatch1(List amqpMessages) { + ... +} + +@RabbitListener(queues = "batch.2", containerFactory = "consumerBatchContainerFactory") +public void consumerBatch2(List> messages) { + ... +} + +@RabbitListener(queues = "batch.3", containerFactory = "consumerBatchContainerFactory") +public void consumerBatch3(List strings) { + ... +} +---- + +* the first is called with the raw, unconverted `org.springframework.amqp.core.Message` s received. +* the second is called with the `org.springframework.messaging.Message` s with converted payloads and mapped headers/properties. +* the third is called with the converted payloads, with no access to headers/properties. + +You can also add a `Channel` parameter, often used when using `MANUAL` ack mode. +This is not very useful with the third example because you don't have access to the `delivery_tag` property. + +Spring Boot provides a configuration property for `consumerBatchEnabled` and `batchSize`, but not for `batchListener`. +Starting with version 3.0, setting `consumerBatchEnabled` to `true` on the container factory also sets `batchListener` to `true`. +When `consumerBatchEnabled` is `true`, the listener **must** be a batch listener. + +Starting with version 3.0, listener methods can consume `Collection` or `List`. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/choose-container.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/choose-container.adoc new file mode 100644 index 00000000..78b3a6cc --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/choose-container.adoc @@ -0,0 +1,36 @@ +[[choose-container]] += Choosing a Container + +Version 2.0 introduced the `DirectMessageListenerContainer` (DMLC). +Previously, only the `SimpleMessageListenerContainer` (SMLC) was available. +The SMLC uses an internal queue and a dedicated thread for each consumer. +If a container is configured to listen to multiple queues, the same consumer thread is used to process all the queues. +Concurrency is controlled by `concurrentConsumers` and other properties. +As messages arrive from the RabbitMQ client, the client thread hands them off to the consumer thread through the queue. +This architecture was required because, in early versions of the RabbitMQ client, multiple concurrent deliveries were not possible. +Newer versions of the client have a revised threading model and can now support concurrency. +This has allowed the introduction of the DMLC where the listener is now invoked directly on the RabbitMQ Client thread. +Its architecture is, therefore, actually "`simpler`" than the SMLC. +However, there are some limitations with this approach, and certain features of the SMLC are not available with the DMLC. +Also, concurrency is controlled by `consumersPerQueue` (and the client library's thread pool). +The `concurrentConsumers` and associated properties are not available with this container. + +The following features are available with the SMLC but not the DMLC: + +* `batchSize`: With the SMLC, you can set this to control how many messages are delivered in a transaction or to reduce the number of acks, but it may cause the number of duplicate deliveries to increase after a failure. +(The DMLC does have `messagesPerAck`, which you can use to reduce the acks, the same as with `batchSize` and the SMLC, but it cannot be used with transactions -- each message is delivered and ack'd in a separate transaction). +* `consumerBatchEnabled`: enables batching of discrete messages in the consumer; see xref:amqp/containerAttributes.adoc[Message Listener Container Configuration] for more information. +* `maxConcurrentConsumers` and consumer scaling intervals or triggers -- there is no auto-scaling in the DMLC. +It does, however, let you programmatically change the `consumersPerQueue` property and the consumers are adjusted accordingly. + +However, the DMLC has the following benefits over the SMLC: + +* Adding and removing queues at runtime is more efficient. +With the SMLC, the entire consumer thread is restarted (all consumers canceled and re-created). +With the DMLC, unaffected consumers are not canceled. +* The context switch between the RabbitMQ Client thread and the consumer thread is avoided. +* Threads are shared across consumers rather than having a dedicated thread for each consumer in the SMLC. +However, see the IMPORTANT note about the connection factory configuration in xref:amqp/receiving-messages/threading.adoc[Threading and Asynchronous Consumers]. + +See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration] for information about which configuration properties apply to each container. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/consumer-events.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/consumer-events.adoc new file mode 100644 index 00000000..af5e756f --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/consumer-events.adoc @@ -0,0 +1,39 @@ +[[consumer-events]] += Consumer Events + +The containers publish application events whenever a listener +(consumer) experiences a failure of some kind. +The event `ListenerContainerConsumerFailedEvent` has the following properties: + +* `container`: The listener container where the consumer experienced the problem. +* `reason`: A textual reason for the failure. +* `fatal`: A boolean indicating whether the failure was fatal. +With non-fatal exceptions, the container tries to restart the consumer, according to the `recoveryInterval` or `recoveryBackoff` (for the `SimpleMessageListenerContainer`) or the `monitorInterval` (for the `DirectMessageListenerContainer`). +* `throwable`: The `Throwable` that was caught. + +These events can be consumed by implementing `ApplicationListener`. + +NOTE: System-wide events (such as connection failures) are published by all consumers when `concurrentConsumers` is greater than 1. + +If a consumer fails because one if its queues is being used exclusively, by default, as well as publishing the event, a `DEBUG` log is issued (since 3.1, previously WARN). +To change this logging behavior, provide a custom `ConditionalExceptionLogger` in the `AbstractMessageListenerContainer` instance's `exclusiveConsumerExceptionLogger` property. +In addition, the `SimpleMessageListenerContainer` consumer restart after such an exception is now logged at DEBUG level by default (previously INFO). +A new method `logRestart()` has been added to the `ConditionalExceptionLogger` to allow this to be changed. + +Also, the `AbstractMessageListenerContainer.DefaultExclusiveConsumerLogger` is now public, allowing it to be sub classed. + +See also <>. + +Fatal errors are always logged at the `ERROR` level. +This it not modifiable. + +Several other events are published at various stages of the container lifecycle: + +* `AsyncConsumerStartedEvent`: When the consumer is started. +* `AsyncConsumerRestartedEvent`: When the consumer is restarted after a failure - `SimpleMessageListenerContainer` only. +* `AsyncConsumerTerminatedEvent`: When a consumer is stopped normally. +* `AsyncConsumerStoppedEvent`: When the consumer is stopped - `SimpleMessageListenerContainer` only. +* `ConsumeOkEvent`: When a `consumeOk` is received from the broker, contains the queue name and `consumerTag` +* `ListenerContainerIdleEvent`: See xref:amqp/receiving-messages/idle-containers.adoc[Detecting Idle Asynchronous Consumers]. +* `MissingQueueEvent`: When a missing queue is detected. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/consumerTags.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/consumerTags.adoc new file mode 100644 index 00000000..80cfbeca --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/consumerTags.adoc @@ -0,0 +1,21 @@ +[[consumerTags]] += Consumer Tags +:page-section-summary-toc: 1 + +You can provide a strategy to generate consumer tags. +By default, the consumer tag is generated by the broker. +The following listing shows the `ConsumerTagStrategy` interface definition: + +[source,java] +---- +public interface ConsumerTagStrategy { + + String createConsumerTag(String queue); + +} +---- + +The queue is made available so that it can (optionally) be used in the tag. + +See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration]. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/de-batching.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/de-batching.adoc new file mode 100644 index 00000000..30562937 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/de-batching.adoc @@ -0,0 +1,16 @@ +[[de-batching]] += Batched Messages +:page-section-summary-toc: 1 + +Batched messages (created by a producer) are automatically de-batched by listener containers (using the `springBatchFormat` message header). +Rejecting any message from a batch causes the entire batch to be rejected. +See xref:amqp/sending-messages.adoc#template-batching[Batching] for more information about batching. + +Starting with version 2.2, the `SimpleMessageListenerContainer` can be use to create batches on the consumer side (where the producer sent discrete messages). + +Set the container property `consumerBatchEnabled` to enable this feature. +`deBatchingEnabled` must also be true so that the container is responsible for processing batches of both types. +Implement `BatchMessageListener` or `ChannelAwareBatchMessageListener` when `consumerBatchEnabled` is true. +Starting with version 2.2.7 both the `SimpleMessageListenerContainer` and `DirectMessageListenerContainer` can debatch xref:amqp/sending-messages.adoc#template-batching[producer created batches] as `List`. +See xref:amqp/receiving-messages/batch.adoc[@RabbitListener with Batching] for information about using this feature with `@RabbitListener`. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/idle-containers.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/idle-containers.adoc new file mode 100644 index 00000000..239fa97a --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/idle-containers.adoc @@ -0,0 +1,95 @@ +[[idle-containers]] += Detecting Idle Asynchronous Consumers + +While efficient, one problem with asynchronous consumers is detecting when they are idle -- users might want to take +some action if no messages arrive for some period of time. + +Starting with version 1.6, it is now possible to configure the listener container to publish a +`ListenerContainerIdleEvent` when some time passes with no message delivery. +While the container is idle, an event is published every `idleEventInterval` milliseconds. + +To configure this feature, set `idleEventInterval` on the container. +The following example shows how to do so in XML and in Java (for both a `SimpleMessageListenerContainer` and a `SimpleRabbitListenerContainerFactory`): + +[source, xml] +---- + + + +---- + +[source, java] +---- +@Bean +public SimpleMessageListenerContainer(ConnectionFactory connectionFactory) { + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory); + ... + container.setIdleEventInterval(60000L); + ... + return container; +} +---- + +[source, java] +---- +@Bean +public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() { + SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); + factory.setConnectionFactory(rabbitConnectionFactory()); + factory.setIdleEventInterval(60000L); + ... + return factory; +} +---- + +In each of these cases, an event is published once per minute while the container is idle. + +[[event-consumption]] +== Event Consumption + +You can capture idle events by implementing `ApplicationListener` -- either a general listener, or one narrowed to only +receive this specific event. +You can also use `@EventListener`, introduced in Spring Framework 4.2. + +The following example combines the `@RabbitListener` and `@EventListener` into a single class. +You need to understand that the application listener gets events for all containers, so you may need to +check the listener ID if you want to take specific action based on which container is idle. +You can also use the `@EventListener` `condition` for this purpose. + +The events have four properties: + +* `source`: The listener container instance +* `id`: The listener ID (or container bean name) +* `idleTime`: The time the container had been idle when the event was published +* `queueNames`: The names of the queue(s) that the container listens to + +The following example shows how to create listeners by using both the `@RabbitListener` and the `@EventListener` annotations: + +[source, Java] +---- +public class Listener { + + @RabbitListener(id="someId", queues="#{queue.name}") + public String listen(String foo) { + return foo.toUpperCase(); + } + + @EventListener(condition = "event.listenerId == 'someId'") + public void onApplicationEvent(ListenerContainerIdleEvent event) { + ... + } + +} +---- + +IMPORTANT: Event listeners see events for all containers. +Consequently, in the preceding example, we narrow the events received based on the listener ID. + +CAUTION: If you wish to use the idle event to stop the lister container, you should not call `container.stop()` on the thread that calls the listener. +Doing so always causes delays and unnecessary log messages. +Instead, you should hand off the event to a different thread that can then stop the container. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer-observation.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer-observation.adoc new file mode 100644 index 00000000..fadad7cc --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer-observation.adoc @@ -0,0 +1,19 @@ +[[micrometer-observation]] += Micrometer Observation +:page-section-summary-toc: 1 + +Using Micrometer for observation is now supported, since version 3.0, for the `RabbitTemplate` and listener containers. + +Set `observationEnabled` on each component to enable observation; this will disable xref:amqp/receiving-messages/micrometer.adoc[Micrometer Timers] because the timers will now be managed with each observation. +When using annotated listeners, set `observationEnabled` on the container factory. + +Refer to https://micrometer.io/docs/tracing[Micrometer Tracing] for more information. + +To add tags to timers/traces, configure a custom `RabbitTemplateObservationConvention` or `RabbitListenerObservationConvention` to the template or listener container, respectively. + +The default implementations add the `name` tag for template observations and `listener.id` tag for containers. + +You can either subclass `DefaultRabbitTemplateObservationConvention` or `DefaultRabbitListenerObservationConvention` or provide completely new implementations. + +See xref:appendix/micrometer.adoc[Micrometer Observation Documentation] for more details. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer.adoc new file mode 100644 index 00000000..3715ee69 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer.adoc @@ -0,0 +1,21 @@ +[[micrometer]] += Monitoring Listener Performance +:page-section-summary-toc: 1 + +Starting with version 2.2, the listener containers will automatically create and update Micrometer `Timer` s for the listener, if `Micrometer` is detected on the class path, and a single `MeterRegistry` is present in the application context (or exactly one is annotated `@Primary`, such as when using Spring Boot). +The timers can be disabled by setting the container property `micrometerEnabled` to `false`. + +Two timers are maintained - one for successful calls to the listener and one for failures. +With a simple `MessageListener`, there is a pair of timers for each configured queue. + +The timers are named `spring.rabbitmq.listener` and have the following tags: + +* `listenerId` : (listener id or container bean name) +* `queue` : (the queue name for a simple listener or list of configured queue names when `consumerBatchEnabled` is `true` - because a batch may contain messages from multiple queues) +* `result` : `success` or `failure` +* `exception` : `none` or `ListenerExecutionFailedException` + +You can add additional tags using the `micrometerTags` container property. + +Also see xref:stream.adoc#stream-micrometer-observation[Micrometer Observation]. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/polling-consumer.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/polling-consumer.adoc new file mode 100644 index 00000000..893ab1b1 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/polling-consumer.adoc @@ -0,0 +1,102 @@ +[[polling-consumer]] += Polling Consumer + +The `AmqpTemplate` itself can be used for polled `Message` reception. +By default, if no message is available, `null` is returned immediately. +There is no blocking. +Starting with version 1.5, you can set a `receiveTimeout`, in milliseconds, and the receive methods block for up to that long, waiting for a message. +A value less than zero means block indefinitely (or at least until the connection to the broker is lost). +Version 1.6 introduced variants of the `receive` methods that allows the timeout be passed in on each call. + +CAUTION: Since the receive operation creates a new `QueueingConsumer` for each message, this technique is not really appropriate for high-volume environments. +Consider using an asynchronous consumer or a `receiveTimeout` of zero for those use cases. + +Starting with version 2.4.8, when using a non-zero timeout, you can specify arguments passed into the `basicConsume` method used to associate the consumer with the channel. +For example: `template.addConsumerArg("x-priority", 10)`. + +There are four simple `receive` methods available. +As with the `Exchange` on the sending side, there is a method that requires that a default queue property has been set +directly on the template itself, and there is a method that accepts a queue parameter at runtime. +Version 1.6 introduced variants to accept `timeoutMillis` to override `receiveTimeout` on a per-request basis. +The following listing shows the definitions of the four methods: + +[source,java] +---- +Message receive() throws AmqpException; + +Message receive(String queueName) throws AmqpException; + +Message receive(long timeoutMillis) throws AmqpException; + +Message receive(String queueName, long timeoutMillis) throws AmqpException; +---- + +As in the case of sending messages, the `AmqpTemplate` has some convenience methods for receiving POJOs instead of `Message` instances, and implementations provide a way to customize the `MessageConverter` used to create the `Object` returned: +The following listing shows those methods: + +[source,java] +---- +Object receiveAndConvert() throws AmqpException; + +Object receiveAndConvert(String queueName) throws AmqpException; + +Object receiveAndConvert(long timeoutMillis) throws AmqpException; + +Object receiveAndConvert(String queueName, long timeoutMillis) throws AmqpException; +---- + +Starting with version 2.0, there are variants of these methods that take an additional `ParameterizedTypeReference` argument to convert complex types. +The template must be configured with a `SmartMessageConverter`. +See xref:amqp/message-converters.adoc#json-complex[Converting From a `Message` With `RabbitTemplate`] for more information. + +Similar to `sendAndReceive` methods, beginning with version 1.3, the `AmqpTemplate` has several convenience `receiveAndReply` methods for synchronously receiving, processing and replying to messages. +The following listing shows those method definitions: + +[source,java] +---- + boolean receiveAndReply(ReceiveAndReplyCallback callback) + throws AmqpException; + + boolean receiveAndReply(String queueName, ReceiveAndReplyCallback callback) + throws AmqpException; + + boolean receiveAndReply(ReceiveAndReplyCallback callback, + String replyExchange, String replyRoutingKey) throws AmqpException; + + boolean receiveAndReply(String queueName, ReceiveAndReplyCallback callback, + String replyExchange, String replyRoutingKey) throws AmqpException; + + boolean receiveAndReply(ReceiveAndReplyCallback callback, + ReplyToAddressCallback replyToAddressCallback) throws AmqpException; + + boolean receiveAndReply(String queueName, ReceiveAndReplyCallback callback, + ReplyToAddressCallback replyToAddressCallback) throws AmqpException; +---- + +The `AmqpTemplate` implementation takes care of the `receive` and `reply` phases. +In most cases, you should provide only an implementation of `ReceiveAndReplyCallback` to perform some business logic for the received message and build a reply object or message, if needed. +Note, a `ReceiveAndReplyCallback` may return `null`. +In this case, no reply is sent and `receiveAndReply` works like the `receive` method. +This lets the same queue be used for a mixture of messages, some of which may not need a reply. + +Automatic message (request and reply) conversion is applied only if the provided callback is not an instance of `ReceiveAndReplyMessageCallback`, which provides a raw message exchange contract. + +The `ReplyToAddressCallback` is useful for cases requiring custom logic to determine the `replyTo` address at runtime against the received message and reply from the `ReceiveAndReplyCallback`. +By default, `replyTo` information in the request message is used to route the reply. + +The following listing shows an example of POJO-based receive and reply: + +[source,java] +---- +boolean received = + this.template.receiveAndReply(ROUTE, new ReceiveAndReplyCallback() { + + public Invoice handle(Order order) { + return processOrder(order); + } + }); +if (received) { + log.info("We received an order!"); +} +---- + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/threading.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/threading.adoc new file mode 100644 index 00000000..0edb275b --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/threading.adoc @@ -0,0 +1,28 @@ +[[threading]] += Threading and Asynchronous Consumers + +A number of different threads are involved with asynchronous consumers. + +Threads from the `TaskExecutor` configured in the `SimpleMessageListenerContainer` are used to invoke the `MessageListener` when a new message is delivered by `RabbitMQ Client`. +If not configured, a `SimpleAsyncTaskExecutor` is used. +If you use a pooled executor, you need to ensure the pool size is sufficient to handle the configured concurrency. +With the `DirectMessageListenerContainer`, the `MessageListener` is invoked directly on a `RabbitMQ Client` thread. +In this case, the `taskExecutor` is used for the task that monitors the consumers. + +NOTE: When using the default `SimpleAsyncTaskExecutor`, for the threads the listener is invoked on, the listener container `beanName` is used in the `threadNamePrefix`. +This is useful for log analysis. +We generally recommend always including the thread name in the logging appender configuration. +When a `TaskExecutor` is specifically provided through the `taskExecutor` property on the container, it is used as is, without modification. +It is recommended that you use a similar technique to name the threads created by a custom `TaskExecutor` bean definition, to aid with thread identification in log messages. + +The `Executor` configured in the `CachingConnectionFactory` is passed into the `RabbitMQ Client` when creating the connection, and its threads are used to deliver new messages to the listener container. +If this is not configured, the client uses an internal thread pool executor with (at the time of writing) a pool size of `Runtime.getRuntime().availableProcessors() * 2` for each connection. + +If you have a large number of factories or are using `CacheMode.CONNECTION`, you may wish to consider using a shared `ThreadPoolTaskExecutor` with enough threads to satisfy your workload. + +IMPORTANT: With the `DirectMessageListenerContainer`, you need to ensure that the connection factory is configured with a task executor that has sufficient threads to support your desired concurrency across all listener containers that use that factory. +The default pool size (at the time of writing) is `Runtime.getRuntime().availableProcessors() * 2`. + +The `RabbitMQ client` uses a `ThreadFactory` to create threads for low-level I/O (socket) operations. +To modify this factory, you need to configure the underlying RabbitMQ `ConnectionFactory`, as discussed in <>. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/using-container-factories.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/using-container-factories.adoc new file mode 100644 index 00000000..c2a2f00d --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/using-container-factories.adoc @@ -0,0 +1,51 @@ +[[using-container-factories]] += Using Container Factories + +Listener container factories were introduced to support the `@RabbitListener` and registering containers with the `RabbitListenerEndpointRegistry`, as discussed in xref:amqp/receiving-messages/async-annotation-driven/registration.adoc[Programmatic Endpoint Registration]. + +Starting with version 2.1, they can be used to create any listener container -- even a container without a listener (such as for use in Spring Integration). +Of course, a listener must be added before the container is started. + +There are two ways to create such containers: + +* Use a SimpleRabbitListenerEndpoint +* Add the listener after creation + +The following example shows how to use a `SimpleRabbitListenerEndpoint` to create a listener container: + +[source, java] +---- +@Bean +public SimpleMessageListenerContainer factoryCreatedContainerSimpleListener( + SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory) { + SimpleRabbitListenerEndpoint endpoint = new SimpleRabbitListenerEndpoint(); + endpoint.setQueueNames("queue.1"); + endpoint.setMessageListener(message -> { + ... + }); + return rabbitListenerContainerFactory.createListenerContainer(endpoint); +} +---- + +The following example shows how to add the listener after creation: + +[source, java] +---- +@Bean +public SimpleMessageListenerContainer factoryCreatedContainerNoListener( + SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory) { + SimpleMessageListenerContainer container = rabbitListenerContainerFactory.createListenerContainer(); + container.setMessageListener(message -> { + ... + }); + container.setQueueNames("test.no.listener.yet"); + return container; +} +---- + +In either case, the listener can also be a `ChannelAwareMessageListener`, since it is now a sub-interface of `MessageListener`. + +These techniques are useful if you wish to create several containers with similar properties or use a pre-configured container factory such as the one provided by Spring Boot auto configuration or both. + +IMPORTANT: Containers created this way are normal `@Bean` instances and are not registered in the `RabbitListenerEndpointRegistry`. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/request-reply.adoc b/src/reference/antora/modules/ROOT/pages/amqp/request-reply.adoc new file mode 100644 index 00000000..dadca617 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/request-reply.adoc @@ -0,0 +1,301 @@ +[[request-reply]] += Request/Reply Messaging + +The `AmqpTemplate` also provides a variety of `sendAndReceive` methods that accept the same argument options that were described earlier for the one-way send operations (`exchange`, `routingKey`, and `Message`). +Those methods are quite useful for request-reply scenarios, since they handle the configuration of the necessary `reply-to` property before sending and can listen for the reply message on an exclusive queue that is created internally for that purpose. + +Similar request-reply methods are also available where the `MessageConverter` is applied to both the request and reply. +Those methods are named `convertSendAndReceive`. +See the https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/core/AmqpTemplate.html[Javadoc of `AmqpTemplate`] for more detail. + +Starting with version 1.5.0, each of the `sendAndReceive` method variants has an overloaded version that takes `CorrelationData`. +Together with a properly configured connection factory, this enables the receipt of publisher confirms for the send side of the operation. +See xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns] and the https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/rabbit/core/RabbitOperations.html[Javadoc for `RabbitOperations`] for more information. + +Starting with version 2.0, there are variants of these methods (`convertSendAndReceiveAsType`) that take an additional `ParameterizedTypeReference` argument to convert complex returned types. +The template must be configured with a `SmartMessageConverter`. +See xref:amqp/message-converters.adoc#json-complex[Converting From a `Message` With `RabbitTemplate`] for more information. + +Starting with version 2.1, you can configure the `RabbitTemplate` with the `noLocalReplyConsumer` option to control a `noLocal` flag for reply consumers. +This is `false` by default. + +[[reply-timeout]] +== Reply Timeout + +By default, the send and receive methods timeout after five seconds and return null. +You can modify this behavior by setting the `replyTimeout` property. +Starting with version 1.5, if you set the `mandatory` property to `true` (or the `mandatory-expression` evaluates to `true` for a particular message), if the message cannot be delivered to a queue, an `AmqpMessageReturnedException` is thrown. +This exception has `returnedMessage`, `replyCode`, and `replyText` properties, as well as the `exchange` and `routingKey` used for the send. + +NOTE: This feature uses publisher returns. +You can enable it by setting `publisherReturns` to `true` on the `CachingConnectionFactory` (see xref:amqp/connections.adoc#cf-pub-conf-ret[Publisher Confirms and Returns]). +Also, you must not have registered your own `ReturnCallback` with the `RabbitTemplate`. + +Starting with version 2.1.2, a `replyTimedOut` method has been added, letting subclasses be informed of the timeout so that they can clean up any retained state. + +Starting with versions 2.0.11 and 2.1.3, when you use the default `DirectReplyToMessageListenerContainer`, you can add an error handler by setting the template's `replyErrorHandler` property. +This error handler is invoked for any failed deliveries, such as late replies and messages received without a correlation header. +The exception passed in is a `ListenerExecutionFailedException`, which has a `failedMessage` property. + +[[direct-reply-to]] +== RabbitMQ Direct reply-to + +IMPORTANT: Starting with version 3.4.0, the RabbitMQ server supports https://www.rabbitmq.com/direct-reply-to.html[direct reply-to]. +This eliminates the main reason for a fixed reply queue (to avoid the need to create a temporary queue for each request). +Starting with Spring AMQP version 1.4.1 direct reply-to is used by default (if supported by the server) instead of creating temporary reply queues. +When no `replyQueue` is provided (or it is set with a name of `amq.rabbitmq.reply-to`), the `RabbitTemplate` automatically detects whether direct reply-to is supported and either uses it or falls back to using a temporary reply queue. +When using direct reply-to, a `reply-listener` is not required and should not be configured. + +Reply listeners are still supported with named queues (other than `amq.rabbitmq.reply-to`), allowing control of reply concurrency and so on. + +Starting with version 1.6, if you wish to use a temporary, exclusive, auto-delete queue for each +reply, set the `useTemporaryReplyQueues` property to `true`. +This property is ignored if you set a `replyAddress`. + +You can change the criteria that dictate whether to use direct reply-to by subclassing `RabbitTemplate` and overriding `useDirectReplyTo()` to check different criteria. +The method is called once only, when the first request is sent. + +Prior to version 2.0, the `RabbitTemplate` created a new consumer for each request and canceled the consumer when the reply was received (or timed out). +Now the template uses a `DirectReplyToMessageListenerContainer` instead, letting the consumers be reused. +The template still takes care of correlating the replies, so there is no danger of a late reply going to a different sender. +If you want to revert to the previous behavior, set the `useDirectReplyToContainer` (`direct-reply-to-container` when using XML configuration) property to false. + +The `AsyncRabbitTemplate` has no such option. +It always used a `DirectReplyToContainer` for replies when direct reply-to is used. + +Starting with version 2.3.7, the template has a new property `useChannelForCorrelation`. +When this is `true`, the server does not have to copy the correlation id from the request message headers to the reply message. +Instead, the channel used to send the request is used to correlate the reply to the request. + +[[message-correlation-with-a-reply-queue]] +== Message Correlation With A Reply Queue + +When using a fixed reply queue (other than `amq.rabbitmq.reply-to`), you must provide correlation data so that replies can be correlated to requests. +See https://www.rabbitmq.com/tutorials/tutorial-six-java.html[RabbitMQ Remote Procedure Call (RPC)]. +By default, the standard `correlationId` property is used to hold the correlation data. +However, if you wish to use a custom property to hold correlation data, you can set the `correlation-key` attribute on the . +Explicitly setting the attribute to `correlationId` is the same as omitting the attribute. +The client and server must use the same header for correlation data. + +NOTE: Spring AMQP version 1.1 used a custom property called `spring_reply_correlation` for this data. +If you wish to revert to this behavior with the current version (perhaps to maintain compatibility with another application using 1.1), you must set the attribute to `spring_reply_correlation`. + +By default, the template generates its own correlation ID (ignoring any user-supplied value). +If you wish to use your own correlation ID, set the `RabbitTemplate` instance's `userCorrelationId` property to `true`. + +IMPORTANT: The correlation ID must be unique to avoid the possibility of a wrong reply being returned for a request. + +[[reply-listener]] +== Reply Listener Container + +When using RabbitMQ versions prior to 3.4.0, a new temporary queue is used for each reply. +However, a single reply queue can be configured on the template, which can be more efficient and also lets you set arguments on that queue. +In this case, however, you must also provide a sub element. +This element provides a listener container for the reply queue, with the template being the listener. +All of the xref:amqp/containerAttributes.adoc[Message Listener Container Configuration] attributes allowed on a are allowed on the element, except for `connection-factory` and `message-converter`, which are inherited from the template's configuration. + +IMPORTANT: If you run multiple instances of your application or use multiple `RabbitTemplate` instances, you *MUST* use a unique reply queue for each. +RabbitMQ has no ability to select messages from a queue, so, if they all use the same queue, each instance would compete for replies and not necessarily receive their own. + +The following example defines a rabbit template with a connection factory: + +[source,xml] +---- + + + +---- + +While the container and template share a connection factory, they do not share a channel. +Therefore, requests and replies are not performed within the same transaction (if transactional). + +NOTE: Prior to version 1.5.0, the `reply-address` attribute was not available. +Replies were always routed by using the default exchange and the `reply-queue` name as the routing key. +This is still the default, but you can now specify the new `reply-address` attribute. +The `reply-address` can contain an address with the form `/` and the reply is routed to the specified exchange and routed to a queue bound with the routing key. +The `reply-address` has precedence over `reply-queue`. +When only `reply-address` is in use, the `` must be configured as a separate `` component. +The `reply-address` and `reply-queue` (or `queues` attribute on the ``) must refer to the same queue logically. + +With this configuration, a `SimpleListenerContainer` is used to receive the replies, with the `RabbitTemplate` being the `MessageListener`. +When defining a template with the `` namespace element, as shown in the preceding example, the parser defines the container and wires in the template as the listener. + +NOTE: When the template does not use a fixed `replyQueue` (or is using direct reply-to -- see xref:amqp/request-reply.adoc#direct-reply-to[RabbitMQ Direct reply-to]), a listener container is not needed. +Direct `reply-to` is the preferred mechanism when using RabbitMQ 3.4.0 or later. + +If you define your `RabbitTemplate` as a `` or use an `@Configuration` class to define it as an `@Bean` or when you create the template programmatically, you need to define and wire up the reply listener container yourself. +If you fail to do this, the template never receives the replies and eventually times out and returns null as the reply to a call to a `sendAndReceive` method. + +Starting with version 1.5, the `RabbitTemplate` detects if it has been +configured as a `MessageListener` to receive replies. +If not, attempts to send and receive messages with a reply address +fail with an `IllegalStateException` (because the replies are never received). + +Further, if a simple `replyAddress` (queue name) is used, the reply listener container verifies that it is listening +to a queue with the same name. +This check cannot be performed if the reply address is an exchange and routing key and a debug log message is written. + +IMPORTANT: When wiring the reply listener and template yourself, it is important to ensure that the template's `replyAddress` and the container's `queues` (or `queueNames`) properties refer to the same queue. +The template inserts the reply address into the outbound message `replyTo` property. + +The following listing shows examples of how to manually wire up the beans: + +[source,xml] +---- + + + + + + + + + + + + + + + + +---- + +[source,java] +---- + @Bean + public RabbitTemplate amqpTemplate() { + RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory()); + rabbitTemplate.setMessageConverter(msgConv()); + rabbitTemplate.setReplyAddress(replyQueue().getName()); + rabbitTemplate.setReplyTimeout(60000); + rabbitTemplate.setUseDirectReplyToContainer(false); + return rabbitTemplate; + } + + @Bean + public SimpleMessageListenerContainer replyListenerContainer() { + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); + container.setConnectionFactory(connectionFactory()); + container.setQueues(replyQueue()); + container.setMessageListener(amqpTemplate()); + return container; + } + + @Bean + public Queue replyQueue() { + return new Queue("my.reply.queue"); + } +---- + +A complete example of a `RabbitTemplate` wired with a fixed reply queue, together with a "`remote`" listener container that handles the request and returns the reply is shown in https://github.com/spring-projects/spring-amqp/tree/main/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/JavaConfigFixedReplyQueueTests.java[this test case]. + +IMPORTANT: When the reply times out (`replyTimeout`), the `sendAndReceive()` methods return null. + +Prior to version 1.3.6, late replies for timed out messages were only logged. +Now, if a late reply is received, it is rejected (the template throws an `AmqpRejectAndDontRequeueException`). +If the reply queue is configured to send rejected messages to a dead letter exchange, the reply can be retrieved for later analysis. +To do so, bind a queue to the configured dead letter exchange with a routing key equal to the reply queue's name. + +See the https://www.rabbitmq.com/dlx.html[RabbitMQ Dead Letter Documentation] for more information about configuring dead lettering. +You can also take a look at the `FixedReplyQueueDeadLetterTests` test case for an example. + +[[async-template]] +== Async Rabbit Template + +Version 1.6 introduced the `AsyncRabbitTemplate`. +This has similar `sendAndReceive` (and `convertSendAndReceive`) methods to those on the xref:amqp/template.adoc[`AmqpTemplate`]. +However, instead of blocking, they return a `CompletableFuture`. + +The `sendAndReceive` methods return a `RabbitMessageFuture`. +The `convertSendAndReceive` methods return a `RabbitConverterFuture`. + +You can either synchronously retrieve the result later, by invoking `get()` on the future, or you can register a callback that is called asynchronously with the result. +The following listing shows both approaches: + +[source, java] +---- +@Autowired +private AsyncRabbitTemplate template; + +... + +public void doSomeWorkAndGetResultLater() { + + ... + + CompletableFuture future = this.template.convertSendAndReceive("foo"); + + // do some more work + + String reply = null; + try { + reply = future.get(10, TimeUnit.SECONDS); + } + catch (ExecutionException e) { + ... + } + + ... + +} + +public void doSomeWorkAndGetResultAsync() { + + ... + + RabbitConverterFuture future = this.template.convertSendAndReceive("foo"); + future.whenComplete((result, ex) -> { + if (ex == null) { + // success + } + else { + // failure + } + }); + + ... + +} +---- + +If `mandatory` is set and the message cannot be delivered, the future throws an `ExecutionException` with a cause of `AmqpMessageReturnedException`, which encapsulates the returned message and information about the return. + +If `enableConfirms` is set, the future has a property called `confirm`, which is itself a `CompletableFuture` with `true` indicating a successful publish. +If the confirm future is `false`, the `RabbitFuture` has a further property called `nackCause`, which contains the reason for the failure, if available. + +IMPORTANT: The publisher confirm is discarded if it is received after the reply, since the reply implies a successful publish. + +You can set the `receiveTimeout` property on the template to time out replies (it defaults to `30000` - 30 seconds). +If a timeout occurs, the future is completed with an `AmqpReplyTimeoutException`. + +The template implements `SmartLifecycle`. +Stopping the template while there are pending replies causes the pending `Future` instances to be canceled. + +Starting with version 2.0, the asynchronous template now supports https://www.rabbitmq.com/direct-reply-to.html[direct reply-to] instead of a configured reply queue. +To enable this feature, use one of the following constructors: + +[source, java] +---- +public AsyncRabbitTemplate(ConnectionFactory connectionFactory, String exchange, String routingKey) + +public AsyncRabbitTemplate(RabbitTemplate template) +---- + +See xref:amqp/request-reply.adoc#direct-reply-to[RabbitMQ Direct reply-to] to use direct reply-to with the synchronous `RabbitTemplate`. + +Version 2.0 introduced variants of these methods (`convertSendAndReceiveAsType`) that take an additional `ParameterizedTypeReference` argument to convert complex returned types. +You must configure the underlying `RabbitTemplate` with a `SmartMessageConverter`. +See xref:amqp/message-converters.adoc#json-complex[Converting From a `Message` With `RabbitTemplate`] for more information. + +IMPORTANT: Starting with version 3.0, the `AsyncRabbitTemplate` methods now return `CompletableFuture` s instead of `ListenableFuture` s. + +[[remoting]] +== Spring Remoting with AMQP + +Spring remoting is no longer supported because the functionality has been removed from Spring Framework. + +Use `sendAndReceive` operations using the `RabbitTemplate` (client side ) and `@RabbitListener` instead. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/resilience-recovering-from-errors-and-broker-failures.adoc b/src/reference/antora/modules/ROOT/pages/amqp/resilience-recovering-from-errors-and-broker-failures.adoc new file mode 100644 index 00000000..18f51076 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/resilience-recovering-from-errors-and-broker-failures.adoc @@ -0,0 +1,212 @@ +[[resilience:-recovering-from-errors-and-broker-failures]] += Resilience: Recovering from Errors and Broker Failures + +Some of the key (and most popular) high-level features that Spring AMQP provides are to do with recovery and automatic re-connection in the event of a protocol error or broker failure. +We have seen all the relevant components already in this guide, but it should help to bring them all together here and call out the features and recovery scenarios individually. + +The primary reconnection features are enabled by the `CachingConnectionFactory` itself. +It is also often beneficial to use the `RabbitAdmin` auto-declaration features. +In addition, if you care about guaranteed delivery, you probably also need to use the `channelTransacted` flag in `RabbitTemplate` and `SimpleMessageListenerContainer` and the `AcknowledgeMode.AUTO` (or manual if you do the acks yourself) in the `SimpleMessageListenerContainer`. + +[[automatic-declaration]] +== Automatic Declaration of Exchanges, Queues, and Bindings + +The `RabbitAdmin` component can declare exchanges, queues, and bindings on startup. +It does this lazily, through a `ConnectionListener`. +Consequently, if the broker is not present on startup, it does not matter. +The first time a `Connection` is used (for example, +by sending a message) the listener fires and the admin features is applied. +A further benefit of doing the auto declarations in a listener is that, if the connection is dropped for any reason (for example, +broker death, network glitch, and others), they are applied again when the connection is re-established. + +NOTE: Queues declared this way must have fixed names -- either explicitly declared or generated by the framework for `AnonymousQueue` instances. +Anonymous queues are non-durable, exclusive, and auto-deleting. + +IMPORTANT: Automatic declaration is performed only when the `CachingConnectionFactory` cache mode is `CHANNEL` (the default). +This limitation exists because exclusive and auto-delete queues are bound to the connection. + +Starting with version 2.2.2, the `RabbitAdmin` will detect beans of type `DeclarableCustomizer` and apply the function before actually processing the declaration. +This is useful, for example, to set a new argument (property) before it has first class support within the framework. + +[source, java] +---- +@Bean +public DeclarableCustomizer customizer() { + return dec -> { + if (dec instanceof Queue && ((Queue) dec).getName().equals("my.queue")) { + dec.addArgument("some.new.queue.argument", true); + } + return dec; + }; +} +---- + +It is also useful in projects that don't provide direct access to the `Declarable` bean definitions. + +See also xref:amqp/connections.adoc#auto-recovery[RabbitMQ Automatic Connection/Topology recovery]. + +[[retry]] +== Failures in Synchronous Operations and Options for Retry + +If you lose your connection to the broker in a synchronous sequence when using `RabbitTemplate` (for instance), Spring AMQP throws an `AmqpException` (usually, but not always, `AmqpIOException`). +We do not try to hide the fact that there was a problem, so you have to be able to catch and respond to the exception. +The easiest thing to do if you suspect that the connection was lost (and it was not your fault) is to try the operation again. +You can do this manually, or you could look at using Spring Retry to handle the retry (imperatively or declaratively). + +Spring Retry provides a couple of AOP interceptors and a great deal of flexibility to specify the parameters of the retry (number of attempts, exception types, backoff algorithm, and others). +Spring AMQP also provides some convenience factory beans for creating Spring Retry interceptors in a convenient form for AMQP use cases, with strongly typed callback interfaces that you can use to implement custom recovery logic. +See the Javadoc and properties of `StatefulRetryOperationsInterceptor` and `StatelessRetryOperationsInterceptor` for more detail. +Stateless retry is appropriate if there is no transaction or if a transaction is started inside the retry callback. +Note that stateless retry is simpler to configure and analyze than stateful retry, but it is not usually appropriate if there is an ongoing transaction that must be rolled back or definitely is going to roll back. +A dropped connection in the middle of a transaction should have the same effect as a rollback. +Consequently, for reconnections where the transaction is started higher up the stack, stateful retry is usually the best choice. +Stateful retry needs a mechanism to uniquely identify a message. +The simplest approach is to have the sender put a unique value in the `MessageId` message property. +The provided message converters provide an option to do this: you can set `createMessageIds` to `true`. +Otherwise, you can inject a `MessageKeyGenerator` implementation into the interceptor. +The key generator must return a unique key for each message. +In versions prior to version 2.0, a `MissingMessageIdAdvice` was provided. +It enabled messages without a `messageId` property to be retried exactly once (ignoring the retry settings). +This advice is no longer provided, since, along with `spring-retry` version 1.2, its functionality is built into the interceptor and message listener containers. + +NOTE: For backwards compatibility, a message with a null message ID is considered fatal for the consumer (consumer is stopped) by default (after one retry). +To replicate the functionality provided by the `MissingMessageIdAdvice`, you can set the `statefulRetryFatalWithNullMessageId` property to `false` on the listener container. +With that setting, the consumer continues to run and the message is rejected (after one retry). +It is discarded or routed to the dead letter queue (if one is configured). + +Starting with version 1.3, a builder API is provided to aid in assembling these interceptors by using Java (in `@Configuration` classes). +The following example shows how to do so: + +[source,java] +---- +@Bean +public StatefulRetryOperationsInterceptor interceptor() { + return RetryInterceptorBuilder.stateful() + .maxAttempts(5) + .backOffOptions(1000, 2.0, 10000) // initialInterval, multiplier, maxInterval + .build(); +} +---- + +Only a subset of retry capabilities can be configured this way. +More advanced features would need the configuration of a `RetryTemplate` as a Spring bean. +See the https://docs.spring.io/spring-retry/docs/api/current/[Spring Retry Javadoc] for complete information about available policies and their configuration. + +[[batch-retry]] +== Retry with Batch Listeners + +It is not recommended to configure retry with a batch listener, unless the batch was created by the producer, in a single record. +See xref:amqp/receiving-messages/de-batching.adoc[Batched Messages] for information about consumer and producer-created batches. +With a consumer-created batch, the framework has no knowledge about which message in the batch caused the failure so recovery after the retries are exhausted is not possible. +With producer-created batches, since there is only one message that actually failed, the whole message can be recovered. +Applications may want to inform a custom recoverer where in the batch the failure occurred, perhaps by setting an index property of the thrown exception. + +A retry recoverer for a batch listener must implement `MessageBatchRecoverer`. + +[[async-listeners]] +== Message Listeners and the Asynchronous Case + +If a `MessageListener` fails because of a business exception, the exception is handled by the message listener container, which then goes back to listening for another message. +If the failure is caused by a dropped connection (not a business exception), the consumer that is collecting messages for the listener has to be cancelled and restarted. +The `SimpleMessageListenerContainer` handles this seamlessly, and it leaves a log to say that the listener is being restarted. +In fact, it loops endlessly, trying to restart the consumer. +Only if the consumer is very badly behaved indeed will it give up. +One side effect is that if the broker is down when the container starts, it keeps trying until a connection can be established. + +Business exception handling, as opposed to protocol errors and dropped connections, might need more thought and some custom configuration, especially if transactions or container acks are in use. +Prior to 2.8.x, RabbitMQ had no definition of dead letter behavior. +Consequently, by default, a message that is rejected or rolled back because of a business exception can be redelivered endlessly. +To put a limit on the client on the number of re-deliveries, one choice is a `StatefulRetryOperationsInterceptor` in the advice chain of the listener. +The interceptor can have a recovery callback that implements a custom dead letter action -- whatever is appropriate for your particular environment. + +Another alternative is to set the container's `defaultRequeueRejected` property to `false`. +This causes all failed messages to be discarded. +When using RabbitMQ 2.8.x or higher, this also facilitates delivering the message to a dead letter exchange. + +Alternatively, you can throw a `AmqpRejectAndDontRequeueException`. +Doing so prevents message requeuing, regardless of the setting of the `defaultRequeueRejected` property. + +Starting with version 2.1, an `ImmediateRequeueAmqpException` is introduced to perform exactly the opposite logic: the message will be requeued, regardless of the setting of the `defaultRequeueRejected` property. + +Often, a combination of both techniques is used. +You can use a `StatefulRetryOperationsInterceptor` in the advice chain with a `MessageRecoverer` that throws an `AmqpRejectAndDontRequeueException`. +The `MessageRecover` is called when all retries have been exhausted. +The `RejectAndDontRequeueRecoverer` does exactly that. +The default `MessageRecoverer` consumes the errant message and emits a `WARN` message. + +Starting with version 1.3, a new `RepublishMessageRecoverer` is provided, to allow publishing of failed messages after retries are exhausted. + +When a recoverer consumes the final exception, the message is ack'd and is not sent to the dead letter exchange by the broker, if configured. + +NOTE: When `RepublishMessageRecoverer` is used on the consumer side, the received message has `deliveryMode` in the `receivedDeliveryMode` message property. +In this case the `deliveryMode` is `null`. +That means a `NON_PERSISTENT` delivery mode on the broker. +Starting with version 2.0, you can configure the `RepublishMessageRecoverer` for the `deliveryMode` to set into the message to republish if it is `null`. +By default, it uses `MessageProperties` default value - `MessageDeliveryMode.PERSISTENT`. + +The following example shows how to set a `RepublishMessageRecoverer` as the recoverer: + +[source,java] +---- +@Bean +RetryOperationsInterceptor interceptor() { + return RetryInterceptorBuilder.stateless() + .maxAttempts(5) + .recoverer(new RepublishMessageRecoverer(amqpTemplate(), "something", "somethingelse")) + .build(); +} +---- + +The `RepublishMessageRecoverer` publishes the message with additional information in message headers, such as the exception message, stack trace, original exchange, and routing key. +Additional headers can be added by creating a subclass and overriding `additionalHeaders()`. +The `deliveryMode` (or any other properties) can also be changed in the `additionalHeaders()`, as the following example shows: + +[source,java] +---- +RepublishMessageRecoverer recoverer = new RepublishMessageRecoverer(amqpTemplate, "error") { + + protected Map additionalHeaders(Message message, Throwable cause) { + message.getMessageProperties() + .setDeliveryMode(message.getMessageProperties().getReceivedDeliveryMode()); + return null; + } + +}; +---- + +Starting with version 2.0.5, the stack trace may be truncated if it is too large; this is because all headers have to fit in a single frame. +By default, if the stack trace would cause less than 20,000 bytes ('headroom') to be available for other headers, it will be truncated. +This can be adjusted by setting the recoverer's `frameMaxHeadroom` property, if you need more or less space for other headers. +Starting with versions 2.1.13, 2.2.3, the exception message is included in this calculation, and the amount of stack trace will be maximized using the following algorithm: + +* if the stack trace alone would exceed the limit, the exception message header will be truncated to 97 bytes plus `...` and the stack trace is truncated too. +* if the stack trace is small, the message will be truncated (plus `...`) to fit in the available bytes (but the message within the stack trace itself is truncated to 97 bytes plus `...`). + +Whenever a truncation of any kind occurs, the original exception will be logged to retain the complete information. +The evaluation is performed after the headers are enhanced so information such as the exception type can be used in the expressions. + +Starting with version 2.4.8, the error exchange and routing key can be provided as SpEL expressions, with the `Message` being the root object for the evaluation. + +Starting with version 2.3.3, a new subclass `RepublishMessageRecovererWithConfirms` is provided; this supports both styles of publisher confirms and will wait for the confirmation before returning (or throw an exception if not confirmed or the message is returned). + +If the confirm type is `CORRELATED`, the subclass will also detect if a message is returned and throw an `AmqpMessageReturnedException`; if the publication is negatively acknowledged, it will throw an `AmqpNackReceivedException`. + +If the confirm type is `SIMPLE`, the subclass will invoke the `waitForConfirmsOrDie` method on the channel. + +See xref:amqp/connections.adoc#cf-pub-conf-ret[Publisher Confirms and Returns] for more information about confirms and returns. + +Starting with version 2.1, an `ImmediateRequeueMessageRecoverer` is added to throw an `ImmediateRequeueAmqpException`, which notifies a listener container to requeue the current failed message. + +[[exception-classification-for-spring-retry]] +== Exception Classification for Spring Retry + +Spring Retry has a great deal of flexibility for determining which exceptions can invoke retry. +The default configuration retries for all exceptions. +Given that user exceptions are wrapped in a `ListenerExecutionFailedException`, we need to ensure that the classification examines the exception causes. +The default classifier looks only at the top level exception. + +Since Spring Retry 1.0.3, the `BinaryExceptionClassifier` has a property called `traverseCauses` (default: `false`). +When `true`, it travers exception causes until it finds a match or there is no cause. + +To use this classifier for retry, you can use a `SimpleRetryPolicy` created with the constructor that takes the max attempts, the `Map` of `Exception` instances, and the boolean (`traverseCauses`) and inject this policy into the `RetryTemplate`. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/sending-messages.adoc b/src/reference/antora/modules/ROOT/pages/amqp/sending-messages.adoc new file mode 100644 index 00000000..64188a38 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/sending-messages.adoc @@ -0,0 +1,224 @@ +[[sending-messages]] += Sending Messages + +When sending a message, you can use any of the following methods: + +[source,java] +---- +void send(Message message) throws AmqpException; + +void send(String routingKey, Message message) throws AmqpException; + +void send(String exchange, String routingKey, Message message) throws AmqpException; +---- + +We can begin our discussion with the last method in the preceding listing, since it is actually the most explicit. +It lets an AMQP exchange name (along with a routing key)be provided at runtime. +The last parameter is the callback that is responsible for actual creating the message instance. +An example of using this method to send a message might look like this: +The following example shows how to use the `send` method to send a message: + +[source,java] +---- +amqpTemplate.send("marketData.topic", "quotes.nasdaq.THING1", + new Message("12.34".getBytes(), someProperties)); +---- + +You can set the `exchange` property on the template itself if you plan to use that template instance to send to the same exchange most or all of the time. +In such cases, you can use the second method in the preceding listing. +The following example is functionally equivalent to the previous example: + +[source,java] +---- +amqpTemplate.setExchange("marketData.topic"); +amqpTemplate.send("quotes.nasdaq.FOO", new Message("12.34".getBytes(), someProperties)); +---- + +If both the `exchange` and `routingKey` properties are set on the template, you can use the method that accepts only the `Message`. +The following example shows how to do so: + +[source,java] +---- +amqpTemplate.setExchange("marketData.topic"); +amqpTemplate.setRoutingKey("quotes.nasdaq.FOO"); +amqpTemplate.send(new Message("12.34".getBytes(), someProperties)); +---- + +A better way of thinking about the exchange and routing key properties is that the explicit method parameters always override the template's default values. +In fact, even if you do not explicitly set those properties on the template, there are always default values in place. +In both cases, the default is an empty `String`, but that is actually a sensible default. +As far as the routing key is concerned, it is not always necessary in the first place (for example, for +a `Fanout` exchange). +Furthermore, a queue may be bound to an exchange with an empty `String`. +Those are both legitimate scenarios for reliance on the default empty `String` value for the routing key property of the template. +As far as the exchange name is concerned, the empty `String` is commonly used because the AMQP specification defines the "`default exchange`" as having no name. +Since all queues are automatically bound to that default exchange (which is a direct exchange), using their name as the binding value, the second method in the preceding listing can be used for simple point-to-point messaging to any queue through the default exchange. +You can provide the queue name as the `routingKey`, either by providing the method parameter at runtime. +The following example shows how to do so: + +[source,java] +---- +RabbitTemplate template = new RabbitTemplate(); // using default no-name Exchange +template.send("queue.helloWorld", new Message("Hello World".getBytes(), someProperties)); +---- + +Alternately, you can create a template that can be used for publishing primarily or exclusively to a single Queue. +The following example shows how to do so: + +[source,java] +---- +RabbitTemplate template = new RabbitTemplate(); // using default no-name Exchange +template.setRoutingKey("queue.helloWorld"); // but we'll always send to this Queue +template.send(new Message("Hello World".getBytes(), someProperties)); +---- + +[[message-builder]] +== Message Builder API + +Starting with version 1.3, a message builder API is provided by the `MessageBuilder` and `MessagePropertiesBuilder`. +These methods provide a convenient "`fluent`" means of creating a message or message properties. +The following examples show the fluent API in action: + +[source,java] +---- +Message message = MessageBuilder.withBody("foo".getBytes()) + .setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN) + .setMessageId("123") + .setHeader("bar", "baz") + .build(); +---- + +[source,java] +---- +MessageProperties props = MessagePropertiesBuilder.newInstance() + .setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN) + .setMessageId("123") + .setHeader("bar", "baz") + .build(); +Message message = MessageBuilder.withBody("foo".getBytes()) + .andProperties(props) + .build(); +---- + +Each of the properties defined on the https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/core/MessageProperties.html[`MessageProperties`] can be set. +Other methods include `setHeader(String key, String value)`, `removeHeader(String key)`, `removeHeaders()`, and `copyProperties(MessageProperties properties)`. +Each property setting method has a `set*IfAbsent()` variant. +In the cases where a default initial value exists, the method is named `set*IfAbsentOrDefault()`. + +Five static methods are provided to create an initial message builder: + +[source,java] +---- +public static MessageBuilder withBody(byte[] body) <1> + +public static MessageBuilder withClonedBody(byte[] body) <2> + +public static MessageBuilder withBody(byte[] body, int from, int to) <3> + +public static MessageBuilder fromMessage(Message message) <4> + +public static MessageBuilder fromClonedMessage(Message message) <5> +---- + +<1> The message created by the builder has a body that is a direct reference to the argument. +<2> The message created by the builder has a body that is a new array containing a copy of bytes in the argument. +<3> The message created by the builder has a body that is a new array containing the range of bytes from the argument. +See https://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html[`Arrays.copyOfRange()`] for more details. +<4> The message created by the builder has a body that is a direct reference to the body of the argument. +The argument's properties are copied to a new `MessageProperties` object. +<5> The message created by the builder has a body that is a new array containing a copy of the argument's body. +The argument's properties are copied to a new `MessageProperties` object. + +Three static methods are provided to create a `MessagePropertiesBuilder` instance: + +[source,java] +---- +public static MessagePropertiesBuilder newInstance() <1> + +public static MessagePropertiesBuilder fromProperties(MessageProperties properties) <2> + +public static MessagePropertiesBuilder fromClonedProperties(MessageProperties properties) <3> +---- + +<1> A new message properties object is initialized with default values. +<2> The builder is initialized with, and `build()` will return, the provided properties object., +<3> The argument's properties are copied to a new `MessageProperties` object. + +With the `RabbitTemplate` implementation of `AmqpTemplate`, each of the `send()` methods has an overloaded version that takes an additional `CorrelationData` object. +When publisher confirms are enabled, this object is returned in the callback described in xref:amqp/template.adoc[`AmqpTemplate`]. +This lets the sender correlate a confirm (`ack` or `nack`) with the sent message. + +Starting with version 1.6.7, the `CorrelationAwareMessagePostProcessor` interface was introduced, allowing the correlation data to be modified after the message has been converted. +The following example shows how to use it: + +[source, java] +---- +Message postProcessMessage(Message message, Correlation correlation); +---- + +In version 2.0, this interface is deprecated. +The method has been moved to `MessagePostProcessor` with a default implementation that delegates to `postProcessMessage(Message message)`. + +Also starting with version 1.6.7, a new callback interface called `CorrelationDataPostProcessor` is provided. +This is invoked after all `MessagePostProcessor` instances (provided in the `send()` method as well as those provided in `setBeforePublishPostProcessors()`). +Implementations can update or replace the correlation data supplied in the `send()` method (if any). +The `Message` and original `CorrelationData` (if any) are provided as arguments. +The following example shows how to use the `postProcess` method: + +[source, java] +---- +CorrelationData postProcess(Message message, CorrelationData correlationData); +---- + +[[publisher-returns]] +== Publisher Returns + +When the template's `mandatory` property is `true`, returned messages are provided by the callback described in xref:amqp/template.adoc[`AmqpTemplate`]. + +Starting with version 1.4, the `RabbitTemplate` supports the SpEL `mandatoryExpression` property, which is evaluated against each request message as the root evaluation object, resolving to a `boolean` value. +Bean references, such as `@myBean.isMandatory(#root)`, can be used in the expression. + +Publisher returns can also be used internally by the `RabbitTemplate` in send and receive operations. +See xref:amqp/request-reply.adoc#reply-timeout[Reply Timeout] for more information. + +[[template-batching]] +== Batching + +Version 1.4.2 introduced the `BatchingRabbitTemplate`. +This is a subclass of `RabbitTemplate` with an overridden `send` method that batches messages according to the `BatchingStrategy`. +Only when a batch is complete is the message sent to RabbitMQ. +The following listing shows the `BatchingStrategy` interface definition: + +[source, java] +---- +public interface BatchingStrategy { + + MessageBatch addToBatch(String exchange, String routingKey, Message message); + + Date nextRelease(); + + Collection releaseBatches(); + +} +---- + +CAUTION: Batched data is held in memory. +Unsent messages can be lost in the event of a system failure. + +A `SimpleBatchingStrategy` is provided. +It supports sending messages to a single exchange or routing key. +It has the following properties: + +* `batchSize`: The number of messages in a batch before it is sent. +* `bufferLimit`: The maximum size of the batched message. +This preempts the `batchSize`, if exceeded, and causes a partial batch to be sent. +* `timeout`: A time after which a partial batch is sent when there is no new activity adding messages to the batch. + +The `SimpleBatchingStrategy` formats the batch by preceding each embedded message with a four-byte binary length. +This is communicated to the receiving system by setting the `springBatchFormat` message property to `lengthHeader4`. + +IMPORTANT: Batched messages are automatically de-batched by listener containers by default (by using the `springBatchFormat` message header). +Rejecting any message from a batch causes the entire batch to be rejected. + +However, see xref:amqp/receiving-messages/batch.adoc[@RabbitListener with Batching] for more information. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/template.adoc b/src/reference/antora/modules/ROOT/pages/amqp/template.adoc new file mode 100644 index 00000000..2973335b --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/template.adoc @@ -0,0 +1,547 @@ +[[amqp-template]] += `AmqpTemplate` + +As with many other high-level abstractions provided by the Spring Framework and related projects, Spring AMQP provides a "`template`" that plays a central role. +The interface that defines the main operations is called `AmqpTemplate`. +Those operations cover the general behavior for sending and receiving messages. +In other words, they are not unique to any implementation -- hence the "`AMQP`" in the name. +On the other hand, there are implementations of that interface that are tied to implementations of the AMQP protocol. +Unlike JMS, which is an interface-level API itself, AMQP is a wire-level protocol. +The implementations of that protocol provide their own client libraries, so each implementation of the template interface depends on a particular client library. +Currently, there is only a single implementation: `RabbitTemplate`. +In the examples that follow, we often use an `AmqpTemplate`. +However, when you look at the configuration examples or any code excerpts where the template is instantiated or setters are invoked, you can see the implementation type (for example, `RabbitTemplate`). + +As mentioned earlier, the `AmqpTemplate` interface defines all of the basic operations for sending and receiving messages. +We will explore message sending and reception, respectively, in <> and <>. + +See also xref:amqp/request-reply.adoc#async-template[Async Rabbit Template]. + +[[template-retry]] +== Adding Retry Capabilities + +Starting with version 1.3, you can now configure the `RabbitTemplate` to use a `RetryTemplate` to help with handling problems with broker connectivity. +See the https://github.com/spring-projects/spring-retry[spring-retry] project for complete information. +The following is only one example that uses an exponential back off policy and the default `SimpleRetryPolicy`, which makes three tries before throwing the exception to the caller. + +The following example uses the XML namespace: + +[source,xml] +---- + + + + + + + + + + + +---- + +The following example uses the `@Configuration` annotation in Java: + +[source,java] +---- +@Bean +public RabbitTemplate rabbitTemplate() { + RabbitTemplate template = new RabbitTemplate(connectionFactory()); + RetryTemplate retryTemplate = new RetryTemplate(); + ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); + backOffPolicy.setInitialInterval(500); + backOffPolicy.setMultiplier(10.0); + backOffPolicy.setMaxInterval(10000); + retryTemplate.setBackOffPolicy(backOffPolicy); + template.setRetryTemplate(retryTemplate); + return template; +} +---- + +Starting with version 1.4, in addition to the `retryTemplate` property, the `recoveryCallback` option is supported on the `RabbitTemplate`. +It is used as a second argument for the `RetryTemplate.execute(RetryCallback retryCallback, RecoveryCallback recoveryCallback)`. + +NOTE: The `RecoveryCallback` is somewhat limited, in that the retry context contains only the `lastThrowable` field. +For more sophisticated use cases, you should use an external `RetryTemplate` so that you can convey additional information to the `RecoveryCallback` through the context's attributes. +The following example shows how to do so: + +[source,java] +---- +retryTemplate.execute( + new RetryCallback() { + + @Override + public Object doWithRetry(RetryContext context) throws Exception { + context.setAttribute("message", message); + return rabbitTemplate.convertAndSend(exchange, routingKey, message); + } + + }, new RecoveryCallback() { + + @Override + public Object recover(RetryContext context) throws Exception { + Object message = context.getAttribute("message"); + Throwable t = context.getLastThrowable(); + // Do something with message + return null; + } + }); +} +---- + +In this case, you would *not* inject a `RetryTemplate` into the `RabbitTemplate`. + +[[publishing-is-async]] +== Publishing is Asynchronous -- How to Detect Successes and Failures + +Publishing messages is an asynchronous mechanism and, by default, messages that cannot be routed are dropped by RabbitMQ. +For successful publishing, you can receive an asynchronous confirm, as described in xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns]. +Consider two failure scenarios: + +* Publish to an exchange but there is no matching destination queue. +* Publish to a non-existent exchange. + +The first case is covered by publisher returns, as described in xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns]. + +For the second case, the message is dropped and no return is generated. +The underlying channel is closed with an exception. +By default, this exception is logged, but you can register a `ChannelListener` with the `CachingConnectionFactory` to obtain notifications of such events. +The following example shows how to add a `ConnectionListener`: + +[source, java] +---- +this.connectionFactory.addConnectionListener(new ConnectionListener() { + + @Override + public void onCreate(Connection connection) { + } + + @Override + public void onShutDown(ShutdownSignalException signal) { + ... + } + +}); +---- + +You can examine the signal's `reason` property to determine the problem that occurred. + +To detect the exception on the sending thread, you can `setChannelTransacted(true)` on the `RabbitTemplate` and the exception is detected on the `txCommit()`. +However, *transactions significantly impede performance*, so consider this carefully before enabling transactions for just this one use case. + +[[template-confirms]] +== Correlated Publisher Confirms and Returns + +The `RabbitTemplate` implementation of `AmqpTemplate` supports publisher confirms and returns. + +For returned messages, the template's `mandatory` property must be set to `true` or the `mandatory-expression` +must evaluate to `true` for a particular message. +This feature requires a `CachingConnectionFactory` that has its `publisherReturns` property set to `true` (see xref:amqp/connections.adoc#cf-pub-conf-ret[Publisher Confirms and Returns]). +Returns are sent to the client by it registering a `RabbitTemplate.ReturnsCallback` by calling `setReturnsCallback(ReturnsCallback callback)`. +The callback must implement the following method: + +[source,java] +---- +void returnedMessage(ReturnedMessage returned); +---- + +The `ReturnedMessage` has the following properties: + +- `message` - the returned message itself +- `replyCode` - a code indicating the reason for the return +- `replyText` - a textual reason for the return - e.g. `NO_ROUTE` +- `exchange` - the exchange to which the message was sent +- `routingKey` - the routing key that was used + +Only one `ReturnsCallback` is supported by each `RabbitTemplate`. +See also xref:amqp/request-reply.adoc#reply-timeout[Reply Timeout]. + +For publisher confirms (also known as publisher acknowledgements), the template requires a `CachingConnectionFactory` that has its `publisherConfirm` property set to `ConfirmType.CORRELATED`. +Confirms are sent to the client by it registering a `RabbitTemplate.ConfirmCallback` by calling `setConfirmCallback(ConfirmCallback callback)`. +The callback must implement this method: + +[source,java] +---- +void confirm(CorrelationData correlationData, boolean ack, String cause); +---- + +The `CorrelationData` is an object supplied by the client when sending the original message. +The `ack` is true for an `ack` and false for a `nack`. +For `nack` instances, the cause may contain a reason for the `nack`, if it is available when the `nack` is generated. +An example is when sending a message to a non-existent exchange. +In that case, the broker closes the channel. +The reason for the closure is included in the `cause`. +The `cause` was added in version 1.4. + +Only one `ConfirmCallback` is supported by a `RabbitTemplate`. + +NOTE: When a rabbit template send operation completes, the channel is closed. +This precludes the reception of confirms or returns when the connection factory cache is full (when there is space in the cache, the channel is not physically closed and the returns and confirms proceed normally). +When the cache is full, the framework defers the close for up to five seconds, in order to allow time for the confirms and returns to be received. +When using confirms, the channel is closed when the last confirm is received. +When using only returns, the channel remains open for the full five seconds. +We generally recommend setting the connection factory's `channelCacheSize` to a large enough value so that the channel on which a message is published is returned to the cache instead of being closed. +You can monitor channel usage by using the RabbitMQ management plugin. +If you see channels being opened and closed rapidly, you should consider increasing the cache size to reduce overhead on the server. + +IMPORTANT: Before version 2.1, channels enabled for publisher confirms were returned to the cache before the confirms were received. +Some other process could check out the channel and perform some operation that causes the channel to close -- such as publishing a message to a non-existent exchange. +This could cause the confirm to be lost. +Version 2.1 and later no longer return the channel to the cache while confirms are outstanding. +The `RabbitTemplate` performs a logical `close()` on the channel after each operation. +In general, this means that only one confirm is outstanding on a channel at a time. + +NOTE: Starting with version 2.2, the callbacks are invoked on one of the connection factory's `executor` threads. +This is to avoid a potential deadlock if you perform Rabbit operations from within the callback. +With previous versions, the callbacks were invoked directly on the `amqp-client` connection I/O thread; this would deadlock if you perform some RPC operation (such as opening a new channel) since the I/O thread blocks waiting for the result, but the result needs to be processed by the I/O thread itself. +With those versions, it was necessary to hand off work (such as sending a messasge) to another thread within the callback. +This is no longer necessary since the framework now hands off the callback invocation to the executor. + +IMPORTANT: The guarantee of receiving a returned message before the ack is still maintained as long as the return callback executes in 60 seconds or less. +The confirm is scheduled to be delivered after the return callback exits or after 60 seconds, whichever comes first. + +The `CorrelationData` object has a `CompletableFuture` that you can use to get the result, instead of using a `ConfirmCallback` on the template. +The following example shows how to configure a `CorrelationData` instance: + +[source, java] +---- +CorrelationData cd1 = new CorrelationData(); +this.templateWithConfirmsEnabled.convertAndSend("exchange", queue.getName(), "foo", cd1); +assertTrue(cd1.getFuture().get(10, TimeUnit.SECONDS).isAck()); +ReturnedMessage = cd1.getReturn(); +... +---- + +Since it is a `CompletableFuture`, you can either `get()` the result when ready or use `whenComplete()` for an asynchronous callback. +The `Confirm` object is a simple bean with 2 properties: `ack` and `reason` (for `nack` instances). +The reason is not populated for broker-generated `nack` instances. +It is populated for `nack` instances generated by the framework (for example, closing the connection while `ack` instances are outstanding). + +In addition, when both confirms and returns are enabled, the `CorrelationData` `return` property is populated with the returned message, if it couldn't be routed to any queue. +It is guaranteed that the returned message property is set before the future is set with the `ack`. +`CorrelationData.getReturn()` returns a `ReturnMessage` with properties: + +* message (the returned message) +* replyCode +* replyText +* exchange +* routingKey + +See also xref:amqp/template.adoc#scoped-operations[Scoped Operations] for a simpler mechanism for waiting for publisher confirms. + +[[scoped-operations]] +== Scoped Operations + +Normally, when using the template, a `Channel` is checked out of the cache (or created), used for the operation, and returned to the cache for reuse. +In a multi-threaded environment, there is no guarantee that the next operation uses the same channel. +There may be times, however, where you want to have more control over the use of a channel and ensure that a number of operations are all performed on the same channel. + +Starting with version 2.0, a new method called `invoke` is provided, with an `OperationsCallback`. +Any operations performed within the scope of the callback and on the provided `RabbitOperations` argument use the same dedicated `Channel`, which will be closed at the end (not returned to a cache). +If the channel is a `PublisherCallbackChannel`, it is returned to the cache after all confirms have been received (see xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns]). + +[source, java] +---- +@FunctionalInterface +public interface OperationsCallback { + + T doInRabbit(RabbitOperations operations); + +} +---- + +One example of why you might need this is if you wish to use the `waitForConfirms()` method on the underlying `Channel`. +This method was not previously exposed by the Spring API because the channel is, generally, cached and shared, as discussed earlier. +The `RabbitTemplate` now provides `waitForConfirms(long timeout)` and `waitForConfirmsOrDie(long timeout)`, which delegate to the dedicated channel used within the scope of the `OperationsCallback`. +The methods cannot be used outside of that scope, for obvious reasons. + +Note that a higher-level abstraction that lets you correlate confirms to requests is provided elsewhere (see xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns]). +If you want only to wait until the broker has confirmed delivery, you can use the technique shown in the following example: + +[source, java] +---- +Collection messages = getMessagesToSend(); +Boolean result = this.template.invoke(t -> { + messages.forEach(m -> t.convertAndSend(ROUTE, m)); + t.waitForConfirmsOrDie(10_000); + return true; +}); +---- + +If you wish `RabbitAdmin` operations to be invoked on the same channel within the scope of the `OperationsCallback`, the admin must have been constructed by using the same `RabbitTemplate` that was used for the `invoke` operation. + +NOTE: The preceding discussion is moot if the template operations are already performed within the scope of an existing transaction -- for example, when running on a transacted listener container thread and performing operations on a transacted template. +In that case, the operations are performed on that channel and committed when the thread returns to the container. +It is not necessary to use `invoke` in that scenario. + +When using confirms in this way, much of the infrastructure set up for correlating confirms to requests is not really needed (unless returns are also enabled). +Starting with version 2.2, the connection factory supports a new property called `publisherConfirmType`. +When this is set to `ConfirmType.SIMPLE`, the infrastructure is avoided and the confirm processing can be more efficient. + +Furthermore, the `RabbitTemplate` sets the `publisherSequenceNumber` property in the sent message `MessageProperties`. +If you wish to check (or log or otherwise use) specific confirms, you can do so with an overloaded `invoke` method, as the following example shows: + +[source, java] +---- +public T invoke(OperationsCallback action, com.rabbitmq.client.ConfirmCallback acks, + com.rabbitmq.client.ConfirmCallback nacks); +---- + +NOTE: These `ConfirmCallback` objects (for `ack` and `nack` instances) are the Rabbit client callbacks, not the template callback. + +The following example logs `ack` and `nack` instances: + +[source, java] +---- +Collection messages = getMessagesToSend(); +Boolean result = this.template.invoke(t -> { + messages.forEach(m -> t.convertAndSend(ROUTE, m)); + t.waitForConfirmsOrDie(10_000); + return true; +}, (tag, multiple) -> { + log.info("Ack: " + tag + ":" + multiple); +}, (tag, multiple) -> { + log.info("Nack: " + tag + ":" + multiple); +})); +---- + +IMPORTANT: Scoped operations are bound to a thread. +See xref:amqp/template.adoc#multi-strict[Strict Message Ordering in a Multi-Threaded Environment] for a discussion about strict ordering in a multi-threaded environment. + +[[multi-strict]] +== Strict Message Ordering in a Multi-Threaded Environment + +The discussion in xref:amqp/template.adoc#scoped-operations[Scoped Operations] applies only when the operations are performed on the same thread. + +Consider the following situation: + +* `thread-1` sends a message to a queue and hands off work to `thread-2` +* `thread-2` sends a message to the same queue + +Because of the async nature of RabbitMQ and the use of cached channels; it is not certain that the same channel will be used and therefore the order in which the messages arrive in the queue is not guaranteed. +(In most cases they will arrive in order, but the probability of out-of-order delivery is not zero). +To solve this use case, you can use a bounded channel cache with size `1` (together with a `channelCheckoutTimeout`) to ensure the messages are always published on the same channel, and order will be guaranteed. +To do this, if you have other uses for the connection factory, such as consumers, you should either use a dedicated connection factory for the template, or configure the template to use the publisher connection factory embedded in the main connection factory (see xref:amqp/template.adoc#separate-connection[Using a Separate Connection]). + +This is best illustrated with a simple Spring Boot Application: + +[source, java] +---- +@SpringBootApplication +public class Application { + + private static final Logger log = LoggerFactory.getLogger(Application.class); + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Bean + TaskExecutor exec() { + ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor(); + exec.setCorePoolSize(10); + return exec; + } + + @Bean + CachingConnectionFactory ccf() { + CachingConnectionFactory ccf = new CachingConnectionFactory("localhost"); + CachingConnectionFactory publisherCF = (CachingConnectionFactory) ccf.getPublisherConnectionFactory(); + publisherCF.setChannelCacheSize(1); + publisherCF.setChannelCheckoutTimeout(1000L); + return ccf; + } + + @RabbitListener(queues = "queue") + void listen(String in) { + log.info(in); + } + + @Bean + Queue queue() { + return new Queue("queue"); + } + + + @Bean + public ApplicationRunner runner(Service service, TaskExecutor exec) { + return args -> { + exec.execute(() -> service.mainService("test")); + }; + } + +} + +@Component +class Service { + + private static final Logger LOG = LoggerFactory.getLogger(Service.class); + + private final RabbitTemplate template; + + private final TaskExecutor exec; + + Service(RabbitTemplate template, TaskExecutor exec) { + template.setUsePublisherConnection(true); + this.template = template; + this.exec = exec; + } + + void mainService(String toSend) { + LOG.info("Publishing from main service"); + this.template.convertAndSend("queue", toSend); + this.exec.execute(() -> secondaryService(toSend.toUpperCase())); + } + + void secondaryService(String toSend) { + LOG.info("Publishing from secondary service"); + this.template.convertAndSend("queue", toSend); + } + +} +---- + +Even though the publishing is performed on two different threads, they will both use the same channel because the cache is capped at a single channel. + +Starting with version 2.3.7, the `ThreadChannelConnectionFactory` supports transferring a thread's channel(s) to another thread, using the `prepareContextSwitch` and `switchContext` methods. +The first method returns a context which is passed to the second thread which calls the second method. +A thread can have either a non-transactional channel or a transactional channel (or one of each) bound to it; you cannot transfer them individually, unless you use two connection factories. +An example follows: + +[source, java] +---- +@SpringBootApplication +public class Application { + + private static final Logger log = LoggerFactory.getLogger(Application.class); + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Bean + TaskExecutor exec() { + ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor(); + exec.setCorePoolSize(10); + return exec; + } + + @Bean + ThreadChannelConnectionFactory tccf() { + ConnectionFactory rabbitConnectionFactory = new ConnectionFactory(); + rabbitConnectionFactory.setHost("localhost"); + return new ThreadChannelConnectionFactory(rabbitConnectionFactory); + } + + @RabbitListener(queues = "queue") + void listen(String in) { + log.info(in); + } + + @Bean + Queue queue() { + return new Queue("queue"); + } + + + @Bean + public ApplicationRunner runner(Service service, TaskExecutor exec) { + return args -> { + exec.execute(() -> service.mainService("test")); + }; + } + +} + +@Component +class Service { + + private static final Logger LOG = LoggerFactory.getLogger(Service.class); + + private final RabbitTemplate template; + + private final TaskExecutor exec; + + private final ThreadChannelConnectionFactory connFactory; + + Service(RabbitTemplate template, TaskExecutor exec, + ThreadChannelConnectionFactory tccf) { + + this.template = template; + this.exec = exec; + this.connFactory = tccf; + } + + void mainService(String toSend) { + LOG.info("Publishing from main service"); + this.template.convertAndSend("queue", toSend); + Object context = this.connFactory.prepareSwitchContext(); + this.exec.execute(() -> secondaryService(toSend.toUpperCase(), context)); + } + + void secondaryService(String toSend, Object threadContext) { + LOG.info("Publishing from secondary service"); + this.connFactory.switchContext(threadContext); + this.template.convertAndSend("queue", toSend); + this.connFactory.closeThreadChannel(); + } + +} +---- + +IMPORTANT: Once the `prepareSwitchContext` is called, if the current thread performs any more operations, they will be performed on a new channel. +It is important to close the thread-bound channel when it is no longer needed. + +[[template-messaging]] +== Messaging Integration + +Starting with version 1.4, `RabbitMessagingTemplate` (built on top of `RabbitTemplate`) provides an integration with the Spring Framework messaging abstraction -- that is, +`org.springframework.messaging.Message`. +This lets you send and receive messages by using the `spring-messaging` `Message` abstraction. +This abstraction is used by other Spring projects, such as Spring Integration and Spring's STOMP support. +There are two message converters involved: one to convert between a spring-messaging `Message` and Spring AMQP's `Message` abstraction and one to convert between Spring AMQP's `Message` abstraction and the format required by the underlying RabbitMQ client library. +By default, the message payload is converted by the provided `RabbitTemplate` instance's message converter. +Alternatively, you can inject a custom `MessagingMessageConverter` with some other payload converter, as the following example shows: + +[source, java] +---- +MessagingMessageConverter amqpMessageConverter = new MessagingMessageConverter(); +amqpMessageConverter.setPayloadConverter(myPayloadConverter); +rabbitMessagingTemplate.setAmqpMessageConverter(amqpMessageConverter); +---- + +[[template-user-id]] +== Validated User Id + +Starting with version 1.6, the template now supports a `user-id-expression` (`userIdExpression` when using Java configuration). +If a message is sent, the user id property is set (if not already set) after evaluating this expression. +The root object for the evaluation is the message to be sent. + +The following examples show how to use the `user-id-expression` attribute: + +[source, xml] +---- + + + +---- + +The first example is a literal expression. +The second obtains the `username` property from a connection factory bean in the application context. + +[[separate-connection]] +== Using a Separate Connection + +Starting with version 2.0.2, you can set the `usePublisherConnection` property to `true` to use a different connection to that used by listener containers, when possible. +This is to avoid consumers being blocked when a producer is blocked for any reason. +The connection factories maintain a second internal connection factory for this purpose; by default it is the same type as the main factory, but can be set explicitly if you wish to use a different factory type for publishing. +If the rabbit template is running in a transaction started by the listener container, the container's channel is used, regardless of this setting. + +IMPORTANT: In general, you should not use a `RabbitAdmin` with a template that has this set to `true`. +Use the `RabbitAdmin` constructor that takes a connection factory. +If you use the other constructor that takes a template, ensure the template's property is `false`. +This is because, often, an admin is used to declare queues for listener containers. +Using a template that has the property set to `true` would mean that exclusive queues (such as `AnonymousQueue`) would be declared on a different connection to that used by listener containers. +In that case, the queues cannot be used by the containers. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/transactions.adoc b/src/reference/antora/modules/ROOT/pages/amqp/transactions.adoc new file mode 100644 index 00000000..fad761b9 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/transactions.adoc @@ -0,0 +1,157 @@ +[[transactions]] += Transactions + +The Spring Rabbit framework has support for automatic transaction management in the synchronous and asynchronous use cases with a number of different semantics that can be selected declaratively, as is familiar to existing users of Spring transactions. +This makes many if not most common messaging patterns easy to implement. + +There are two ways to signal the desired transaction semantics to the framework. +In both the `RabbitTemplate` and `SimpleMessageListenerContainer`, there is a flag `channelTransacted` which, if `true`, tells the framework to use a transactional channel and to end all operations (send or receive) with a commit or rollback (depending on the outcome), with an exception signaling a rollback. +Another signal is to provide an external transaction with one of Spring's `PlatformTransactionManager` implementations as a context for the ongoing operation. +If there is already a transaction in progress when the framework is sending or receiving a message, and the `channelTransacted` flag is `true`, the commit or rollback of the messaging transaction is deferred until the end of the current transaction. +If the `channelTransacted` flag is `false`, no transaction semantics apply to the messaging operation (it is auto-acked). + +The `channelTransacted` flag is a configuration time setting. +It is declared and processed once when the AMQP components are created, usually at application startup. +The external transaction is more dynamic in principle because the system responds to the current thread state at runtime. +However, in practice, it is often also a configuration setting, when the transactions are layered onto an application declaratively. + +For synchronous use cases with `RabbitTemplate`, the external transaction is provided by the caller, either declaratively or imperatively according to taste (the usual Spring transaction model). +The following example shows a declarative approach (usually preferred because it is non-invasive), where the template has been configured with `channelTransacted=true`: + +[source,java] +---- +@Transactional +public void doSomething() { + String incoming = rabbitTemplate.receiveAndConvert(); + // do some more database processing... + String outgoing = processInDatabaseAndExtractReply(incoming); + rabbitTemplate.convertAndSend(outgoing); +} +---- + +In the preceding example, a `String` payload is received, converted, and sent as a message body inside a method marked as `@Transactional`. +If the database processing fails with an exception, the incoming message is returned to the broker, and the outgoing message is not sent. +This applies to any operations with the `RabbitTemplate` inside a chain of transactional methods (unless, for instance, the `Channel` is directly manipulated to commit the transaction early). + +For asynchronous use cases with `SimpleMessageListenerContainer`, if an external transaction is needed, it has to be requested by the container when it sets up the listener. +To signal that an external transaction is required, the user provides an implementation of `PlatformTransactionManager` to the container when it is configured. +The following example shows how to do so: + +[source,java] +---- +@Configuration +public class ExampleExternalTransactionAmqpConfiguration { + + @Bean + public SimpleMessageListenerContainer messageListenerContainer() { + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); + container.setConnectionFactory(rabbitConnectionFactory()); + container.setTransactionManager(transactionManager()); + container.setChannelTransacted(true); + container.setQueueName("some.queue"); + container.setMessageListener(exampleListener()); + return container; + } + +} +---- + +In the preceding example, the transaction manager is added as a dependency injected from another bean definition (not shown), and the `channelTransacted` flag is also set to `true`. +The effect is that if the listener fails with an exception, the transaction is rolled back, and the message is also returned to the broker. +Significantly, if the transaction fails to commit (for example, because of +a database constraint error or connectivity problem), the AMQP transaction is also rolled back, and the message is returned to the broker. +This is sometimes known as a "`Best Efforts 1 Phase Commit`", and is a very powerful pattern for reliable messaging. +If the `channelTransacted` flag was set to `false` (the default) in the preceding example, the external transaction would still be provided for the listener, but all messaging operations would be auto-acked, so the effect is to commit the messaging operations even on a rollback of the business operation. + +[[conditional-rollback]] +== Conditional Rollback + +Prior to version 1.6.6, adding a rollback rule to a container's `transactionAttribute` when using an external transaction manager (such as JDBC) had no effect. +Exceptions always rolled back the transaction. + +Also, when using a https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/transaction.html#transaction-declarative[transaction advice] in the container's advice chain, conditional rollback was not very useful, because all listener exceptions are wrapped in a `ListenerExecutionFailedException`. + +The first problem has been corrected, and the rules are now applied properly. +Further, the `ListenerFailedRuleBasedTransactionAttribute` is now provided. +It is a subclass of `RuleBasedTransactionAttribute`, with the only difference being that it is aware of the `ListenerExecutionFailedException` and uses the cause of such exceptions for the rule. +This transaction attribute can be used directly in the container or through a transaction advice. + +The following example uses this rule: + +[source, java] +---- +@Bean +public AbstractMessageListenerContainer container() { + ... + container.setTransactionManager(transactionManager); + RuleBasedTransactionAttribute transactionAttribute = + new ListenerFailedRuleBasedTransactionAttribute(); + transactionAttribute.setRollbackRules(Collections.singletonList( + new NoRollbackRuleAttribute(DontRollBackException.class))); + container.setTransactionAttribute(transactionAttribute); + ... +} +---- + +[[transaction-rollback]] +== A note on Rollback of Received Messages + +AMQP transactions apply only to messages and acks sent to the broker. +Consequently, when there is a rollback of a Spring transaction and a message has been received, Spring AMQP has to not only rollback the transaction but also manually reject the message (sort of a nack, but that is not what the specification calls it). +The action taken on message rejection is independent of transactions and depends on the `defaultRequeueRejected` property (default: `true`). +For more information about rejecting failed messages, see xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc#async-listeners[Message Listeners and the Asynchronous Case]. + +For more information about RabbitMQ transactions and their limitations, see https://www.rabbitmq.com/semantics.html[RabbitMQ Broker Semantics]. + +NOTE: Prior to RabbitMQ 2.7.0, such messages (and any that are unacked when a channel is closed or aborts) went to the back of the queue on a Rabbit broker. +Since 2.7.0, rejected messages go to the front of the queue, in a similar manner to JMS rolled back messages. + +NOTE: Previously, message requeue on transaction rollback was inconsistent between local transactions and when a `TransactionManager` was provided. +In the former case, the normal requeue logic (`AmqpRejectAndDontRequeueException` or `defaultRequeueRejected=false`) applied (see xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc#async-listeners[Message Listeners and the Asynchronous Case]). +With a transaction manager, the message was unconditionally requeued on rollback. +Starting with version 2.0, the behavior is consistent and the normal requeue logic is applied in both cases. +To revert to the previous behavior, you can set the container's `alwaysRequeueWithTxManagerRollback` property to `true`. +See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration]. + +[[using-rabbittransactionmanager]] +== Using `RabbitTransactionManager` + +The https://docs.spring.io/spring-amqp/docs/latest_ga/api/org/springframework/amqp/rabbit/transaction/RabbitTransactionManager.html[RabbitTransactionManager] is an alternative to executing Rabbit operations within, and synchronized with, external transactions. +This transaction manager is an implementation of the https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/PlatformTransactionManager.html[`PlatformTransactionManager`] interface and should be used with a single Rabbit `ConnectionFactory`. + +IMPORTANT: This strategy is not able to provide XA transactions -- for example, in order to share transactions between messaging and database access. + +Application code is required to retrieve the transactional Rabbit resources through `ConnectionFactoryUtils.getTransactionalResourceHolder(ConnectionFactory, boolean)` instead of a standard `Connection.createChannel()` call with subsequent channel creation. +When using Spring AMQP's https://docs.spring.io/spring-amqp/docs/latest_ga/api/org/springframework/amqp/rabbit/core/RabbitTemplate.html[RabbitTemplate], it will autodetect a thread-bound Channel and automatically participate in its transaction. + +With Java Configuration, you can setup a new RabbitTransactionManager by using the following bean: + +[source,java] +---- +@Bean +public RabbitTransactionManager rabbitTransactionManager() { + return new RabbitTransactionManager(connectionFactory); +} +---- + +If you prefer XML configuration, you can declare the following bean in your XML Application Context file: + +[source,xml] +---- + + + +---- + +[[tx-sync]] +== Transaction Synchronization + +Synchronizing a RabbitMQ transaction with some other (e.g. DBMS) transaction provides "Best Effort One Phase Commit" semantics. +It is possible that the RabbitMQ transaction fails to commit during the after completion phase of transaction synchronization. +This is logged by the `spring-tx` infrastructure as an error, but no exception is thrown to the calling code. +Starting with version 2.3.10, you can call `ConnectionUtils.checkAfterCompletion()` after the transaction has committed on the same thread that processed the transaction. +It will simply return if no exception occurred; otherwise it will throw an `AfterCompletionFailedException` which will have a property representing the synchronization status of the completion. + +Enable this feature by calling `ConnectionFactoryUtils.enableAfterCompletionFailureCapture(true)`; this is a global flag and applies to all threads. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/change-history.adoc b/src/reference/antora/modules/ROOT/pages/appendix/change-history.adoc new file mode 100644 index 00000000..85b97003 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/change-history.adoc @@ -0,0 +1,5 @@ +[[change-history]] += Change History +:page-section-summary-toc: 1 + +This section describes changes that have been made as versions have changed. diff --git a/src/reference/antora/modules/ROOT/pages/appendix/current-release.adoc b/src/reference/antora/modules/ROOT/pages/appendix/current-release.adoc new file mode 100644 index 00000000..e76e79e5 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/current-release.adoc @@ -0,0 +1,6 @@ +[[current-release]] += Current Release +:page-section-summary-toc: 1 + +See xref:whats-new.adoc[What's New]. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/micrometer.adoc b/src/reference/antora/modules/ROOT/pages/appendix/micrometer.adoc new file mode 100644 index 00000000..0ec7789c --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/micrometer.adoc @@ -0,0 +1,8 @@ +[[observation-gen]] += Micrometer Observation Documentation + +This section describes the Micrometer integration. + +include::partial$metrics.adoc[leveloffset=-1] +include::partial$spans.adoc[leveloffset=-1] +include::partial$conventions.adoc[leveloffset=-1] diff --git a/src/reference/antora/modules/ROOT/pages/appendix/native.adoc b/src/reference/antora/modules/ROOT/pages/appendix/native.adoc new file mode 100644 index 00000000..ee924671 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/native.adoc @@ -0,0 +1,7 @@ +[[native-images]] += Native Images +:page-section-summary-toc: 1 + +https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aot[Spring AOT] native hints are provided to assist in developing native images for Spring applications that use Spring AMQP. + +Some examples can be seen in the https://github.com/spring-projects/spring-aot-smoke-tests/tree/main/integration[`spring-aot-smoke-tests` GitHub repository]. diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new.adoc new file mode 100644 index 00000000..d9f5c278 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new.adoc @@ -0,0 +1,4 @@ +[[previous-whats-new]] += Previous Releases +:page-section-summary-toc: 1 + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-3-since-1-2.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-3-since-1-2.adoc new file mode 100644 index 00000000..da13067f --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-3-since-1-2.adoc @@ -0,0 +1,107 @@ +[[changes-in-1-3-since-1-2]] += Changes in 1.3 Since 1.2 + +[[listener-concurrency]] +== Listener Concurrency + +The listener container now supports dynamic scaling of the number of consumers based on workload, or you can programmatically change the concurrency without stopping the container. +See <>. + +[[listener-queues]] +== Listener Queues + +The listener container now permits the queues on which it listens to be modified at runtime. +Also, the container now starts if at least one of its configured queues is available for use. +See <> + +This listener container now redeclares any auto-delete queues during startup. +See xref:amqp/receiving-messages/async-consumer.adoc#lc-auto-delete[`auto-delete` Queues]. + +[[consumer-priority]] +== Consumer Priority + +The listener container now supports consumer arguments, letting the `x-priority` argument be set. +See <>. + +[[exclusive-consumer]] +== Exclusive Consumer + +You can now configure `SimpleMessageListenerContainer` with a single `exclusive` consumer, preventing other consumers from listening to the queue. +See <>. + +[[rabbit-admin]] +== Rabbit Admin + +You can now have the broker generate the queue name, regardless of `durable`, `autoDelete`, and `exclusive` settings. +See xref:amqp/broker-configuration.adoc[Configuring the Broker]. + +[[direct-exchange-binding]] +== Direct Exchange Binding + +Previously, omitting the `key` attribute from a `binding` element of a `direct-exchange` configuration caused the queue or exchange to be bound with an empty string as the routing key. +Now it is bound with the the name of the provided `Queue` or `Exchange`. +If you wish to bind with an empty string routing key, you need to specify `key=""`. + +[[amqptemplate-changes]] +== `AmqpTemplate` Changes + +The `AmqpTemplate` now provides several synchronous `receiveAndReply` methods. +These are implemented by the `RabbitTemplate`. +For more information see <>. + +The `RabbitTemplate` now supports configuring a `RetryTemplate` to attempt retries (with optional back-off policy) for when the broker is not available. +For more information see xref:amqp/template.adoc#template-retry[Adding Retry Capabilities]. + +[[caching-connection-factory]] +== Caching Connection Factory + +You can now configure the caching connection factory to cache `Connection` instances and their `Channel` instances instead of using a single connection and caching only `Channel` instances. +See xref:amqp/connections.adoc[Connection and Resource Management]. + +[[binding-arguments]] +== Binding Arguments + +The `` of the `` now supports parsing of the `` sub-element. +You can now configure the `` of the `` with a `key/value` attribute pair (to match on a single header) or with a `` sub-element (allowing matching on multiple headers). +These options are mutually exclusive. +See xref:amqp/broker-configuration.adoc#headers-exchange[Headers Exchange]. + +[[routing-connection-factory]] +== Routing Connection Factory + +A new `SimpleRoutingConnectionFactory` has been introduced. +It allows configuration of `ConnectionFactories` mapping, to determine the target `ConnectionFactory` to use at runtime. +See <>. + +[[messagebuilder-and-messagepropertiesbuilder]] +== `MessageBuilder` and `MessagePropertiesBuilder` + +"`Fluent APIs`" for building messages or message properties are now provided. +See xref:amqp/sending-messages.adoc#message-builder[Message Builder API]. + +[[retryinterceptorbuilder-change]] +== `RetryInterceptorBuilder` Change + +A "`Fluent API`" for building listener container retry interceptors is now provided. +See xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc#retry[Failures in Synchronous Operations and Options for Retry]. + +[[republishmessagerecoverer-added]] +== `RepublishMessageRecoverer` Added + +This new `MessageRecoverer` is provided to allow publishing a failed message to another queue (including stack trace information in the header) when retries are exhausted. +See xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc#async-listeners[Message Listeners and the Asynchronous Case]. + +[[default-error-handler-since-1-3-2]] +== Default Error Handler (Since 1.3.2) + +A default `ConditionalRejectingErrorHandler` has been added to the listener container. +This error handler detects fatal message conversion problems and instructs the container to reject the message to prevent the broker from continually redelivering the unconvertible message. +See xref:amqp/exception-handling.adoc[Exception Handling]. + +[[listener-container-missingqueuesfatal-property-since-1-3-5]] +== Listener Container 'missingQueuesFatal` Property (Since 1.3.5) + +The `SimpleMessageListenerContainer` now has a property called `missingQueuesFatal` (default: `true`). +Previously, missing queues were always fatal. +See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration]. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-4-since-1-3.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-4-since-1-3.adoc new file mode 100644 index 00000000..a4346e4b --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-4-since-1-3.adoc @@ -0,0 +1,120 @@ +[[changes-in-1-4-since-1-3]] += Changes in 1.4 Since 1.3 + +[[rabbitlistener-annotation]] +== `@RabbitListener` Annotation + +POJO listeners can be annotated with `@RabbitListener`, enabled by `@EnableRabbit` or ``. +Spring Framework 4.1 is required for this feature. +See xref:amqp/receiving-messages/async-annotation-driven.adoc[Annotation-driven Listener Endpoints] for more information. + +[[rabbitmessagingtemplate-added]] +== `RabbitMessagingTemplate` Added + +A new `RabbitMessagingTemplate` lets you interact with RabbitMQ by using `spring-messaging` `Message` instances. +Internally, it uses the `RabbitTemplate`, which you can configure as normal. +Spring Framework 4.1 is required for this feature. +See xref:amqp/template.adoc#template-messaging[Messaging Integration] for more information. + +[[listener-container-missingqueuesfatal-attribute]] +== Listener Container `missingQueuesFatal` Attribute + +1.3.5 introduced the `missingQueuesFatal` property on the `SimpleMessageListenerContainer`. +This is now available on the listener container namespace element. +See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration]. + +[[rabbittemplate-confirmcallback-interface]] +== RabbitTemplate `ConfirmCallback` Interface + +The `confirm` method on this interface has an additional parameter called `cause`. +When available, this parameter contains the reason for a negative acknowledgement (nack). +See xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns]. + +[[rabbitconnectionfactorybean-added]] +== `RabbitConnectionFactoryBean` Added + +`RabbitConnectionFactoryBean` creates the underlying RabbitMQ `ConnectionFactory` used by the `CachingConnectionFactory`. +This enables configuration of SSL options using Spring's dependency injection. +See <>. + +[[using-cachingconnectionfactory]] +== Using `CachingConnectionFactory` + +The `CachingConnectionFactory` now lets the `connectionTimeout` be set as a property or as an attribute in the namespace. +It sets the property on the underlying RabbitMQ `ConnectionFactory`. +See <>. + +[[log-appender]] +== Log Appender + +The Logback `org.springframework.amqp.rabbit.logback.AmqpAppender` has been introduced. +It provides options similar to `org.springframework.amqp.rabbit.log4j.AmqpAppender`. +For more information, see the JavaDoc of these classes. + +The Log4j `AmqpAppender` now supports the `deliveryMode` property (`PERSISTENT` or `NON_PERSISTENT`, default: `PERSISTENT`). +Previously, all log4j messages were `PERSISTENT`. + +The appender also supports modification of the `Message` before sending -- allowing, for example, the addition of custom headers. +Subclasses should override the `postProcessMessageBeforeSend()`. + +[[listener-queues]] +== Listener Queues + +The listener container now, by default, redeclares any missing queues during startup. +A new `auto-declare` attribute has been added to the `` to prevent these re-declarations. +See xref:amqp/receiving-messages/async-consumer.adoc#lc-auto-delete[`auto-delete` Queues]. + +[[rabbittemplate:-mandatory-and-connectionfactoryselector-expressions]] +== `RabbitTemplate`: `mandatory` and `connectionFactorySelector` Expressions + +The `mandatoryExpression`, `sendConnectionFactorySelectorExpression`, and `receiveConnectionFactorySelectorExpression` SpEL Expression`s properties have been added to `RabbitTemplate`. +The `mandatoryExpression` is used to evaluate a `mandatory` boolean value against each request message when a `ReturnCallback` is in use. +See xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns]. +The `sendConnectionFactorySelectorExpression` and `receiveConnectionFactorySelectorExpression` are used when an `AbstractRoutingConnectionFactory` is provided, to determine the `lookupKey` for the target `ConnectionFactory` at runtime on each AMQP protocol interaction operation. +See <>. + +[[listeners-and-the-routing-connection-factory]] +== Listeners and the Routing Connection Factory + +You can configure a `SimpleMessageListenerContainer` with a routing connection factory to enable connection selection based on the queue names. +See <>. + +[[rabbittemplate:-recoverycallback-option]] +== `RabbitTemplate`: `RecoveryCallback` Option + +The `recoveryCallback` property has been added for use in the `retryTemplate.execute()`. +See xref:amqp/template.adoc#template-retry[Adding Retry Capabilities]. + +[[messageconversionexception-change]] +== `MessageConversionException` Change + +This exception is now a subclass of `AmqpException`. +Consider the following code: + +[source,java] +---- +try { + template.convertAndSend("thing1", "thing2", "cat"); +} +catch (AmqpException e) { + ... +} +catch (MessageConversionException e) { + ... +} +---- + +The second catch block is no longer reachable and needs to be moved above the catch-all `AmqpException` catch block. + +[[rabbitmq-3-4-compatibility]] +== RabbitMQ 3.4 Compatibility + +Spring AMQP is now compatible with the RabbitMQ 3.4, including direct reply-to. +See xref:introduction/quick-tour.adoc#compatibility[Compatibility] and xref:amqp/request-reply.adoc#direct-reply-to[RabbitMQ Direct reply-to] for more information. + +[[contenttypedelegatingmessageconverter-added]] +== `ContentTypeDelegatingMessageConverter` Added + +The `ContentTypeDelegatingMessageConverter` has been introduced to select the `MessageConverter` to use, based on the `contentType` property in the `MessageProperties`. +See xref:amqp/message-converters.adoc[Message Converters] for more information. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-5-since-1-4.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-5-since-1-4.adoc new file mode 100644 index 00000000..29282a00 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-5-since-1-4.adoc @@ -0,0 +1,197 @@ +[[changes-in-1-5-since-1-4]] += Changes in 1.5 Since 1.4 + +[[spring-erlang-is-no-longer-supported]] +== `spring-erlang` Is No Longer Supported + +The `spring-erlang` jar is no longer included in the distribution. +Use <> instead. + +[[cachingconnectionfactory-changes]] +== `CachingConnectionFactory` Changes + +[[empty-addresses-property-in-cachingconnectionfactory]] +=== Empty Addresses Property in `CachingConnectionFactory` + +Previously, if the connection factory was configured with a host and port but an empty String was also supplied for +`addresses`, the host and port were ignored. +Now, an empty `addresses` String is treated the same as a `null`, and the host and port are used. + +[[uri-constructor]] +=== URI Constructor + +The `CachingConnectionFactory` has an additional constructor, with a `URI` parameter, to configure the broker connection. + +[[connection-reset]] +=== Connection Reset + +A new method called `resetConnection()` has been added to let users reset the connection (or connections). +You might use this, for example, to reconnect to the primary broker after failing over to the secondary broker. +This *does* impact in-process operations. +The existing `destroy()` method does exactly the same, but the new method has a less daunting name. + +[[properties-to-control-container-queue-declaration-behavior]] +== Properties to Control Container Queue Declaration Behavior + +When the listener container consumers start, they attempt to passively declare the queues to ensure they are available +on the broker. +Previously, if these declarations failed (for example, because the queues didn't exist) or when an HA queue was being +moved, the retry logic was fixed at three retry attempts at five-second intervals. +If the queues still do not exist, the behavior is controlled by the `missingQueuesFatal` property (default: `true`). +Also, for containers configured to listen from multiple queues, if only a subset of queues are available, the consumer +retried the missing queues on a fixed interval of 60 seconds. + +The `declarationRetries`, `failedDeclarationRetryInterval`, and `retryDeclarationInterval` properties are now configurable. +See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration] for more information. + +[[class-package-change]] +== Class Package Change + +The `RabbitGatewaySupport` class has been moved from `o.s.amqp.rabbit.core.support` to `o.s.amqp.rabbit.core`. + +[[defaultmessagepropertiesconverter-changes]] +== `DefaultMessagePropertiesConverter` Changes + +You can now configure the `DefaultMessagePropertiesConverter` to +determine the maximum length of a `LongString` that is converted +to a `String` rather than to a `DataInputStream`. +The converter has an alternative constructor that takes the value as a limit. +Previously, this limit was hard-coded at `1024` bytes. +(Also available in 1.4.4). + +[[rabbitlistener-improvements]] +== `@RabbitListener` Improvements + +[[queuebinding-for-rabbitlistener]] +=== `@QueueBinding` for `@RabbitListener` + +The `bindings` attribute has been added to the `@RabbitListener` annotation as mutually exclusive with the `queues` +attribute to allow the specification of the `queue`, its `exchange`, and `binding` for declaration by a `RabbitAdmin` on +the Broker. + +[[spel-in-sendto]] +=== SpEL in `@SendTo` + +The default reply address (`@SendTo`) for a `@RabbitListener` can now be a SpEL expression. + +[[multiple-queue-names-through-properties]] +=== Multiple Queue Names through Properties + +You can now use a combination of SpEL and property placeholders to specify multiple queues for a listener. + +See xref:amqp/receiving-messages/async-annotation-driven.adoc[Annotation-driven Listener Endpoints] for more information. + +[[automatic-exchange-queue-and-binding-declaration]] +== Automatic Exchange, Queue, and Binding Declaration + +You can now declare beans that define a collection of these entities, and the `RabbitAdmin` adds the +contents to the list of entities that it declares when a connection is established. +See xref:amqp/broker-configuration.adoc#collection-declaration[Declaring Collections of Exchanges, Queues, and Bindings] for more information. + +[[rabbittemplate-changes]] +== `RabbitTemplate` Changes + +[[reply-address-added]] +=== `reply-address` Added + +The `reply-address` attribute has been added to the `` component as an alternative `reply-queue`. +See xref:amqp/request-reply.adoc[Request/Reply Messaging] for more information. +(Also available in 1.4.4 as a setter on the `RabbitTemplate`). + +[[blocking-receive-methods]] +=== Blocking `receive` Methods + +The `RabbitTemplate` now supports blocking in `receive` and `convertAndReceive` methods. +See xref:amqp/receiving-messages/polling-consumer.adoc[Polling Consumer] for more information. + +[[mandatory-with-sendandreceive-methods]] +=== Mandatory with `sendAndReceive` Methods + +When the `mandatory` flag is set when using the `sendAndReceive` and `convertSendAndReceive` methods, the calling thread +throws an `AmqpMessageReturnedException` if the request message cannot be delivered. +See xref:amqp/request-reply.adoc#reply-timeout[Reply Timeout] for more information. + +[[improper-reply-listener-configuration]] +=== Improper Reply Listener Configuration + +The framework tries to verify proper configuration of a reply listener container when using a named reply queue. + +See xref:amqp/request-reply.adoc#reply-listener[Reply Listener Container] for more information. + +[[rabbitmanagementtemplate-added]] +== `RabbitManagementTemplate` Added + +The `RabbitManagementTemplate` has been introduced to monitor and configure the RabbitMQ Broker by using the REST API provided by its https://www.rabbitmq.com/management.html[management plugin]. +See <> for more information. + +[[listener-container-bean-names-xml]] +== Listener Container Bean Names (XML) + +[IMPORTANT] +==== +The `id` attribute on the `` element has been removed. +Starting with this release, the `id` on the `` child element is used alone to name the listener container bean created for each listener element. + +Normal Spring bean name overrides are applied. +If a later `` is parsed with the same `id` as an existing bean, the new definition overrides the existing one. +Previously, bean names were composed from the `id` attributes of the `` and `` elements. + +When migrating to this release, if you have `id` attributes on your `` elements, remove them and set the `id` on the child `` element instead. +==== + +However, to support starting and stopping containers as a group, a new `group` attribute has been added. +When this attribute is defined, the containers created by this element are added to a bean with this name, of type `Collection`. +You can iterate over this group to start and stop containers. + +[[class-level-rabbitlistener]] +== Class-Level `@RabbitListener` + +The `@RabbitListener` annotation can now be applied at the class level. +Together with the new `@RabbitHandler` method annotation, this lets you select the handler method based on payload type. +See xref:amqp/receiving-messages/async-annotation-driven/method-selection.adoc[Multi-method Listeners] for more information. + +[[simplemessagelistenercontainer:-backoff-support]] +== `SimpleMessageListenerContainer`: BackOff Support + +The `SimpleMessageListenerContainer` can now be supplied with a `BackOff` instance for `consumer` startup recovery. +See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration] for more information. + +[[channel-close-logging]] +== Channel Close Logging + +A mechanism to control the log levels of channel closure has been introduced. +See <>. + +[[application-events]] +== Application Events + +The `SimpleMessageListenerContainer` now emits application events when consumers fail. +See xref:amqp/receiving-messages/consumer-events.adoc[Consumer Events] for more information. + +[[consumer-tag-configuration]] +== Consumer Tag Configuration + +Previously, the consumer tags for asynchronous consumers were generated by the broker. +With this release, it is now possible to supply a naming strategy to the listener container. +See xref:amqp/receiving-messages/consumerTags.adoc[Consumer Tags]. + +[[using-messagelisteneradapter]] +== Using `MessageListenerAdapter` + +The `MessageListenerAdapter` now supports a map of queue names (or consumer tags) to method names, to determine +which delegate method to call based on the queue from which the message was received. + +[[localizedqueueconnectionfactory-added]] +== `LocalizedQueueConnectionFactory` Added + +`LocalizedQueueConnectionFactory` is a new connection factory that connects to the node in a cluster where a mirrored queue actually resides. + +See xref:amqp/connections.adoc#queue-affinity[Queue Affinity and the `LocalizedQueueConnectionFactory`]. + +[[anonymous-queue-naming]] +== Anonymous Queue Naming + +Starting with version 1.5.3, you can now control how `AnonymousQueue` names are generated. +See xref:amqp/broker-configuration.adoc#anonymous-queue[`AnonymousQueue`] for more information. + + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-6-since-1-5.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-6-since-1-5.adoc new file mode 100644 index 00000000..237a3053 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-6-since-1-5.adoc @@ -0,0 +1,262 @@ +[[changes-in-1-6-since-1-5]] += Changes in 1.6 Since 1.5 + +[[testing-support]] +== Testing Support + +A new testing support library is now provided. +See xref:testing.adoc[Testing Support] for more information. + +[[builder]] +== Builder + +Builders that provide a fluent API for configuring `Queue` and `Exchange` objects are now available. +See xref:amqp/broker-configuration.adoc#builder-api[Builder API for Queues and Exchanges] for more information. + +[[namespace-changes]] +== Namespace Changes + +[[connection-factory]] +=== Connection Factory + +You can now add a `thread-factory` to a connection factory bean declaration -- for example, to name the threads +created by the `amqp-client` library. +See xref:amqp/connections.adoc[Connection and Resource Management] for more information. + +When you use `CacheMode.CONNECTION`, you can now limit the total number of connections allowed. +See xref:amqp/connections.adoc[Connection and Resource Management] for more information. + +[[queue-definitions]] +=== Queue Definitions + +You can now provide a naming strategy for anonymous queues. +See xref:amqp/broker-configuration.adoc#anonymous-queue[`AnonymousQueue`] for more information. + +[[listener-container-changes]] +== Listener Container Changes + +[[idle-message-listener-detection]] +=== Idle Message Listener Detection + +You can now configure listener containers to publish `ApplicationEvent` instances when idle. +See xref:amqp/receiving-messages/idle-containers.adoc[Detecting Idle Asynchronous Consumers] for more information. + +[[mismatched-queue-detection]] +=== Mismatched Queue Detection + +By default, when a listener container starts, if queues with mismatched properties or arguments are detected, +the container logs the exception but continues to listen. +The container now has a property called `mismatchedQueuesFatal`, which prevents the container (and context) from +starting if the problem is detected during startup. +It also stops the container if the problem is detected later, such as after recovering from a connection failure. +See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration] for more information. + +[[listener-container-logging]] +=== Listener Container Logging + +Now, listener container provides its `beanName` to the internal `SimpleAsyncTaskExecutor` as a `threadNamePrefix`. +It is useful for logs analysis. + +[[default-error-handler]] +=== Default Error Handler + +The default error handler (`ConditionalRejectingErrorHandler`) now considers irrecoverable `@RabbitListener` +exceptions as fatal. +See xref:amqp/exception-handling.adoc[Exception Handling] for more information. + + +[[autodeclare-and-rabbitadmin-instances]] +== `AutoDeclare` and `RabbitAdmin` Instances + +See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration] (`autoDeclare`) for some changes to the semantics of that option with respect to the use +of `RabbitAdmin` instances in the application context. + +[[amqptemplate:-receive-with-timeout]] +== `AmqpTemplate`: Receive with Timeout + +A number of new `receive()` methods with `timeout` have been introduced for the `AmqpTemplate` +and its `RabbitTemplate` implementation. +See xref:amqp/receiving-messages/polling-consumer.adoc[Polling Consumer] for more information. + +[[using-asyncrabbittemplate]] +== Using `AsyncRabbitTemplate` + +A new `AsyncRabbitTemplate` has been introduced. +This template provides a number of send and receive methods, where the return value is a `ListenableFuture`, which can +be used later to obtain the result either synchronously or asynchronously. +See xref:amqp/request-reply.adoc#async-template[Async Rabbit Template] for more information. + +[[rabbittemplate-changes]] +== `RabbitTemplate` Changes + +1.4.1 introduced the ability to use https://www.rabbitmq.com/direct-reply-to.html[direct reply-to] when the broker supports it. +It is more efficient than using a temporary queue for each reply. +This version lets you override this default behavior and use a temporary queue by setting the `useTemporaryReplyQueues` property to `true`. +See xref:amqp/request-reply.adoc#direct-reply-to[RabbitMQ Direct reply-to] for more information. + +The `RabbitTemplate` now supports a `user-id-expression` (`userIdExpression` when using Java configuration). +See https://www.rabbitmq.com/validated-user-id.html[Validated User-ID RabbitMQ documentation] and xref:amqp/template.adoc#template-user-id[Validated User Id] for more information. + +[[message-properties]] +== Message Properties + +[[using-correlationid]] +=== Using `CorrelationId` + +The `correlationId` message property can now be a `String`. +See xref:amqp/message-converters.adoc#message-properties-converters[Message Properties Converters] for more information. + +[[long-string-headers]] +=== Long String Headers + +Previously, the `DefaultMessagePropertiesConverter` "`converted`" headers longer than the long string limit (default 1024) +to a `DataInputStream` (actually, it referenced the `LongString` instance's `DataInputStream`). +On output, this header was not converted (except to a String -- for example, `java.io.DataInputStream@1d057a39` by calling +`toString()` on the stream). + +With this release, long `LongString` instances are now left as `LongString` instances by default. +You can access the contents by using the `getBytes[]`, `toString()`, or `getStream()` methods. +A large incoming `LongString` is now correctly "`converted`" on output too. + +See xref:amqp/message-converters.adoc#message-properties-converters[Message Properties Converters] for more information. + +[[inbound-delivery-mode]] +=== Inbound Delivery Mode + +The `deliveryMode` property is no longer mapped to the `MessageProperties.deliveryMode`. +This change avoids unintended propagation if the the same `MessageProperties` object is used to send an outbound message. +Instead, the inbound `deliveryMode` header is mapped to `MessageProperties.receivedDeliveryMode`. + +See xref:amqp/message-converters.adoc#message-properties-converters[Message Properties Converters] for more information. + +When using annotated endpoints, the header is provided in the header named `AmqpHeaders.RECEIVED_DELIVERY_MODE`. + +See xref:amqp/receiving-messages/async-annotation-driven/enable-signature.adoc[Annotated Endpoint Method Signature] for more information. + +[[inbound-user-id]] +=== Inbound User ID + +The `user_id` property is no longer mapped to the `MessageProperties.userId`. +This change avoids unintended propagation if the the same `MessageProperties` object is used to send an outbound message. +Instead, the inbound `userId` header is mapped to `MessageProperties.receivedUserId`. + +See xref:amqp/message-converters.adoc#message-properties-converters[Message Properties Converters] for more information. + +When you use annotated endpoints, the header is provided in the header named `AmqpHeaders.RECEIVED_USER_ID`. + +See xref:amqp/receiving-messages/async-annotation-driven/enable-signature.adoc[Annotated Endpoint Method Signature] for more information. + +[[rabbitadmin-changes]] +== `RabbitAdmin` Changes + +[[declaration-failures]] +=== Declaration Failures + +Previously, the `ignoreDeclarationFailures` flag took effect only for `IOException` on the channel (such as mis-matched +arguments). +It now takes effect for any exception (such as `TimeoutException`). +In addition, a `DeclarationExceptionEvent` is now published whenever a declaration fails. +The `RabbitAdmin` last declaration event is also available as a property `lastDeclarationExceptionEvent`. +See xref:amqp/broker-configuration.adoc[Configuring the Broker] for more information. + +[[rabbitlistener-changes]] +== `@RabbitListener` Changes + +[[multiple-containers-for-each-bean]] +=== Multiple Containers for Each Bean + +When you use Java 8 or later, you can now add multiple `@RabbitListener` annotations to `@Bean` classes or +their methods. +When using Java 7 or earlier, you can use the `@RabbitListeners` container annotation to provide the same +functionality. +See xref:amqp/receiving-messages/async-annotation-driven/repeatable-rabbit-listener.adoc[`@Repeatable` `@RabbitListener`] for more information. + +[[sendto-spel-expressions]] +=== `@SendTo` SpEL Expressions + +`@SendTo` for routing replies with no `replyTo` property can now be SpEL expressions evaluated against the +request/reply. +See xref:amqp/receiving-messages/async-annotation-driven/reply.adoc[Reply Management] for more information. + +[[queuebinding-improvements]] +=== `@QueueBinding` Improvements + +You can now specify arguments for queues, exchanges, and bindings in `@QueueBinding` annotations. +Header exchanges are now supported by `@QueueBinding`. +See xref:amqp/receiving-messages/async-annotation-driven.adoc[Annotation-driven Listener Endpoints] for more information. + +[[delayed-message-exchange]] +== Delayed Message Exchange + +Spring AMQP now has first class support for the RabbitMQ Delayed Message Exchange plugin. +See <> for more information. + +[[exchange-internal-flag]] +== Exchange Internal Flag + +Any `Exchange` definitions can now be marked as `internal`, and `RabbitAdmin` passes the value to the broker when +declaring the exchange. +See xref:amqp/broker-configuration.adoc[Configuring the Broker] for more information. + +[[cachingconnectionfactory-changes]] +== `CachingConnectionFactory` Changes + +[[cachingconnectionfactory-cache-statistics]] +=== `CachingConnectionFactory` Cache Statistics + +The `CachingConnectionFactory` now provides cache properties at runtime and over JMX. +See xref:amqp/connections.adoc#runtime-cache-properties[Runtime Cache Properties] for more information. + +[[accessing-the-underlying-rabbitmq-connection-factory]] +=== Accessing the Underlying RabbitMQ Connection Factory + +A new getter has been added to provide access to the underlying factory. +You can use this getter, for example, to add custom connection properties. +See xref:amqp/custom-client-props.adoc[Adding Custom Client Connection Properties] for more information. + +[[channel-cache]] +=== Channel Cache + +The default channel cache size has been increased from 1 to 25. +See xref:amqp/connections.adoc[Connection and Resource Management] for more information. + +In addition, the `SimpleMessageListenerContainer` no longer adjusts the cache size to be at least as large as the number +of `concurrentConsumers` -- this was superfluous, since the container consumer channels are never cached. + +[[using-rabbitconnectionfactorybean]] +== Using `RabbitConnectionFactoryBean` + +The factory bean now exposes a property to add client connection properties to connections made by the resulting +factory. + +[[java-deserialization]] +== Java Deserialization + +You can now configure a "`allowed list`" of allowable classes when you use Java deserialization. +You should consider creating an allowed list if you accept messages with serialized java objects from +untrusted sources. +See <> for more information. + +[[json-messageconverter]] +== JSON `MessageConverter` + +Improvements to the JSON message converter now allow the consumption of messages that do not have type information +in message headers. +See xref:amqp/receiving-messages/async-annotation-driven/conversion.adoc[Message Conversion for Annotated Methods] and <> for more information. + +[[logging-appenders]] +== Logging Appenders + +[[log4j-2]] +=== Log4j 2 + +A log4j 2 appender has been added, and the appenders can now be configured with an `addresses` property to connect +to a broker cluster. + +[[client-connection-properties]] +=== Client Connection Properties + +You can now add custom client connection properties to RabbitMQ connections. + +See xref:logging.adoc[Logging Subsystem AMQP Appenders] for more information. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-7-since-1-6.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-7-since-1-6.adoc new file mode 100644 index 00000000..2c565b30 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-7-since-1-6.adoc @@ -0,0 +1,76 @@ +[[changes-in-1-7-since-1-6]] += Changes in 1.7 Since 1.6 + +[[amqp-client-library]] +== AMQP Client library + +Spring AMQP now uses the new 4.0.x version of the `amqp-client` library provided by the RabbitMQ team. +This client has auto-recovery configured by default. +See xref:amqp/connections.adoc#auto-recovery[RabbitMQ Automatic Connection/Topology recovery]. + +NOTE: The 4.0.x client enables automatic recovery by default. +While compatible with this feature, Spring AMQP has its own recovery mechanisms, and the client recovery feature generally is not needed. +We recommend disabling `amqp-client` automatic recovery, to avoid getting `AutoRecoverConnectionNotCurrentlyOpenException` instances when the broker is available but the connection has not yet recovered. +Starting with version 1.7.1, Spring AMQP disables it unless you explicitly create your own RabbitMQ connection factory and provide it to the `CachingConnectionFactory`. +RabbitMQ `ConnectionFactory` instances created by the `RabbitConnectionFactoryBean` also have the option disabled by default. + + +[[log4j-2-upgrade]] +== Log4j 2 upgrade +The minimum Log4j 2 version (for the `AmqpAppender`) is now `2.7`. +The framework is no longer compatible with previous versions. +See xref:logging.adoc[Logging Subsystem AMQP Appenders] for more information. + +[[logback-appender]] +== Logback Appender + +This appender no longer captures caller data (method, line number) by default. +You can re-enable it by setting the `includeCallerData` configuration option. +See xref:logging.adoc[Logging Subsystem AMQP Appenders] for information about the available log appenders. + +[[spring-retry-upgrade]] +== Spring Retry Upgrade + +The minimum Spring Retry version is now `1.2`. +The framework is no longer compatible with previous versions. + +[[shutdown-behavior]] +=== Shutdown Behavior + +You can now set `forceCloseChannel` to `true` so that, if the container threads do not respond to a shutdown within `shutdownTimeout`, the channels are forced closed, +causing any unacked messages to be re-queued. +See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration] for more information. + +[[fasterxml-jackson-upgrade]] +== FasterXML Jackson upgrade + +The minimum Jackson version is now `2.8`. +The framework is no longer compatible with previous versions. + +[[junit-rules]] +== JUnit `@Rules` + +Rules that have previously been used internally by the framework have now been made available in a separate jar called `spring-rabbit-junit`. +See <> for more information. + +[[container-conditional-rollback]] +== Container Conditional Rollback + +When you use an external transaction manager (such as JDBC), rule-based rollback is now supported when you provide the container with a transaction attribute. +It is also now more flexible when you use a transaction advice. + +[[connection-naming-strategy]] +== Connection Naming Strategy + +A new `ConnectionNameStrategy` is now provided to populate the application-specific identification of the target RabbitMQ connection from the `AbstractConnectionFactory`. +See xref:amqp/connections.adoc[Connection and Resource Management] for more information. + +[[listener-container-changes]] +== Listener Container Changes + +[[transaction-rollback-behavior]] +=== Transaction Rollback Behavior + +You can now configure message re-queue on transaction rollback to be consistent, regardless of whether or not a transaction manager is configured. +See xref:amqp/transactions.adoc#transaction-rollback[A note on Rollback of Received Messages] for more information. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-0-since-1-7.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-0-since-1-7.adoc new file mode 100644 index 00000000..93551304 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-0-since-1-7.adoc @@ -0,0 +1,203 @@ +[[changes-in-2-0-since-1-7]] += Changes in 2.0 Since 1.7 + +[[using-cachingconnectionfactory]] +== Using `CachingConnectionFactory` + +Starting with version 2.0.2, you can configure the `RabbitTemplate` to use a different connection to that used by listener containers. +This change avoids deadlocked consumers when producers are blocked for any reason. +See xref:amqp/template.adoc#separate-connection[Using a Separate Connection] for more information. + +[[amqp-client-library]] +== AMQP Client library + +Spring AMQP now uses the new 5.0.x version of the `amqp-client` library provided by the RabbitMQ team. +This client has auto recovery configured by default. +See xref:amqp/connections.adoc#auto-recovery[RabbitMQ Automatic Connection/Topology recovery]. + +NOTE: As of version 4.0, the client enables automatic recovery by default. +While compatible with this feature, Spring AMQP has its own recovery mechanisms, and the client recovery feature generally is not needed. +We recommend that you disable `amqp-client` automatic recovery, to avoid getting `AutoRecoverConnectionNotCurrentlyOpenException` instances when the broker is available but the connection has not yet recovered. +Starting with version 1.7.1, Spring AMQP disables it unless you explicitly create your own RabbitMQ connection factory and provide it to the `CachingConnectionFactory`. +RabbitMQ `ConnectionFactory` instances created by the `RabbitConnectionFactoryBean` also have the option disabled by default. + +[[general-changes]] +== General Changes + +The `ExchangeBuilder` now builds durable exchanges by default. +The `@Exchange` annotation used within a `@QeueueBinding` also declares durable exchanges by default. +The `@Queue` annotation used within a `@RabbitListener` by default declares durable queues if named and non-durable if anonymous. +See xref:amqp/broker-configuration.adoc#builder-api[Builder API for Queues and Exchanges] and xref:amqp/receiving-messages/async-annotation-driven.adoc[Annotation-driven Listener Endpoints] for more information. + +[[deleted-classes]] +== Deleted Classes + +`UniquelyNameQueue` is no longer provided. +It is unusual to create a durable non-auto-delete queue with a unique name. +This class has been deleted. +If you require its functionality, use `new Queue(UUID.randomUUID().toString())`. + +[[new-listener-container]] +== New Listener Container + +The `DirectMessageListenerContainer` has been added alongside the existing `SimpleMessageListenerContainer`. +See xref:amqp/receiving-messages/choose-container.adoc[Choosing a Container] and xref:amqp/containerAttributes.adoc[Message Listener Container Configuration] for information about choosing which container to use as well as how to configure them. + + +[[log4j-appender]] +== Log4j Appender + +This appender is no longer available due to the end-of-life of log4j. +See xref:logging.adoc[Logging Subsystem AMQP Appenders] for information about the available log appenders. + + +[[rabbittemplate-changes]] +== `RabbitTemplate` Changes + +IMPORTANT: Previously, a non-transactional `RabbitTemplate` participated in an existing transaction if it ran on a transactional listener container thread. +This was a serious bug. +However, users might have relied on this behavior. +Starting with version 1.6.2, you must set the `channelTransacted` boolean on the template for it to participate in the container transaction. + +The `RabbitTemplate` now uses a `DirectReplyToMessageListenerContainer` (by default) instead of creating a new consumer for each request. +See xref:amqp/request-reply.adoc#direct-reply-to[RabbitMQ Direct reply-to] for more information. + +The `AsyncRabbitTemplate` now supports direct reply-to. +See xref:amqp/request-reply.adoc#async-template[Async Rabbit Template] for more information. + +The `RabbitTemplate` and `AsyncRabbitTemplate` now have `receiveAndConvert` and `convertSendAndReceiveAsType` methods that take a `ParameterizedTypeReference` argument, letting the caller specify the type to which to convert the result. +This is particularly useful for complex types or when type information is not conveyed in message headers. +It requires a `SmartMessageConverter` such as the `Jackson2JsonMessageConverter`. +See xref:amqp/request-reply.adoc[Request/Reply Messaging], xref:amqp/request-reply.adoc#async-template[Async Rabbit Template], xref:amqp/message-converters.adoc#json-complex[Converting From a `Message` With `RabbitTemplate`], and <> for more information. + +You can now use a `RabbitTemplate` to perform multiple operations on a dedicated channel. +See xref:amqp/template.adoc#scoped-operations[Scoped Operations] for more information. + +[[listener-adapter]] +== Listener Adapter + +A convenient `FunctionalInterface` is available for using lambdas with the `MessageListenerAdapter`. +See xref:amqp/receiving-messages/async-consumer.adoc#message-listener-adapter[`MessageListenerAdapter`] for more information. + +[[listener-container-changes]] +== Listener Container Changes + +[[prefetch-default-value]] +=== Prefetch Default Value + +The prefetch default value used to be 1, which could lead to under-utilization of efficient consumers. +The default prefetch value is now 250, which should keep consumers busy in most common scenarios and, +thus, improve throughput. + +IMPORTANT: There are scenarios where the prefetch value should +be low -- for example, with large messages, especially if the processing is slow (messages could add up +to a large amount of memory in the client process), and if strict message ordering is necessary +(the prefetch value should be set back to 1 in this case). +Also, with low-volume messaging and multiple consumers (including concurrency within a single listener container instance), you may wish to reduce the prefetch to get a more even distribution of messages across consumers. + +For more background about prefetch, see this post about https://www.rabbitmq.com/blog/2014/04/14/finding-bottlenecks-with-rabbitmq-3-3/[consumer utilization in RabbitMQ] +and this post about https://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/[queuing theory]. + +[[message-count]] +=== Message Count + +Previously, `MessageProperties.getMessageCount()` returned `0` for messages emitted by the container. +This property applies only when you use `basicGet` (for example, from `RabbitTemplate.receive()` methods) and is now initialized to `null` for container messages. + +[[transaction-rollback-behavior]] +=== Transaction Rollback Behavior + +Message re-queue on transaction rollback is now consistent, regardless of whether or not a transaction manager is configured. +See xref:amqp/transactions.adoc#transaction-rollback[A note on Rollback of Received Messages] for more information. + +[[shutdown-behavior]] +=== Shutdown Behavior + +If the container threads do not respond to a shutdown within `shutdownTimeout`, the channels are forced closed by default. +See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration] for more information. + +[[after-receive-message-post-processors]] +=== After Receive Message Post Processors + +If a `MessagePostProcessor` in the `afterReceiveMessagePostProcessors` property returns `null`, the message is discarded (and acknowledged if appropriate). + +[[connection-factory-changes]] +== Connection Factory Changes + +The connection and channel listener interfaces now provide a mechanism to obtain information about exceptions. +See xref:amqp/connections.adoc#connection-channel-listeners[Connection and Channel Listeners] and xref:amqp/template.adoc#publishing-is-async[Publishing is Asynchronous -- How to Detect Successes and Failures] for more information. + +A new `ConnectionNameStrategy` is now provided to populate the application-specific identification of the target RabbitMQ connection from the `AbstractConnectionFactory`. +See xref:amqp/connections.adoc[Connection and Resource Management] for more information. + +[[retry-changes]] +== Retry Changes + +The `MissingMessageIdAdvice` is no longer provided. +Its functionality is now built-in. +See xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc#retry[Failures in Synchronous Operations and Options for Retry] for more information. + +[[anonymous-queue-naming]] +== Anonymous Queue Naming + +By default, `AnonymousQueues` are now named with the default `Base64UrlNamingStrategy` instead of a simple `UUID` string. +See xref:amqp/broker-configuration.adoc#anonymous-queue[`AnonymousQueue`] for more information. + +[[rabbitlistener-changes]] +== `@RabbitListener` Changes + +You can now provide simple queue declarations (bound only to the default exchange) in `@RabbitListener` annotations. +See xref:amqp/receiving-messages/async-annotation-driven.adoc[Annotation-driven Listener Endpoints] for more information. + +You can now configure `@RabbitListener` annotations so that any exceptions are returned to the sender. +You can also configure a `RabbitListenerErrorHandler` to handle exceptions. +See xref:amqp/receiving-messages/async-annotation-driven/error-handling.adoc[Handling Exceptions] for more information. + +You can now bind a queue with multiple routing keys when you use the `@QueueBinding` annotation. +Also `@QueueBinding.exchange()` now supports custom exchange types and declares durable exchanges by default. + +You can now set the `concurrency` of the listener container at the annotation level rather than having to configure a different container factory for different concurrency settings. + +You can now set the `autoStartup` property of the listener container at the annotation level, overriding the default setting in the container factory. + +You can now set after receive and before send (reply) `MessagePostProcessor` instances in the `RabbitListener` container factories. + +See xref:amqp/receiving-messages/async-annotation-driven.adoc[Annotation-driven Listener Endpoints] for more information. + +Starting with version 2.0.3, one of the `@RabbitHandler` annotations on a class-level `@RabbitListener` can be designated as the default. +See xref:amqp/receiving-messages/async-annotation-driven/method-selection.adoc[Multi-method Listeners] for more information. + +[[container-conditional-rollback]] +== Container Conditional Rollback + +When using an external transaction manager (such as JDBC), rule-based rollback is now supported when you provide the container with a transaction attribute. +It is also now more flexible when you use a transaction advice. +See xref:amqp/transactions.adoc#conditional-rollback[Conditional Rollback] for more information. + +[[remove-jackson-1-x-support]] +== Remove Jackson 1.x support + +Deprecated in previous versions, Jackson `1.x` converters and related components have now been deleted. +You can use similar components based on Jackson 2.x. +See <> for more information. + +[[json-message-converter]] +== JSON Message Converter + +When the `__TypeId__` is set to `Hashtable` for an inbound JSON message, the default conversion type is now `LinkedHashMap`. +Previously, it was `Hashtable`. +To revert to a `Hashtable`, you can use `setDefaultMapType` on the `DefaultClassMapper`. + +[[xml-parsers]] +== XML Parsers + +When parsing `Queue` and `Exchange` XML components, the parsers no longer register the `name` attribute value as a bean alias if an `id` attribute is present. +See xref:amqp/broker-configuration.adoc#note-id-name[A Note On the `id` and `name` Attributes] for more information. + +[[blocked-connection]] +== Blocked Connection +You can now inject the `com.rabbitmq.client.BlockedListener` into the `org.springframework.amqp.rabbit.connection.Connection` object. +Also, the `ConnectionBlockedEvent` and `ConnectionUnblockedEvent` events are emitted by the `ConnectionFactory` when the connection is blocked or unblocked by the Broker. + +See xref:amqp/connections.adoc[Connection and Resource Management] for more information. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-1-since-2-0.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-1-since-2-0.adoc new file mode 100644 index 00000000..a905c189 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-1-since-2-0.adoc @@ -0,0 +1,127 @@ +[[changes-in-2-1-since-2-0]] += Changes in 2.1 Since 2.0 + +[[amqp-client-library]] +== AMQP Client library + +Spring AMQP now uses the 5.4.x version of the `amqp-client` library provided by the RabbitMQ team. +This client has auto-recovery configured by default. +See xref:amqp/connections.adoc#auto-recovery[RabbitMQ Automatic Connection/Topology recovery]. + +NOTE: As of version 4.0, the client enables automatic recovery by default. +While compatible with this feature, Spring AMQP has its own recovery mechanisms and the client recovery feature generally is not needed. +We recommend disabling `amqp-client` automatic recovery, to avoid getting `AutoRecoverConnectionNotCurrentlyOpenException` instances when the broker is available but the connection has not yet recovered. +Starting with version 1.7.1, Spring AMQP disables it unless you explicitly create your own RabbitMQ connection factory and provide it to the `CachingConnectionFactory`. +RabbitMQ `ConnectionFactory` instances created by the `RabbitConnectionFactoryBean` also have the option disabled by default. + + +[[package-changes]] +== Package Changes + +Certain classes have moved to different packages. +Most are internal classes and do not affect user applications. +Two exceptions are `ChannelAwareMessageListener` and `RabbitListenerErrorHandler`. +These interfaces are now in `org.springframework.amqp.rabbit.listener.api`. + +[[publisher-confirms-changes]] +== Publisher Confirms Changes + +Channels enabled for publisher confirmations are not returned to the cache while there are outstanding confirmations. +See xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns] for more information. + +[[listener-container-factory-improvements]] +== Listener Container Factory Improvements + +You can now use the listener container factories to create any listener container, not only those for use with `@RabbitListener` annotations or the `@RabbitListenerEndpointRegistry`. +See xref:amqp/receiving-messages/using-container-factories.adoc[Using Container Factories] for more information. + +`ChannelAwareMessageListener` now inherits from `MessageListener`. + +[[broker-event-listener]] +== Broker Event Listener + +A `BrokerEventListener` is introduced to publish selected broker events as `ApplicationEvent` instances. +See xref:amqp/broker-events.adoc[Broker Event Listener] for more information. + +[[rabbitadmin-changes]] +== RabbitAdmin Changes + +The `RabbitAdmin` discovers beans of type `Declarables` (which is a container for `Declarable` - `Queue`, `Exchange`, and `Binding` objects) and declare the contained objects on the broker. +Users are discouraged from using the old mechanism of declaring `>` (and others) and should use `Declarables` beans instead. +By default, the old mechanism is disabled. +See xref:amqp/broker-configuration.adoc#collection-declaration[Declaring Collections of Exchanges, Queues, and Bindings] for more information. + +`AnonymousQueue` instances are now declared with `x-queue-master-locator` set to `client-local` by default, to ensure the queues are created on the node the application is connected to. +See xref:amqp/broker-configuration.adoc[Configuring the Broker] for more information. + +[[rabbittemplate-changes]] +== RabbitTemplate Changes + +You can now configure the `RabbitTemplate` with the `noLocalReplyConsumer` option to control a `noLocal` flag for reply consumers in the `sendAndReceive()` operations. +See xref:amqp/request-reply.adoc[Request/Reply Messaging] for more information. + +`CorrelationData` for publisher confirmations now has a `ListenableFuture`, which you can use to get the acknowledgment instead of using a callback. +When returns and confirmations are enabled, the correlation data, if provided, is populated with the returned message. +See xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns] for more information. + +A method called `replyTimedOut` is now provided to notify subclasses that a reply has timed out, allowing for any state cleanup. +See xref:amqp/request-reply.adoc#reply-timeout[Reply Timeout] for more information. + +You can now specify an `ErrorHandler` to be invoked when using request/reply with a `DirectReplyToMessageListenerContainer` (the default) when exceptions occur when replies are delivered (for example, late replies). +See `setReplyErrorHandler` on the `RabbitTemplate`. +(Also since 2.0.11). + +[[message-conversion]] +== Message Conversion + +We introduced a new `Jackson2XmlMessageConverter` to support converting messages from and to XML format. +See xref:amqp/message-converters.adoc#jackson2xml[`Jackson2XmlMessageConverter`] for more information. + +[[management-rest-api]] +== Management REST API + +The `RabbitManagementTemplate` is now deprecated in favor of the direct `com.rabbitmq.http.client.Client` (or `com.rabbitmq.http.client.ReactorNettyClient`) usage. +See <> for more information. + +[[rabbitlistener-changes]] +== `@RabbitListener` Changes + +The listener container factory can now be configured with a `RetryTemplate` and, optionally, a `RecoveryCallback` used when sending replies. +See xref:amqp/receiving-messages/async-annotation-driven/enable.adoc[Enable Listener Endpoint Annotations] for more information. + +[[async-rabbitlistener-return]] +== Async `@RabbitListener` Return + +`@RabbitListener` methods can now return `ListenableFuture` or `Mono`. +See xref:amqp/receiving-messages/async-returns.adoc[Asynchronous `@RabbitListener` Return Types] for more information. + +[[connection-factory-bean-changes]] +== Connection Factory Bean Changes + +By default, the `RabbitConnectionFactoryBean` now calls `enableHostnameVerification()`. +To revert to the previous behavior, set the `enableHostnameVerification` property to `false`. + +[[connection-factory-changes]] +== Connection Factory Changes + +The `CachingConnectionFactory` now unconditionally disables auto-recovery in the underlying RabbitMQ `ConnectionFactory`, even if a pre-configured instance is provided in a constructor. +While steps have been taken to make Spring AMQP compatible with auto recovery, certain corner cases have arisen where issues remain. +Spring AMQP has had its own recovery mechanism since 1.0.0 and does not need to use the recovery provided by the client. +While it is still possible to enable the feature (using `cachingConnectionFactory.getRabbitConnectionFactory()` `.setAutomaticRecoveryEnabled()`) after the `CachingConnectionFactory` is constructed, **we strongly recommend that you not do so**. +We recommend that you use a separate RabbitMQ `ConnectionFactory` if you need auto recovery connections when using the client factory directly (rather than using Spring AMQP components). + +[[listener-container-changes]] +== Listener Container Changes + +The default `ConditionalRejectingErrorHandler` now completely discards messages that cause fatal errors if an `x-death` header is present. +See xref:amqp/exception-handling.adoc[Exception Handling] for more information. + +[[immediate-requeue]] +== Immediate requeue + +A new `ImmediateRequeueAmqpException` is introduced to notify a listener container that the message has to be re-queued. +To use this feature, a new `ImmediateRequeueMessageRecoverer` implementation is added. + +See xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc#async-listeners[Message Listeners and the Asynchronous Case] for more information. + + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-2-since-2-1.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-2-since-2-1.adoc new file mode 100644 index 00000000..90e38330 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-2-since-2-1.adoc @@ -0,0 +1,137 @@ +[[changes-in-2-2-since-2-1]] += Changes in 2.2 Since 2.1 + +This section describes the changes between version 2.1 and version 2.2. + +[[package-changes]] +== Package Changes + +The following classes/interfaces have been moved from `org.springframework.amqp.rabbit.core.support` to `org.springframework.amqp.rabbit.batch`: + +* `BatchingStrategy` +* `MessageBatch` +* `SimpleBatchingStrategy` + +In addition, `ListenerExecutionFailedException` has been moved from `org.springframework.amqp.rabbit.listener.exception` to `org.springframework.amqp.rabbit.support`. + +[[dependency-changes]] +== Dependency Changes + +JUnit (4) is now an optional dependency and will no longer appear as a transitive dependency. + +The `spring-rabbit-junit` module is now a *compile* dependency in the `spring-rabbit-test` module for a better target application development experience when with only a single `spring-rabbit-test` we get the full stack of testing utilities for AMQP components. + +[[-breaking-api-changes]] +== "Breaking" API Changes + +the JUnit (5) `RabbitAvailableCondition.getBrokerRunning()` now returns a `BrokerRunningSupport` instance instead of a `BrokerRunning`, which depends on JUnit 4. +It has the same API so it's just a matter of changing the class name of any references. +See xref:testing.adoc#junit5-conditions[JUnit5 Conditions] for more information. + +[[listenercontainer-changes]] +== ListenerContainer Changes + +Messages with fatal exceptions are now rejected and NOT requeued, by default, even if the acknowledge mode is manual. +See xref:amqp/exception-handling.adoc[Exception Handling] for more information. + +Listener performance can now be monitored using Micrometer `Timer` s. +See xref:amqp/receiving-messages/micrometer.adoc[Monitoring Listener Performance] for more information. + +[[rabbitlistener-changes]] +== @RabbitListener Changes + +You can now configure an `executor` on each listener, overriding the factory configuration, to more easily identify threads associated with the listener. +You can now override the container factory's `acknowledgeMode` property with the annotation's `ackMode` property. +See xref:amqp/receiving-messages/async-annotation-driven/enable.adoc#listener-property-overrides[overriding container factory properties] for more information. + +When using xref:amqp/receiving-messages/batch.adoc[batching], `@RabbitListener` methods can now receive a complete batch of messages in one call instead of getting them one-at-a-time. + +When receiving batched messages one-at-a-time, the last message has the `isLastInBatch` message property set to true. + +In addition, received batched messages now contain the `amqp_batchSize` header. + +Listeners can also consume batches created in the `SimpleMessageListenerContainer`, even if the batch is not created by the producer. +See xref:amqp/receiving-messages/choose-container.adoc[Choosing a Container] for more information. + +Spring Data Projection interfaces are now supported by the `Jackson2JsonMessageConverter`. +See xref:amqp/message-converters.adoc#data-projection[Using Spring Data Projection Interfaces] for more information. + +The `Jackson2JsonMessageConverter` now assumes the content is JSON if there is no `contentType` property, or it is the default (`application/octet-string`). +See xref:amqp/message-converters.adoc#Jackson2JsonMessageConverter-from-message[Converting from a `Message`] for more information. + +Similarly. the `Jackson2XmlMessageConverter` now assumes the content is XML if there is no `contentType` property, or it is the default (`application/octet-string`). +See xref:amqp/message-converters.adoc#jackson2xml[`Jackson2XmlMessageConverter`] for more information. + +When a `@RabbitListener` method returns a result, the bean and `Method` are now available in the reply message properties. +This allows configuration of a `beforeSendReplyMessagePostProcessor` to, for example, set a header in the reply to indicate which method was invoked on the server. +See xref:amqp/receiving-messages/async-annotation-driven/reply.adoc[Reply Management] for more information. + +You can now configure a `ReplyPostProcessor` to make modifications to a reply message before it is sent. +See xref:amqp/receiving-messages/async-annotation-driven/reply.adoc[Reply Management] for more information. + +[[amqp-logging-appenders-changes]] +== AMQP Logging Appenders Changes + +The Log4J and Logback `AmqpAppender` s now support a `verifyHostname` SSL option. + +Also these appenders now can be configured to not add MDC entries as headers. +The `addMdcAsHeaders` boolean option has been introduces to configure such a behavior. + +The appenders now support the `SaslConfig` property. + +See xref:logging.adoc[Logging Subsystem AMQP Appenders] for more information. + +[[messagelisteneradapter-changes]] +== MessageListenerAdapter Changes + +The `MessageListenerAdapter` provides now a new `buildListenerArguments(Object, Channel, Message)` method to build an array of arguments to be passed into target listener and an old one is deprecated. +See xref:amqp/receiving-messages/async-consumer.adoc#message-listener-adapter[`MessageListenerAdapter`] for more information. + +[[exchange/queue-declaration-changes]] +== Exchange/Queue Declaration Changes + +The `ExchangeBuilder` and `QueueBuilder` fluent APIs used to create `Exchange` and `Queue` objects for declaration by `RabbitAdmin` now support "well known" arguments. +See xref:amqp/broker-configuration.adoc#builder-api[Builder API for Queues and Exchanges] for more information. + +The `RabbitAdmin` has a new property `explicitDeclarationsOnly`. +See xref:amqp/broker-configuration.adoc#conditional-declaration[Conditional Declaration] for more information. + +[[connection-factory-changes]] +== Connection Factory Changes + +The `CachingConnectionFactory` has a new property `shuffleAddresses`. +When providing a list of broker node addresses, the list will be shuffled before creating a connection so that the order in which the connections are attempted is random. +See xref:amqp/connections.adoc#cluster[Connecting to a Cluster] for more information. + +When using Publisher confirms and returns, the callbacks are now invoked on the connection factory's `executor`. +This avoids a possible deadlock in the `amqp-clients` library if you perform rabbit operations from within the callback. +See xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns] for more information. + +Also, the publisher confirm type is now specified with the `ConfirmType` enum instead of the two mutually exclusive setter methods. + +The `RabbitConnectionFactoryBean` now uses TLS 1.2 by default when SSL is enabled. +See xref:amqp/connections.adoc#rabbitconnectionfactorybean-configuring-ssl[`RabbitConnectionFactoryBean` and Configuring SSL] for more information. + +[[new-messagepostprocessor-classes]] +== New MessagePostProcessor Classes + +Classes `DeflaterPostProcessor` and `InflaterPostProcessor` were added to support compression and decompression, respectively, when the message content-encoding is set to `deflate`. + +[[other-changes]] +== Other Changes + +The `Declarables` object (for declaring multiple queues, exchanges, bindings) now has a filtered getter for each type. +See xref:amqp/broker-configuration.adoc#collection-declaration[Declaring Collections of Exchanges, Queues, and Bindings] for more information. + +You can now customize each `Declarable` bean before the `RabbitAdmin` processes the declaration thereof. +See xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc#automatic-declaration[Automatic Declaration of Exchanges, Queues, and Bindings] for more information. + +`singleActiveConsumer()` has been added to the `QueueBuilder` to set the `x-single-active-consumer` queue argument. +See xref:amqp/broker-configuration.adoc#builder-api[Builder API for Queues and Exchanges] for more information. + +Outbound headers with values of type `Class` are now mapped using `getName()` instead of `toString()`. +See xref:amqp/message-converters.adoc#message-properties-converters[Message Properties Converters] for more information. + +Recovery of failed producer-created batches is now supported. +See xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc#batch-retry[Retry with Batch Listeners] for more information. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-3-since-2-2.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-3-since-2-2.adoc new file mode 100644 index 00000000..d49bf312 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-3-since-2-2.adoc @@ -0,0 +1,72 @@ +[[changes-in-2-3-since-2-2]] += Changes in 2.3 Since 2.2 + +This section describes the changes between version 2.2 and version 2.3. +See xref:appendix/change-history.adoc[Change History] for changes in previous versions. + +[[connection-factory-changes]] +== Connection Factory Changes + +Two additional connection factories are now provided. +See xref:amqp/connections.adoc#choosing-factory[Choosing a Connection Factory] for more information. + +[[rabbitlistener-changes]] +== `@RabbitListener` Changes + +You can now specify a reply content type. +See xref:amqp/receiving-messages/async-annotation-driven/reply-content-type.adoc[Reply ContentType] for more information. + +[[message-converter-changes]] +== Message Converter Changes + +The `Jackson2JMessageConverter` s can now deserialize abstract classes (including interfaces) if the `ObjectMapper` is configured with a custom deserializer. +See xref:amqp/message-converters.adoc#jackson-abstract[Deserializing Abstract Classes] for more information. + +[[testing-changes]] +== Testing Changes + +A new annotation `@SpringRabbitTest` is provided to automatically configure some infrastructure beans for when you are not using `SpringBootTest`. +See xref:testing.adoc#spring-rabbit-test[@SpringRabbitTest] for more information. + +[[rabbittemplate-changes]] +== RabbitTemplate Changes + +The template's `ReturnCallback` has been refactored as `ReturnsCallback` for simpler use in lambda expressions. +See xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns] for more information. + +When using returns and correlated confirms, the `CorrelationData` now requires a unique `id` property. +See xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns] for more information. + +When using direct reply-to, you can now configure the template such that the server does not need to return correlation data with the reply. +See xref:amqp/request-reply.adoc#direct-reply-to[RabbitMQ Direct reply-to] for more information. + +[[listener-container-changes]] +== Listener Container Changes + +A new listener container property `consumeDelay` is now available; it is helpful when using the https://github.com/rabbitmq/rabbitmq-sharding[RabbitMQ Sharding Plugin]. + +The default `JavaLangErrorHandler` now calls `System.exit(99)`. +To revert to the previous behavior (do nothing), add a no-op handler. + +The containers now support the `globalQos` property to apply the `prefetchCount` globally for the channel rather than for each consumer on the channel. + +See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration] for more information. + +[[messagepostprocessor-changes]] +== MessagePostProcessor Changes + +The compressing `MessagePostProcessor` s now use a comma to separate multiple content encodings instead of a colon. +The decompressors can handle both formats but, if you produce messages with this version that are consumed by versions earlier than 2.2.12, you should configure the compressor to use the old delimiter. +See the IMPORTANT note in xref:amqp/post-processing.adoc[Modifying Messages - Compression and More] for more information. + +[[multiple-broker-support-improvements]] +== Multiple Broker Support Improvements + +See xref:amqp/multi-rabbit.adoc[Multiple Broker (or Cluster) Support] for more information. + +[[republishmessagerecoverer-changes]] +== RepublishMessageRecoverer Changes + +A new subclass of this recoverer is not provided that supports publisher confirms. +See xref:amqp/resilience-recovering-from-errors-and-broker-failures.adoc#async-listeners[Message Listeners and the Asynchronous Case] for more information. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-4-since-2-3.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-4-since-2-3.adoc new file mode 100644 index 00000000..0dc4bf3f --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-4-since-2-3.adoc @@ -0,0 +1,24 @@ +[[changes-in-2-4-since-2-3]] += Changes in 2.4 Since 2.3 +:page-section-summary-toc: 1 + +This section describes the changes between version 2.3 and version 2.4. +See xref:appendix/change-history.adoc[Change History] for changes in previous versions. + +[[rabbitlistener-changes]] +== `@RabbitListener` Changes + +`MessageProperties` is now available for argument matching. +See xref:amqp/receiving-messages/async-annotation-driven/enable-signature.adoc[Annotated Endpoint Method Signature] for more information. + +[[rabbitadmin-changes]] +== `RabbitAdmin` Changes + +A new property `recoverManualDeclarations` allows recovery of manually declared queues/exchanges/bindings. +See xref:amqp/broker-configuration.adoc#declarable-recovery[Recovering Auto-Delete Declarations] for more information. + +[[remoting-support]] +== Remoting Support + +Support remoting using Spring Framework’s RMI support is deprecated and will be removed in 3.0. See Spring Remoting with AMQP for more information. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-3-0-since-2-4.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-3-0-since-2-4.adoc new file mode 100644 index 00000000..63562059 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-3-0-since-2-4.adoc @@ -0,0 +1,70 @@ +[[changes-in-3-0-since-2-4]] += Changes in 3.0 Since 2.4 + +[[java-17-spring-framework-6-0]] +== Java 17, Spring Framework 6.0 + +This version requires Spring Framework 6.0 and Java 17 + +[[remoting]] +== Remoting + +The remoting feature (using RMI) is no longer supported. + +[[observation]] +== Observation + +Enabling observation for timers and tracing using Micrometer is now supported. +See xref:stream.adoc#stream-micrometer-observation[Micrometer Observation] for more information. + +[[x30-Native]] +== Native Images + +Support for creating native images is provided. +See xref:appendix/previous-whats-new/changes-in-3-0-since-2-4.adoc#x30-Native[Native Images] for more information. + +[[asyncrabbittemplate]] +== AsyncRabbitTemplate + +IMPORTANT: The `AsyncRabbitTemplate` now returns `CompletableFuture` s instead of `ListenableFuture` s. +See xref:amqp/request-reply.adoc#async-template[Async Rabbit Template] for more information. + +[[stream-support-changes]] +== Stream Support Changes + +IMPORTANT: `RabbitStreamOperations` and `RabbitStreamTemplate` methods now return `CompletableFuture` instead of `ListenableFuture`. + +Super streams and single active consumers thereon are now supported. + +See xref:stream.adoc[Using the RabbitMQ Stream Plugin] for more information. + +[[rabbitlistener-changes]] +== `@RabbitListener` Changes + +Batch listeners can now consume `Collection` as well as `List`. +The batch messaging adapter now ensures that the method is suitable for consuming batches. +When setting the container factory `consumerBatchEnabled` to `true`, the `batchListener` property is also set to `true`. +See xref:amqp/receiving-messages/batch.adoc[@RabbitListener with Batching] for more information. + +`MessageConverter` s can now return `Optional.empty()` for a null value; this is currently implemented by the `Jackson2JsonMessageConverter`. +See xref:amqp/message-converters.adoc#Jackson2JsonMessageConverter-from-message[Converting from a `Message`] for more information + +You can now configure a `ReplyPostProcessor` via the container factory rather than via a property on `@RabbitListener`. +See xref:amqp/receiving-messages/async-annotation-driven/reply.adoc[Reply Management] for more information. + +The `@RabbitListener` (and `@RabbitHandler`) methods can now be declared as Kotlin `suspend` functions. +See xref:amqp/receiving-messages/async-returns.adoc[Asynchronous `@RabbitListener` Return Types] for more information. + +Starting with version 3.0.5, listeners with async return types (including Kotlin suspend functions) invoke the `RabbitListenerErrorHandler` (if configured) after a failure. +Previously, the error handler was only invoked with synchronous invocations. + +[[connection-factory-changes]] +== Connection Factory Changes + +The default `addressShuffleMode` in `AbstractConnectionFactory` is now `RANDOM`. +This results in connecting to a random host when multiple addresses are provided. +See xref:amqp/connections.adoc#cluster[Connecting to a Cluster] for more information. + +The `LocalizedQueueConnectionFactory` no longer uses the RabbitMQ `http-client` library to determine which node is the leader for a queue. +See xref:amqp/connections.adoc#queue-affinity[Queue Affinity and the `LocalizedQueueConnectionFactory`] for more information. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-to-1-1-since-1-0.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-to-1-1-since-1-0.adoc new file mode 100644 index 00000000..b74f52de --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-to-1-1-since-1-0.adoc @@ -0,0 +1,21 @@ +[[changes-to-1-1-since-1-0]] += Changes to 1.1 Since 1.0 +:page-section-summary-toc: 1 + +[[general]] +== General + +Spring-AMQP is now built with Gradle. + +Adds support for publisher confirms and returns. + +Adds support for HA queues and broker failover. + +Adds support for dead letter exchanges and dead letter queues. + +[[amqp-log4j-appender]] +== AMQP Log4j Appender + +Adds an option to support adding a message ID to logged messages. + +Adds an option to allow the specification of a `Charset` name to be used when converting `String` to `byte[]`. diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-to-1-2-since-1-1.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-to-1-2-since-1-1.adoc new file mode 100644 index 00000000..17410e98 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-to-1-2-since-1-1.adoc @@ -0,0 +1,58 @@ +[[changes-to-1-2-since-1-1]] += Changes to 1.2 Since 1.1 + +[[rabbitmq-version]] +== RabbitMQ Version + +Spring AMQP now uses RabbitMQ 3.1.x by default (but retains compatibility with earlier versions). +Certain deprecations have been added for features no longer supported by RabbitMQ 3.1.x -- federated exchanges and the `immediate` property on the `RabbitTemplate`. + +[[rabbit-admin]] +== Rabbit Admin + +`RabbitAdmin` now provides an option to let exchange, queue, and binding declarations continue when a declaration fails. +Previously, all declarations stopped on a failure. +By setting `ignore-declaration-exceptions`, such exceptions are logged (at the `WARN` level), but further declarations continue. +An example where this might be useful is when a queue declaration fails because of a slightly different `ttl` setting that would normally stop other declarations from proceeding. + +`RabbitAdmin` now provides an additional method called `getQueueProperties()`. +You can use this determine if a queue exists on the broker (returns `null` for a non-existent queue). +In addition, it returns the current number of messages in the queue as well as the current number of consumers. + +[[rabbit-template]] +== Rabbit Template + +Previously, when the `...sendAndReceive()` methods were used with a fixed reply queue, two custom headers were used for correlation data and to retain and restore reply queue information. +With this release, the standard message property (`correlationId`) is used by default, although you can specify a custom property to use instead. +In addition, nested `replyTo` information is now retained internally in the template, instead of using a custom header. + +The `immediate` property is deprecated. +You must not set this property when using RabbitMQ 3.0.x or greater. + +[[json-message-converters]] +== JSON Message Converters + +A Jackson 2.x `MessageConverter` is now provided, along with the existing converter that uses Jackson 1.x. + +[[automatic-declaration-of-queues-and-other-items]] +== Automatic Declaration of Queues and Other Items + +Previously, when declaring queues, exchanges and bindings, you could not define which connection factory was used for the declarations. +Each `RabbitAdmin` declared all components by using its connection. + +Starting with this release, you can now limit declarations to specific `RabbitAdmin` instances. +See xref:amqp/broker-configuration.adoc#conditional-declaration[Conditional Declaration]. + +[[amqp-remoting]] +== AMQP Remoting + +Facilities are now provided for using Spring remoting techniques, using AMQP as the transport for the RPC calls. +For more information see <> + +[[requested-heart-beats]] +== Requested Heart Beats + +Several users have asked for the underlying client connection factory's `requestedHeartBeats` property to be exposed on the Spring AMQP `CachingConnectionFactory`. +This is now available. +Previously, it was necessary to configure the AMQP client factory as a separate bean and provide a reference to it in the `CachingConnectionFactory`. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/earlier-releases.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/earlier-releases.adoc new file mode 100644 index 00000000..d92f5b10 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/earlier-releases.adoc @@ -0,0 +1,6 @@ +[[earlier-releases]] += Earlier Releases +:page-section-summary-toc: 1 + +See xref:appendix/previous-whats-new.adoc[Previous Releases] for changes in previous versions. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/message-converter-changes-1.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/message-converter-changes-1.adoc new file mode 100644 index 00000000..a342746b --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/message-converter-changes-1.adoc @@ -0,0 +1,7 @@ +[[message-converter-changes]] += Message Converter Changes +:page-section-summary-toc: 1 + +The `Jackson2JsonMessageConverter` can now determine the charset from the `contentEncoding` header. +See <> for more information. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/message-converter-changes.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/message-converter-changes.adoc new file mode 100644 index 00000000..a342746b --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/message-converter-changes.adoc @@ -0,0 +1,7 @@ +[[message-converter-changes]] += Message Converter Changes +:page-section-summary-toc: 1 + +The `Jackson2JsonMessageConverter` can now determine the charset from the `contentEncoding` header. +See <> for more information. + diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/stream-support-changes.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/stream-support-changes.adoc new file mode 100644 index 00000000..b6be8ec4 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/stream-support-changes.adoc @@ -0,0 +1,7 @@ +[[stream-support-changes]] += Stream Support Changes +:page-section-summary-toc: 1 + +`RabbitStreamOperations` and `RabbitStreamTemplate` have been deprecated in favor of `RabbitStreamOperations2` and `RabbitStreamTemplate2` respectively; they return `CompletableFuture` instead of `ListenableFuture`. +See xref:stream.adoc[Using the RabbitMQ Stream Plugin] for more information. + diff --git a/src/reference/asciidoc/further-reading.adoc b/src/reference/antora/modules/ROOT/pages/further-reading.adoc similarity index 94% rename from src/reference/asciidoc/further-reading.adoc rename to src/reference/antora/modules/ROOT/pages/further-reading.adoc index 37c01eea..d199110d 100644 --- a/src/reference/asciidoc/further-reading.adoc +++ b/src/reference/antora/modules/ROOT/pages/further-reading.adoc @@ -1,5 +1,6 @@ [[further-reading]] -=== Further Reading += Further Reading +:page-section-summary-toc: 1 For those who are not familiar with AMQP, the https://www.amqp.org/resources/download[specification] is actually quite readable. It is, of course, the authoritative source of information, and the Spring AMQP code should be easy to understand for anyone who is familiar with the spec. diff --git a/src/reference/asciidoc/preface.adoc b/src/reference/antora/modules/ROOT/pages/index.adoc similarity index 51% rename from src/reference/asciidoc/preface.adoc rename to src/reference/antora/modules/ROOT/pages/index.adoc index 5f07f478..d69ae831 100644 --- a/src/reference/asciidoc/preface.adoc +++ b/src/reference/antora/modules/ROOT/pages/index.adoc @@ -1,3 +1,13 @@ +[[spring-amqp-reference]] += Spring AMQP +ifdef::backend-html5[] +:revnumber: '' +endif::[] +:numbered: +:icons: font +:hide-uri-scheme: +Mark Pollack; Mark Fisher; Oleg Zhurakousky; Dave Syer; Gary Russell; Gunnar Hillert; Artem Bilan; Stéphane Nicoll; Arnaud Cogoluègnes; Jay Bryant + [[preface]] The Spring AMQP project applies core Spring concepts to the development of AMQP-based messaging solutions. We provide a "`template`" as a high-level abstraction for sending and receiving messages. @@ -5,3 +15,7 @@ We also provide support for message-driven POJOs. These libraries facilitate management of AMQP resources while promoting the use of dependency injection and declarative configuration. In all of these cases, you can see similarities to the JMS support in the Spring Framework. For other project-related information, visit the Spring AMQP project https://projects.spring.io/spring-amqp/[homepage]. + +(C) 2010 - 2021 by VMware, Inc. + +Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. \ No newline at end of file diff --git a/src/reference/antora/modules/ROOT/pages/integration-reference.adoc b/src/reference/antora/modules/ROOT/pages/integration-reference.adoc new file mode 100644 index 00000000..f0d23fe5 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/integration-reference.adoc @@ -0,0 +1,4 @@ +[[spring-integration-reference]] += Spring Integration - Reference + +This part of the reference documentation provides a quick introduction to the AMQP support within the Spring Integration project. \ No newline at end of file diff --git a/src/reference/antora/modules/ROOT/pages/introduction/index.adoc b/src/reference/antora/modules/ROOT/pages/introduction/index.adoc new file mode 100644 index 00000000..b88a71a6 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/introduction/index.adoc @@ -0,0 +1,5 @@ +[[introduction]] += Introduction + +This first part of the reference documentation is a high-level overview of Spring AMQP and the underlying concepts. +It includes some code snippets to get you up and running as quickly as possible. diff --git a/src/reference/asciidoc/quick-tour.adoc b/src/reference/antora/modules/ROOT/pages/introduction/quick-tour.adoc similarity index 93% rename from src/reference/asciidoc/quick-tour.adoc rename to src/reference/antora/modules/ROOT/pages/introduction/quick-tour.adoc index 75029ba7..21736104 100644 --- a/src/reference/asciidoc/quick-tour.adoc +++ b/src/reference/antora/modules/ROOT/pages/introduction/quick-tour.adoc @@ -1,7 +1,8 @@ [[quick-tour]] -=== Quick Tour for the impatient += Quick Tour for the impatient -==== Introduction +[[introduction]] +== Introduction This is the five-minute tour to get started with Spring AMQP. @@ -9,7 +10,6 @@ Prerequisites: Install and run the RabbitMQ broker (https://www.rabbitmq.com/dow Then grab the spring-rabbit JAR and all its dependencies - the easiest way to do so is to declare a dependency in your build tool. For example, for Maven, you can do something resembling the following: -==== [source,xml,subs="+attributes"] ---- @@ -18,19 +18,16 @@ For example, for Maven, you can do something resembling the following: {project-version} ---- -==== For Gradle, you can do something resembling the following: -==== [source,groovy,subs="+attributes"] ---- compile 'org.springframework.amqp:spring-rabbit:{project-version}' ---- -==== [[compatibility]] -===== Compatibility +=== Compatibility The minimum Spring Framework version dependency is 6.1.0. @@ -38,13 +35,13 @@ The minimum `amqp-client` Java client library version is 5.18.0. The minimum `stream-client` Java client library for stream queues is 0.12.0. -===== Very, Very Quick +[[very-very-quick]] +=== Very, Very Quick This section offers the fastest introduction. First, add the following `import` statements to make the examples later in this section work: -==== [source, java] ---- import org.springframework.amqp.core.AmqpAdmin; @@ -55,11 +52,9 @@ import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitAdmin; import org.springframework.amqp.rabbit.core.RabbitTemplate; ---- -==== The following example uses plain, imperative Java to send and receive a message: -==== [source,java] ---- ConnectionFactory connectionFactory = new CachingConnectionFactory(); @@ -69,7 +64,6 @@ AmqpTemplate template = new RabbitTemplate(connectionFactory); template.convertAndSend("myqueue", "foo"); String foo = (String) template.receiveAndConvert("myqueue"); ---- -==== Note that there is also a `ConnectionFactory` in the native Java Rabbit client. We use the Spring abstraction in the preceding code. @@ -77,11 +71,11 @@ It caches channels (and optionally connections) for reuse. We rely on the default exchange in the broker (since none is specified in the send), and the default binding of all queues to the default exchange by their name (thus, we can use the queue name as a routing key in the send). Those behaviors are defined in the AMQP specification. -===== With XML Configuration +[[with-xml-configuration]] +=== With XML Configuration The following example is the same as the preceding example but externalizes the resource configuration to XML: -==== [source,java] ---- ApplicationContext context = @@ -111,18 +105,17 @@ String foo = (String) template.receiveAndConvert("myqueue"); ---- -==== By default, the `` declaration automatically looks for beans of type `Queue`, `Exchange`, and `Binding` and declares them to the broker on behalf of the user. As a result, you need not use that bean explicitly in the simple Java driver. There are plenty of options to configure the properties of the components in the XML schema. You can use auto-complete features of your XML editor to explore them and look at their documentation. -===== With Java Configuration +[[with-java-configuration]] +=== With Java Configuration The following example repeats the same example as the preceding example but with the external configuration defined in Java: -==== [source,java] ---- ApplicationContext context = @@ -157,13 +150,12 @@ public class RabbitConfiguration { } } ---- -==== -===== With Spring Boot Auto Configuration and an Async POJO Listener +[[with-spring-boot-auto-configuration-and-an-async-pojo-listener]] +=== With Spring Boot Auto Configuration and an Async POJO Listener Spring Boot automatically configures the infrastructure beans, as the following example shows: -==== [source, java] ---- @SpringBootApplication @@ -190,4 +182,3 @@ public class Application { } ---- -==== diff --git a/src/reference/asciidoc/logging.adoc b/src/reference/antora/modules/ROOT/pages/logging.adoc similarity index 93% rename from src/reference/asciidoc/logging.adoc rename to src/reference/antora/modules/ROOT/pages/logging.adoc index fe07bf4d..21b68553 100644 --- a/src/reference/asciidoc/logging.adoc +++ b/src/reference/antora/modules/ROOT/pages/logging.adoc @@ -1,5 +1,5 @@ [[logging]] -=== Logging Subsystem AMQP Appenders += Logging Subsystem AMQP Appenders The framework provides logging appenders for some popular logging subsystems: @@ -8,7 +8,8 @@ The framework provides logging appenders for some popular logging subsystems: The appenders are configured by using the normal mechanisms for the logging subsystem, available properties are specified in the following sections. -==== Common properties +[[common-properties]] +== Common properties The following properties are available with all appenders: @@ -34,7 +35,7 @@ See `declareExchange`. | applicationId | -| Application ID -- added to the routing key if the pattern includes `%X{applicationId}`. +| Application ID -- added to the routing key if the pattern includes `+%X{applicationId}+`. | senderPoolSize | 2 @@ -72,12 +73,12 @@ Retries are delayed as follows: `N ^ log(N)`, where `N` is the retry number. | useSsl | false | Whether to use SSL for the RabbitMQ connection. -See <> +See xref:amqp/connections.adoc#rabbitconnectionfactorybean-configuring-ssl[`RabbitConnectionFactoryBean` and Configuring SSL] | verifyHostname | true | Enable server hostname verification for TLS connections. -See <> +See xref:amqp/connections.adoc#rabbitconnectionfactorybean-configuring-ssl[`RabbitConnectionFactoryBean` and Configuring SSL] | sslAlgorithm | null @@ -165,11 +166,11 @@ Please note, the `JsonLayout` adds MDC into the message by default. |=== -==== Log4j 2 Appender +[[log4j-2-appender]] +== Log4j 2 Appender The following example shows how to configure a Log4j 2 appender: -==== [source, xml] ---- @@ -185,7 +186,6 @@ The following example shows how to configure a Log4j 2 appender: ---- -==== [IMPORTANT] ==== @@ -198,11 +198,11 @@ One way to do that is to set the system property `-Dlog4j2.enable.threadlocals=f If you use asynchronous publishing with the `ReusableLogEventFactory`, events have a high likelihood of being corrupted due to cross-talk. ==== -==== Logback Appender +[[logback-appender]] +== Logback Appender The following example shows how to configure a logback appender: -==== [source, xml] ---- @@ -222,7 +222,6 @@ The following example shows how to configure a logback appender: false ---- -==== Starting with version 1.7.1, the Logback `AmqpAppender` provides an `includeCallerData` option, which is `false` by default. Extracting caller data can be rather expensive, because the log event has to create a throwable and inspect it to determine the calling location. @@ -232,7 +231,8 @@ You can configure the appender to include caller data by setting the `includeCal Starting with version 2.0.0, the Logback `AmqpAppender` supports https://logback.qos.ch/manual/encoders.html[Logback encoders] with the `encoder` option. The `encoder` and `layout` options are mutually exclusive. -==== Customizing the Messages +[[customizing-the-messages]] +== Customizing the Messages By default, AMQP appenders populate the following message properties: @@ -255,7 +255,6 @@ Each of the appenders can be subclassed, letting you modify the messages before The following example shows how to customize log messages: -==== [source, java] ---- public class MyEnhancedAppender extends AmqpAppender { @@ -268,12 +267,10 @@ public class MyEnhancedAppender extends AmqpAppender { } ---- -==== Starting with 2.2.4, the log4j2 `AmqpAppender` can be extended using `@PluginBuilderFactory` and extending also `AmqpAppender.Builder` -==== [source, java] ---- @Plugin(name = "MyEnhancedAppender", category = "Core", elementType = "appender", printObject = true) @@ -305,21 +302,21 @@ public class MyEnhancedAppender extends AmqpAppender { } ---- -==== -==== Customizing the Client Properties +[[customizing-the-client-properties]] +== Customizing the Client Properties You can add custom client properties by adding either string properties or more complex properties. -===== Simple String Properties +[[simple-string-properties]] +=== Simple String Properties Each appender supports adding client properties to the RabbitMQ connection. The following example shows how to add a custom client property for logback: -==== [source, xml] ---- @@ -328,10 +325,8 @@ The following example shows how to add a custom client property for logback: ... ---- -==== .log4j2 -==== [source, xml] ---- @@ -343,20 +338,19 @@ The following example shows how to add a custom client property for logback: ---- -==== The properties are a comma-delimited list of `key:value` pairs. Keys and values cannot contain commas or colons. These properties appear on the RabbitMQ Admin UI when the connection is viewed. -===== Advanced Technique for Logback +[[advanced-technique-for-logback]] +=== Advanced Technique for Logback You can subclass the Logback appender. Doing so lets you modify the client connection properties before the connection is established. The following example shows how to do so: -==== [source, java] ---- public class MyEnhancedAppender extends AmqpAppender { @@ -374,14 +368,14 @@ public class MyEnhancedAppender extends AmqpAppender { } ---- -==== Then you can add `thing2` to logback.xml. For String properties such as those shown in the preceding example, the previous technique can be used. Subclasses allow for adding richer properties (such as adding a `Map` or numeric property). -==== Providing a Custom Queue Implementation +[[providing-a-custom-queue-implementation]] +== Providing a Custom Queue Implementation The `AmqpAppenders` use a `BlockingQueue` to asynchronously publish logging events to RabbitMQ. By default, a `LinkedBlockingQueue` is used. @@ -389,7 +383,6 @@ However, you can supply any kind of custom `BlockingQueue` implementation. The following example shows how to do so for Logback: -==== [source, java] ---- public class MyEnhancedAppender extends AmqpAppender { @@ -401,11 +394,9 @@ public class MyEnhancedAppender extends AmqpAppender { } ---- -==== The Log4j 2 appender supports using a https://logging.apache.org/log4j/2.x/manual/appenders.html#BlockingQueueFactory[`BlockingQueueFactory`], as the following example shows: -==== [source, xml] ---- @@ -416,4 +407,3 @@ The Log4j 2 appender supports using a https://logging.apache.org/log4j/2.x/manua ---- -==== diff --git a/src/reference/antora/modules/ROOT/pages/reference.adoc b/src/reference/antora/modules/ROOT/pages/reference.adoc new file mode 100644 index 00000000..247bbac9 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/reference.adoc @@ -0,0 +1,9 @@ +[[reference]] += Reference + +This part of the reference documentation details the various components that comprise Spring AMQP. +The xref:amqp.adoc[main chapter] covers the core classes to develop an AMQP application. +This part also includes a chapter about the xref:sample-apps.adoc[sample applications]. + + + diff --git a/src/reference/antora/modules/ROOT/pages/resources.adoc b/src/reference/antora/modules/ROOT/pages/resources.adoc new file mode 100644 index 00000000..f3720389 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/resources.adoc @@ -0,0 +1,6 @@ +[[resources]] += Other Resources + +In addition to this reference documentation, there exist a number of other resources that may help you learn about AMQP. + + diff --git a/src/reference/asciidoc/sample-apps.adoc b/src/reference/antora/modules/ROOT/pages/sample-apps.adoc similarity index 96% rename from src/reference/asciidoc/sample-apps.adoc rename to src/reference/antora/modules/ROOT/pages/sample-apps.adoc index b75dc601..188a097b 100644 --- a/src/reference/asciidoc/sample-apps.adoc +++ b/src/reference/antora/modules/ROOT/pages/sample-apps.adoc @@ -1,5 +1,5 @@ [[sample-apps]] -=== Sample Applications += Sample Applications The https://github.com/SpringSource/spring-amqp-samples[Spring AMQP Samples] project includes two sample applications. The first is a simple "`Hello World`" example that demonstrates both synchronous and asynchronous message reception. @@ -9,13 +9,13 @@ In this chapter, we provide a quick walk-through of each sample so that you can The samples are both Maven-based, so you should be able to import them directly into any Maven-aware IDE (such as https://www.springsource.org/sts[SpringSource Tool Suite]). [[hello-world-sample]] -==== The "`Hello World`" Sample +== The "`Hello World`" Sample The "`Hello World`" sample demonstrates both synchronous and asynchronous message reception. You can import the `spring-rabbit-helloworld` sample into the IDE and then follow the discussion below. [[hello-world-sync]] -===== Synchronous Example +=== Synchronous Example Within the `src/main/java` directory, navigate to the `org.springframework.amqp.helloworld` package. Open the `HelloWorldConfiguration` class and notice that it contains the `@Configuration` annotation at the class level and notice some `@Bean` annotations at method-level. @@ -24,7 +24,6 @@ You can read more about that https://docs.spring.io/spring/docs/current/spring-f The following listing shows how the connection factory is created: -==== [source,java] ---- @Bean @@ -36,14 +35,12 @@ public CachingConnectionFactory connectionFactory() { return connectionFactory; } ---- -==== The configuration also contains an instance of `RabbitAdmin`, which, by default, looks for any beans of type exchange, queue, or binding and then declares them on the broker. In fact, the `helloWorldQueue` bean that is generated in `HelloWorldConfiguration` is an example because it is an instance of `Queue`. The following listing shows the `helloWorldQueue` bean definition: -==== [source,java] ---- @Bean @@ -51,7 +48,6 @@ public Queue helloWorldQueue() { return new Queue(this.helloWorldQueueName); } ---- -==== Looking back at the `rabbitTemplate` bean configuration, you can see that it has the name of `helloWorldQueue` set as its `queue` property (for receiving messages) and for its `routingKey` property (for sending messages). @@ -61,7 +57,6 @@ It contains a `main()` method where the Spring `ApplicationContext` is created. The following listing shows the `main` method: -==== [source,java] ---- public static void main(String[] args) { @@ -72,7 +67,6 @@ public static void main(String[] args) { System.out.println("Sent: Hello World"); } ---- -==== In the preceding example, the `AmqpTemplate` bean is retrieved and used for sending a `Message`. Since the client code should rely on interfaces whenever possible, the type is `AmqpTemplate` rather than `RabbitTemplate`. @@ -83,12 +77,11 @@ In this case, it uses the default `SimpleMessageConverter`, but a different impl Now open the `Consumer` class. It actually shares the same configuration base class, which means it shares the `rabbitTemplate` bean. That is why we configured that template with both a `routingKey` (for sending) and a `queue` (for receiving). -As we describe in <>, you could instead pass the 'routingKey' argument to the send method and the 'queue' argument to the receive method. +As we describe in xref:amqp/template.adoc[`AmqpTemplate`], you could instead pass the 'routingKey' argument to the send method and the 'queue' argument to the receive method. The `Consumer` code is basically a mirror image of the Producer, calling `receiveAndConvert()` rather than `convertAndSend()`. The following listing shows the main method for the `Consumer`: -==== [source,java] ---- public static void main(String[] args) { @@ -98,14 +91,13 @@ public static void main(String[] args) { System.out.println("Received: " + amqpTemplate.receiveAndConvert()); } ---- -==== If you run the `Producer` and then run the `Consumer`, you should see `Received: Hello World` in the console output. [[hello-world-async]] -===== Asynchronous Example +=== Asynchronous Example -<> walked through the synchronous Hello World sample. +xref:sample-apps.adoc#hello-world-sync[Synchronous Example] walked through the synchronous Hello World sample. This section describes a slightly more advanced but significantly more powerful option. With a few modifications, the Hello World sample can provide an example of asynchronous reception, also known as message-driven POJOs. In fact, there is a sub-package that provides exactly that: `org.springframework.amqp.samples.helloworld.async`. @@ -120,7 +112,6 @@ That is why we only need to provide the routing key here. The following listing shows the `rabbitTemplate` definition: -==== [source,java] ---- public RabbitTemplate rabbitTemplate() { @@ -129,7 +120,6 @@ public RabbitTemplate rabbitTemplate() { return template; } ---- -==== Since this sample demonstrates asynchronous message reception, the producing side is designed to continuously send messages (if it were a message-per-execution model like the synchronous version, it would not be quite so obvious that it is, in fact, a message-driven consumer). The component responsible for continuously sending messages is defined as an inner class within the `ProducerConfiguration`. @@ -137,7 +127,6 @@ It is configured to run every three seconds. The following listing shows the component: -==== [source,java] ---- static class ScheduledProducer { @@ -153,7 +142,6 @@ static class ScheduledProducer { } } ---- -==== You do not need to understand all of the details, since the real focus should be on the receiving side (which we cover next). However, if you are not yet familiar with Spring task scheduling support, you can learn more https://docs.spring.io/spring/docs/current/spring-framework-reference/html/scheduling.html#scheduling-annotation-support[here]. @@ -163,7 +151,6 @@ Now we can turn to the receiving side. To emphasize the message-driven POJO behavior, we start with the component that react to the messages. The class is called `HelloWorldHandler` and is shown in the following listing: -==== [source,java] ---- public class HelloWorldHandler { @@ -174,7 +161,6 @@ public class HelloWorldHandler { } ---- -==== That class is a POJO. It does not extend any base class, it does not implement any interfaces, and it does not even contain any imports. @@ -185,7 +171,6 @@ You can see the POJO wrapped in the adapter there. The following listing shows how the `listenerContainer` is defined: -==== [source,java] ---- @Bean @@ -197,16 +182,16 @@ public SimpleMessageListenerContainer listenerContainer() { return container; } ---- -==== The `SimpleMessageListenerContainer` is a Spring lifecycle component and, by default, starts automatically. If you look in the `Consumer` class, you can see that its `main()` method consists of nothing more than a one-line bootstrap to create the `ApplicationContext`. The Producer's `main()` method is also a one-line bootstrap, since the component whose method is annotated with `@Scheduled` also starts automatically. You can start the `Producer` and `Consumer` in any order, and you should see messages being sent and received every three seconds. -==== Stock Trading +[[stock-trading]] +== Stock Trading -The Stock Trading sample demonstrates more advanced messaging scenarios than <>. +The Stock Trading sample demonstrates more advanced messaging scenarios than xref:sample-apps.adoc#hello-world-sample[the Hello World sample]. However, the configuration is very similar, if a bit more involved. Since we walked through the Hello World configuration in detail, here, we focus on what makes this sample different. There is a server that pushes market data (stock quotations) to a topic exchange. @@ -225,21 +210,18 @@ First, it configures the market data exchange on the `RabbitTemplate` so that it It does this within an abstract callback method defined in the base configuration class. The following listing shows that method: -==== [source,java] ---- public void configureRabbitTemplate(RabbitTemplate rabbitTemplate) { rabbitTemplate.setExchange(MARKET_DATA_EXCHANGE_NAME); } ---- -==== Second, the stock request queue is declared. It does not require any explicit bindings in this case, because it is bound to the default no-name exchange with its own name as the routing key. As mentioned earlier, the AMQP specification defines that behavior. The following listing shows the definition of the `stockRequestQueue` bean: -==== [source,java] ---- @Bean @@ -247,7 +229,6 @@ public Queue stockRequestQueue() { return new Queue(STOCK_REQUEST_QUEUE_NAME); } ---- -==== Now that you have seen the configuration of the server's AMQP resources, navigate to the `org.springframework.amqp.rabbit.stocks` package under the `src/test/java` directory. There, you can see the actual `Server` class that provides a `main()` method. @@ -263,20 +244,17 @@ Notice that it is not itself coupled to the framework or any of the AMQP concept It accepts a `TradeRequest` and returns a `TradeResponse`. The following listing shows the definition of the `handleMessage` method: -==== [source,java] ---- public TradeResponse handleMessage(TradeRequest tradeRequest) { ... } ---- -==== Now that we have seen the most important configuration and code for the server, we can turn to the client. The best starting point is probably `RabbitClientConfiguration`, in the `org.springframework.amqp.rabbit.stocks.config.client` package. Notice that it declares two queues without providing explicit names. The following listing shows the bean definitions for the two queues: -==== [source,java] ---- @Bean @@ -289,7 +267,6 @@ public Queue traderJoeQueue() { return amqpAdmin().declareQueue(); } ---- -==== Those are private queues, and unique names are generated automatically. The first generated queue is used by the client to bind to the market data exchange that has been exposed by the server. @@ -299,7 +276,6 @@ Since the market data exchange is a topic exchange, the binding can be expressed The `RabbitClientConfiguration` does so with a `Binding` object, and that object is generated with the `BindingBuilder` fluent API. The following listing shows the `Binding`: -==== [source,java] ---- @Value("${stocks.quote.pattern}") @@ -311,7 +287,6 @@ public Binding marketDataBinding() { marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey); } ---- -==== Notice that the actual value has been externalized in a properties file (`client.properties` under `src/main/resources`), and that we use Spring's `@Value` annotation to inject that value. This is generally a good idea. @@ -331,7 +306,6 @@ The corresponding code on the `Client` side is `RabbitStockServiceGateway` in th It delegates to the `RabbitTemplate` in order to send messages. The following listing shows the `send` method: -==== [source,java] ---- public void send(TradeRequest tradeRequest) { @@ -350,13 +324,11 @@ public void send(TradeRequest tradeRequest) { }); } ---- -==== Notice that, prior to sending the message, it sets the `replyTo` address. It provides the queue that was generated by the `traderJoeQueue` bean definition (shown earlier). The following listing shows the `@Bean` definition for the `StockServiceGateway` class itself: -==== [source,java] ---- @Bean @@ -367,14 +339,13 @@ public StockServiceGateway stockServiceGateway() { return gateway; } ---- -==== If you are no longer running the server and client, start them now. Try sending a request with the format of '100 TCKR'. After a brief artificial delay that simulates "`processing`" of the request, you should see a confirmation message appear on the client. [[spring-rabbit-json]] -==== Receiving JSON from Non-Spring Applications +== Receiving JSON from Non-Spring Applications Spring applications, when sending JSON, set the `__TypeId__` header to the fully qualified class name to assist the receiving application in converting the JSON back to a Java object. diff --git a/src/reference/asciidoc/si-amqp.adoc b/src/reference/antora/modules/ROOT/pages/si-amqp.adoc similarity index 92% rename from src/reference/asciidoc/si-amqp.adoc rename to src/reference/antora/modules/ROOT/pages/si-amqp.adoc index c52c40c6..02d5fbf7 100644 --- a/src/reference/asciidoc/si-amqp.adoc +++ b/src/reference/antora/modules/ROOT/pages/si-amqp.adoc @@ -1,10 +1,10 @@ [[spring-integration-amqp]] -=== Spring Integration AMQP Support += Spring Integration AMQP Support This brief chapter covers the relationship between the Spring Integration and the Spring AMQP projects. [[spring-integration-amqp-introduction]] -==== Introduction +== Introduction The https://www.springsource.org/spring-integration[Spring Integration] project includes AMQP Channel Adapters and Gateways that build upon the Spring AMQP project. Those adapters are developed and released in the Spring Integration project. @@ -15,27 +15,26 @@ Since the AMQP adapters are part of the Spring Integration release, the document We provide a quick overview of the main features here. See the https://docs.spring.io/spring-integration/reference/htmlsingle/[Spring Integration Reference Guide] for much more detail. -==== Inbound Channel Adapter +[[inbound-channel-adapter]] +== Inbound Channel Adapter To receive AMQP Messages from a queue, you can configure an ``. The following example shows how to configure an inbound channel adapter: -==== [source,xml] ---- ---- -==== -==== Outbound Channel Adapter +[[outbound-channel-adapter]] +== Outbound Channel Adapter To send AMQP Messages to an exchange, you can configure an ``. You can optionally provide a 'routing-key' in addition to the exchange name. The following example shows how to define an outbound channel adapter: -==== [source,xml] ---- ---- -==== -==== Inbound Gateway +[[inbound-gateway]] +== Inbound Gateway To receive an AMQP Message from a queue and respond to its reply-to address, you can configure an ``. The following example shows how to define an inbound gateway: -==== [source,xml] ---- ---- -==== -==== Outbound Gateway +[[outbound-gateway]] +== Outbound Gateway To send AMQP Messages to an exchange and receive back a response from a remote client, you can configure an ``. You can optionally provide a 'routing-key' in addition to the exchange name. The following example shows how to define an outbound gateway: -==== [source,xml] ---- ---- -==== diff --git a/src/reference/asciidoc/stream.adoc b/src/reference/antora/modules/ROOT/pages/stream.adoc similarity index 92% rename from src/reference/asciidoc/stream.adoc rename to src/reference/antora/modules/ROOT/pages/stream.adoc index a53c2e48..52cbc6dc 100644 --- a/src/reference/asciidoc/stream.adoc +++ b/src/reference/antora/modules/ROOT/pages/stream.adoc @@ -1,5 +1,5 @@ [[stream-support]] -=== Using the RabbitMQ Stream Plugin += Using the RabbitMQ Stream Plugin Version 2.4 introduces initial support for the https://github.com/rabbitmq/rabbitmq-stream-java-client[RabbitMQ Stream Plugin Java Client] for the https://rabbitmq.com/stream.html[RabbitMQ Stream Plugin]. @@ -9,7 +9,6 @@ Version 2.4 introduces initial support for the https://github.com/rabbitmq/rabbi Add the `spring-rabbit-stream` dependency to your project: .maven -==== [source,xml,subs="+attributes"] ---- @@ -18,35 +17,29 @@ Add the `spring-rabbit-stream` dependency to your project: {project-version} ---- -==== .gradle -==== [source,groovy,subs="+attributes"] ---- compile 'org.springframework.amqp:spring-rabbit-stream:{project-version}' ---- -==== You can provision the queues as normal, using a `RabbitAdmin` bean, using the `QueueBuilder.stream()` method to designate the queue type. For example: -==== [source, java] ---- @Bean Queue stream() { return QueueBuilder.durable("stream.queue1") .stream() - .build(); + .build(); } ---- -==== However, this will only work if you are also using non-stream components (such as the `SimpleMessageListenerContainer` or `DirectMessageListenerContainer`) because the admin is triggered to declare the defined beans when an AMQP connection is opened. If your application only uses stream components, or you wish to use advanced stream configuration features, you should configure a `StreamAdmin` instead: -==== [source, java] ---- @Bean @@ -57,16 +50,15 @@ StreamAdmin streamAdmin(Environment env) { }); } ---- -==== Refer to the RabbitMQ documentation for more information about the `StreamCreator`. -==== Sending Messages +[[sending-messages]] +== Sending Messages The `RabbitStreamTemplate` provides a subset of the `RabbitTemplate` (AMQP) functionality. .RabbitStreamOperations -==== [source, java] ---- public interface RabbitStreamOperations extends AutoCloseable { @@ -90,12 +82,10 @@ public interface RabbitStreamOperations extends AutoCloseable { } ---- -==== The `RabbitStreamTemplate` implementation has the following constructor and properties: .RabbitStreamTemplate -==== [source, java] ---- public RabbitStreamTemplate(Environment environment, String streamName) { @@ -110,7 +100,6 @@ public void setStreamConverter(StreamMessageConverter streamConverter) { public synchronized void setProducerCustomizer(ProducerCustomizer producerCustomizer) { } ---- -==== The `MessageConverter` is used in the `convertAndSend` methods to convert the object to a Spring AMQP `Message`. @@ -124,7 +113,8 @@ Refer to the https://rabbitmq.github.io/rabbitmq-stream-java-client/stable/htmls IMPORTANT: Starting with version 3.0, the method return types are `CompletableFuture` instead of `ListenableFuture`. -==== Receiving Messages +[[receiving-messages]] +== Receiving Messages Asynchronous message reception is provided by the `StreamListenerContainer` (and the `StreamRabbitListenerContainerFactory` when using `@RabbitListener`). @@ -132,7 +122,6 @@ The listener container requires an `Environment` as well as a single stream name You can either receive Spring AMQP `Message` s using the classic `MessageListener`, or you can receive native stream `Message` s using a new interface: -==== [source, java] ---- public interface StreamMessageListener extends MessageListener { @@ -141,9 +130,8 @@ public interface StreamMessageListener extends MessageListener { } ---- -==== -See <> for information about supported properties. +See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration] for information about supported properties. Similar the template, the container has a `ConsumerCustomizer` property. @@ -153,9 +141,8 @@ When using `@RabbitListener`, configure a `StreamRabbitListenerContainerFactory` In addition, `queues` can only contain one stream name. [[stream-examples]] -==== Examples +== Examples -==== [source, java] ---- @Bean @@ -197,22 +184,20 @@ void nativeMsg(Message in, Context context) { Queue stream() { return QueueBuilder.durable("test.stream.queue1") .stream() - .build(); + .build(); } @Bean Queue stream() { return QueueBuilder.durable("test.stream.queue2") .stream() - .build(); + .build(); } ---- -==== Version 2.4.5 added the `adviceChain` property to the `StreamListenerContainer` (and its factory). A new factory bean is also provided to create a stateless retry interceptor with an optional `StreamMessageRecoverer` for use when consuming raw stream messages. -==== [source, java] ---- @Bean @@ -226,19 +211,19 @@ public StreamRetryOperationsInterceptorFactoryBean sfb(RetryTemplate retryTempla return rfb; } ---- -==== IMPORTANT: Stateful retry is not supported with this container. -==== Super Streams +[[super-streams]] +== Super Streams A Super Stream is an abstract concept for a partitioned stream, implemented by binding a number of stream queues to an exchange having an argument `x-super-stream: true`. -===== Provisioning +[[provisioning]] +=== Provisioning For convenience, a super stream can be provisioned by defining a single bean of type `SuperStream`. -==== [source, java] ---- @Bean @@ -246,13 +231,11 @@ SuperStream superStream() { return new SuperStream("my.super.stream", 3); } ---- -==== The `RabbitAdmin` detects this bean and will declare the exchange (`my.super.stream`) and 3 queues (partitions) - `my.super-stream-n` where `n` is `0`, `1`, `2`, bound with routing keys equal to `n`. If you also wish to publish over AMQP to the exchange, you can provide custom routing keys: -==== [source, java] ---- @Bean @@ -262,15 +245,14 @@ SuperStream superStream() { .collect(Collectors.toList())); } ---- -==== The number of keys must equal the number of partitions. -===== Producing to a SuperStream +[[producing-to-a-superstream]] +=== Producing to a SuperStream You must add a `superStreamRoutingFunction` to the `RabbitStreamTemplate`: -==== [source, java] ---- @Bean @@ -282,16 +264,14 @@ RabbitStreamTemplate streamTemplate(Environment env) { return template; } ---- -==== You can also publish over AMQP, using the `RabbitTemplate`. [[super-stream-consumer]] -===== Consuming Super Streams with Single Active Consumers +=== Consuming Super Streams with Single Active Consumers Invoke the `superStream` method on the listener container to enable a single active consumer on a super stream. -==== [source, java] ---- @Bean @@ -305,18 +285,17 @@ StreamListenerContainer container(Environment env, String name) { return container; } ---- -==== IMPORTANT: At this time, when the concurrency is greater than 1, the actual concurrency is further controlled by the `Environment`; to achieve full concurrency, set the environment's `maxConsumersByConnection` to 1. See https://rabbitmq.github.io/rabbitmq-stream-java-client/snapshot/htmlsingle/#configuring-the-environment[Configuring the Environment]. [[stream-micrometer-observation]] -==== Micrometer Observation +== Micrometer Observation Using Micrometer for observation is now supported, since version 3.0.5, for the `RabbitStreamTemplate` and the stream listener container. The container now also supports Micrometer timers (when observation is not enabled). -Set `observationEnabled` on each component to enable observation; this will disable <> because the timers will now be managed with each observation. +Set `observationEnabled` on each component to enable observation; this will disable xref:amqp/receiving-messages/micrometer.adoc[Micrometer Timers] because the timers will now be managed with each observation. When using annotated listeners, set `observationEnabled` on the container factory. Refer to https://micrometer.io/docs/tracing[Micrometer Tracing] for more information. @@ -327,4 +306,4 @@ The default implementations add the `name` tag for template observations and `li You can either subclass `DefaultRabbitStreamTemplateObservationConvention` or `DefaultStreamRabbitListenerObservationConvention` or provide completely new implementations. -See <> for more details. +See xref:appendix/micrometer.adoc[Micrometer Observation Documentation] for more details. diff --git a/src/reference/asciidoc/testing.adoc b/src/reference/antora/modules/ROOT/pages/testing.adoc similarity index 94% rename from src/reference/asciidoc/testing.adoc rename to src/reference/antora/modules/ROOT/pages/testing.adoc index feec38c8..96e84abc 100644 --- a/src/reference/asciidoc/testing.adoc +++ b/src/reference/antora/modules/ROOT/pages/testing.adoc @@ -1,5 +1,5 @@ [[testing]] -=== Testing Support += Testing Support Writing integration for asynchronous applications is necessarily more complex than testing simpler applications. This is made more complex when abstractions such as the `@RabbitListener` annotations come into the picture. @@ -14,7 +14,7 @@ It is anticipated that this project will expand over time, but we need community Please use https://jira.spring.io/browse/AMQP[JIRA] or https://github.com/spring-projects/spring-amqp/issues[GitHub Issues] to provide such feedback. [[spring-rabbit-test]] -==== @SpringRabbitTest +== @SpringRabbitTest Use this annotation to add infrastructure beans to the Spring test `ApplicationContext`. This is not necessary when using, for example `@SpringBootTest` since Spring Boot's auto configuration will add the beans. @@ -29,7 +29,6 @@ Beans that are registered are: In addition, the beans associated with `@EnableRabbit` (to support `@RabbitListener`) are added. .Junit5 example -==== [source, java] ---- @SpringJunitConfig @@ -59,19 +58,17 @@ public class MyRabbitTests { } ---- -==== With JUnit4, replace `@SpringJunitConfig` with `@RunWith(SpringRunnner.class)`. [[mockito-answer]] -==== Mockito `Answer` Implementations +== Mockito `Answer` Implementations There are currently two `Answer` implementations to help with testing. The first, `LatchCountDownAndCallRealMethodAnswer`, provides an `Answer` that returns `null` and counts down a latch. The following example shows how to use `LatchCountDownAndCallRealMethodAnswer`: -==== [source, java] ---- LatchCountDownAndCallRealMethodAnswer answer = this.harness.getLatchAnswerFor("myListener", 2); @@ -82,14 +79,12 @@ doAnswer(answer) assertThat(answer.await(10)).isTrue(); ---- -==== The second, `LambdaAnswer` provides a mechanism to optionally call the real method and provides an opportunity to return a custom result, based on the `InvocationOnMock` and the result (if any). Consider the following POJO: -==== [source, java] ---- public class Thing { @@ -100,11 +95,9 @@ public class Thing { } ---- -==== The following class tests the `Thing` POJO: -==== [source, java] ---- Thing thing = spy(new Thing()); @@ -121,15 +114,14 @@ doAnswer(new LambdaAnswer(false, (i, r) -> "" + i.getArguments()[0] + i.getArguments()[0])).when(thing).thing(anyString()); assertEquals("thingthing", thing.thing("thing")); ---- -==== Starting with version 2.2.3, the answers capture any exceptions thrown by the method under test. Use `answer.getExceptions()` to get a reference to them. -When used in conjunction with the <> use `harness.getLambdaAnswerFor("listenerId", true, ...)` to get a properly constructed answer for the listener. +When used in conjunction with the xref:testing.adoc#test-harness[`@RabbitListenerTest` and `RabbitListenerTestHarness`] use `harness.getLambdaAnswerFor("listenerId", true, ...)` to get a properly constructed answer for the listener. [[test-harness]] -==== `@RabbitListenerTest` and `RabbitListenerTestHarness` +== `@RabbitListenerTest` and `RabbitListenerTestHarness` Annotating one of your `@Configuration` classes with `@RabbitListenerTest` causes the framework to replace the standard `RabbitListenerAnnotationBeanPostProcessor` with a subclass called `RabbitListenerTestHarness` (it also enables @@ -149,7 +141,6 @@ Consider some examples. The following example uses spy: -==== [source, java] ---- @Configuration @@ -224,11 +215,9 @@ We use one of the link:#mockito-answer[Answer] implementations to help with t IMPORTANT: Due to the way the listener is spied, it is important to use `harness.getLatchAnswerFor()` to get a properly configured answer for the spy. <4> Configure the spy to invoke the `Answer`. -==== The following example uses the capture advice: -==== [source, java] ---- @Configuration @@ -311,13 +300,12 @@ for the result. to suspend the test thread. <5> When the listener throws an exception, it is available in the `throwable` property of the invocation data. -==== IMPORTANT: When using custom `Answer` s with the harness, in order to operate properly, such answers should subclass `ForwardsInvocation` and get the actual listener (not the spy) from the harness (`getDelegate("myListener")`) and call `super.answer(invocation)`. -See the provided <> source code for examples. +See the provided xref:testing.adoc#mockito-answer[Mockito `Answer` Implementations] source code for examples. [[test-template]] -==== Using `TestRabbitTemplate` +== Using `TestRabbitTemplate` The `TestRabbitTemplate` is provided to perform some basic integration testing without the need for a broker. When you add it as a `@Bean` in your test case, it discovers all the listener containers in the context, whether declared as `@Bean` or `` or using the `@RabbitListener` annotation. @@ -327,7 +315,6 @@ Request-reply messaging (`sendAndReceive` methods) is supported for listeners th The following test case uses the template: -==== [source, java] ---- @RunWith(SpringRunner.class) @@ -435,16 +422,16 @@ public class TestRabbitTemplateTests { } ---- -==== [[junit-rules]] -==== JUnit4 `@Rules` +== JUnit4 `@Rules` Spring AMQP version 1.7 and later provide an additional jar called `spring-rabbit-junit`. This jar contains a couple of utility `@Rule` instances for use when running JUnit4 tests. -See <> for JUnit5 testing. +See xref:testing.adoc#junit5-conditions[JUnit5 Conditions] for JUnit5 testing. -===== Using `BrokerRunning` +[[using-brokerrunning]] +=== Using `BrokerRunning` `BrokerRunning` provides a mechanism to let tests succeed when a broker is not running (on `localhost`, by default). @@ -452,7 +439,6 @@ It also has utility methods to initialize and empty queues and delete queues and The following example shows its usage: -==== [source, java] ---- @@ -464,12 +450,11 @@ public static void tearDown() { brokerRunning.removeTestQueues("some.other.queue.too") // removes foo, bar as well } ---- -==== There are several `isRunning...` static methods, such as `isBrokerAndManagementRunning()`, which verifies the broker has the management plugin enabled. [[brokerRunning-configure]] -====== Configuring the Rule +==== Configuring the Rule There are times when you want tests to fail if there is no broker, such as a nightly CI build. To disable the rule at runtime, set an environment variable called `RABBITMQ_SERVER_REQUIRED` to `true`. @@ -478,7 +463,6 @@ You can override the broker properties, such as hostname with either setters or The following example shows how to override properties with setters: -==== [source, java] ---- @@ -494,11 +478,9 @@ public static void tearDown() { brokerRunning.removeTestQueues("some.other.queue.too") // removes foo, bar as well } ---- -==== You can also override properties by setting the following environment variables: -==== [source, java] ---- public static final String BROKER_ADMIN_URI = "RABBITMQ_TEST_ADMIN_URI"; @@ -509,7 +491,6 @@ public static final String BROKER_PW = "RABBITMQ_TEST_PASSWORD"; public static final String BROKER_ADMIN_USER = "RABBITMQ_TEST_ADMIN_USER"; public static final String BROKER_ADMIN_PW = "RABBITMQ_TEST_ADMIN_PASSWORD"; ---- -==== These environment variables override the default settings (`localhost:5672` for amqp and `http://localhost:15672/api/` for the management REST API). @@ -525,7 +506,6 @@ Invoke `clearEnvironmentVariableOverrides()` to reset the rule to use defaults ( In your test cases, you can use the `brokerRunning` when creating the connection factory; `getConnectionFactory()` returns the rule's RabbitMQ `ConnectionFactory`. The following example shows how to do so: -==== [source, java] ---- @Bean @@ -533,31 +513,30 @@ public CachingConnectionFactory rabbitConnectionFactory() { return new CachingConnectionFactory(brokerRunning.getConnectionFactory()); } ---- -==== -===== Using `LongRunningIntegrationTest` +[[using-longrunningintegrationtest]] +=== Using `LongRunningIntegrationTest` `LongRunningIntegrationTest` is a rule that disables long running tests. You might want to use this on a developer system but ensure that the rule is disabled on, for example, nightly CI builds. The following example shows its usage: -==== [source, java] ---- @Rule public LongRunningIntegrationTest longTests = new LongRunningIntegrationTest(); ---- -==== To disable the rule at runtime, set an environment variable called `RUN_LONG_INTEGRATION_TESTS` to `true`. [[junit5-conditions]] -==== JUnit5 Conditions +== JUnit5 Conditions Version 2.0.2 introduced support for JUnit5. -===== Using the `@RabbitAvailable` Annotation +[[using-the-rabbitavailable-annotation]] +=== Using the `@RabbitAvailable` Annotation This class-level annotation is similar to the `BrokerRunning` `@Rule` discussed in <>. It is processed by the `RabbitAvailableCondition`. @@ -569,8 +548,8 @@ The annotation has three properties: * `purgeAfterEach`: (Since version 2.2) when `true` (default), the `queues` will be purged between tests. It is used to check whether the broker is available and skip the tests if not. -As discussed in <>, the environment variable called `RABBITMQ_SERVER_REQUIRED`, if `true`, causes the tests to fail fast if there is no broker. -You can configure the condition by using environment variables as discussed in <>. +As discussed in xref:testing.adoc#brokerRunning-configure[Configuring the Rule], the environment variable called `RABBITMQ_SERVER_REQUIRED`, if `true`, causes the tests to fail fast if there is no broker. +You can configure the condition by using environment variables as discussed in xref:testing.adoc#brokerRunning-configure[Configuring the Rule]. In addition, the `RabbitAvailableCondition` supports argument resolution for parameterized test constructors and methods. Two argument types are supported: @@ -580,7 +559,6 @@ Two argument types are supported: The following example shows both: -==== [source, java] ---- @RabbitAvailable(queues = "rabbitAvailableTests.queue") @@ -605,13 +583,11 @@ public class RabbitAvailableCTORInjectionTests { } ---- -==== The preceding test is in the framework itself and verifies the argument injection and that the condition created the queue properly. A practical user test might be as follows: -==== [source, java] ---- @RabbitAvailable(queues = "rabbitAvailableTests.queue") @@ -631,7 +607,6 @@ public class RabbitAvailableCTORInjectionTests { } } ---- -==== When you use a Spring annotation application context within a test class, you can get a reference to the condition's connection factory through a static method called `RabbitAvailableCondition.getBrokerRunning()`. @@ -640,7 +615,6 @@ The new class has the same API as `BrokerRunning`. The following test comes from the framework and demonstrates the usage: -==== [source, java] ---- @RabbitAvailable(queues = { @@ -702,14 +676,13 @@ public class RabbitTemplateMPPIntegrationTests { } ---- -==== -===== Using the `@LongRunning` Annotation +[[using-the-longrunning-annotation]] +=== Using the `@LongRunning` Annotation Similar to the `LongRunningIntegrationTest` JUnit4 `@Rule`, this annotation causes tests to be skipped unless an environment variable (or system property) is set to `true`. The following example shows how to use it: -==== [source, java] ---- @RabbitAvailable(queues = SimpleMessageListenerContainerLongTests.QUEUE) @@ -722,6 +695,5 @@ public class SimpleMessageListenerContainerLongTests { } ---- -==== By default, the variable is `RUN_LONG_INTEGRATION_TESTS`, but you can specify the variable name in the annotation's `value` attribute. diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/antora/modules/ROOT/pages/whats-new.adoc similarity index 67% rename from src/reference/asciidoc/whats-new.adoc rename to src/reference/antora/modules/ROOT/pages/whats-new.adoc index 23218ec7..ff9eb62a 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/antora/modules/ROOT/pages/whats-new.adoc @@ -1,17 +1,20 @@ [[whats-new]] -== What's New += What's New +:page-section-summary-toc: 1 -=== Changes in 3.1 Since 3.0 +[[changes-in-3-1-since-3-0]] +== Changes in 3.1 Since 3.0 -==== Java 17, Spring Framework 6.1 +[[java-17-spring-framework-6-1]] +=== Java 17, Spring Framework 6.1 This version requires Spring Framework 6.1 and Java 17. [[x31-exc]] -==== Exclusive Consumer Logging +=== Exclusive Consumer Logging Log messages reporting access refusal due to exclusive consumers are now logged at DEBUG level by default. It remains possible to configure your own logging behavior by setting the `exclusiveConsumerExceptionLogger` and `closeExceptionLogger` properties on the listener container and connection factory respectively. In addition, the `SimpleMessageListenerContainer` consumer restart after such an exception is now logged at DEBUG level by default (previously INFO). A new method `logRestart()` has been added to the `ConditionalExceptionLogger` to allow this to be changed. -See <> and <> for more information. +See xref:amqp/receiving-messages/consumer-events.adoc[Consumer Events] and <> for more information. diff --git a/src/reference/asciidoc/amqp.adoc b/src/reference/asciidoc/amqp.adoc deleted file mode 100644 index 879cf2a0..00000000 --- a/src/reference/asciidoc/amqp.adoc +++ /dev/null @@ -1,7059 +0,0 @@ -[[amqp]] -=== Using Spring AMQP - -This chapter explores the interfaces and classes that are the essential components for developing applications with Spring AMQP. - -==== AMQP Abstractions - -Spring AMQP consists of two modules (each represented by a JAR in the distribution): `spring-amqp` and `spring-rabbit`. -The 'spring-amqp' module contains the `org.springframework.amqp.core` package. -Within that package, you can find the classes that represent the core AMQP "`model`". -Our intention is to provide generic abstractions that do not rely on any particular AMQP broker implementation or client library. -End user code can be more portable across vendor implementations as it can be developed against the abstraction layer only. -These abstractions are then implemented by broker-specific modules, such as 'spring-rabbit'. -There is currently only a RabbitMQ implementation. -However, the abstractions have been validated in .NET using Apache Qpid in addition to RabbitMQ. -Since AMQP operates at the protocol level, in principle, you can use the RabbitMQ client with any broker that supports the same protocol version, but we do not test any other brokers at present. - -This overview assumes that you are already familiar with the basics of the AMQP specification. -If not, have a look at the resources listed in <> - -===== `Message` - -The 0-9-1 AMQP specification does not define a `Message` class or interface. -Instead, when performing an operation such as `basicPublish()`, the content is passed as a byte-array argument and additional properties are passed in as separate arguments. -Spring AMQP defines a `Message` class as part of a more general AMQP domain model representation. -The purpose of the `Message` class is to encapsulate the body and properties within a single instance so that the API can, in turn, be simpler. -The following example shows the `Message` class definition: - -==== -[source,java] ----- -public class Message { - - private final MessageProperties messageProperties; - - private final byte[] body; - - public Message(byte[] body, MessageProperties messageProperties) { - this.body = body; - this.messageProperties = messageProperties; - } - - public byte[] getBody() { - return this.body; - } - - public MessageProperties getMessageProperties() { - return this.messageProperties; - } -} ----- -==== - -The `MessageProperties` interface defines several common properties, such as 'messageId', 'timestamp', 'contentType', and several more. -You can also extend those properties with user-defined 'headers' by calling the `setHeader(String key, Object value)` method. - -IMPORTANT: Starting with versions `1.5.7`, `1.6.11`, `1.7.4`, and `2.0.0`, if a message body is a serialized `Serializable` java object, it is no longer deserialized (by default) when performing `toString()` operations (such as in log messages). -This is to prevent unsafe deserialization. -By default, only `java.util` and `java.lang` classes are deserialized. -To revert to the previous behavior, you can add allowable class/package patterns by invoking `Message.addAllowedListPatterns(...)`. -A simple `*` wildcard is supported, for example `com.something.*, *.MyClass`. -Bodies that cannot be deserialized are represented by `byte[]` in log messages. - -===== Exchange - -The `Exchange` interface represents an AMQP Exchange, which is what a Message Producer sends to. -Each Exchange within a virtual host of a broker has a unique name as well as a few other properties. -The following example shows the `Exchange` interface: - -[source,java] ----- -public interface Exchange { - - String getName(); - - String getExchangeType(); - - boolean isDurable(); - - boolean isAutoDelete(); - - Map getArguments(); - -} ----- - -As you can see, an `Exchange` also has a 'type' represented by constants defined in `ExchangeTypes`. -The basic types are: `direct`, `topic`, `fanout`, and `headers`. -In the core package, you can find implementations of the `Exchange` interface for each of those types. -The behavior varies across these `Exchange` types in terms of how they handle bindings to queues. -For example, a `Direct` exchange lets a queue be bound by a fixed routing key (often the queue's name). -A `Topic` exchange supports bindings with routing patterns that may include the '*' and '#' wildcards for 'exactly-one' and 'zero-or-more', respectively. -The `Fanout` exchange publishes to all queues that are bound to it without taking any routing key into consideration. -For much more information about these and the other Exchange types, see <>. - -NOTE: The AMQP specification also requires that any broker provide a "`default`" direct exchange that has no name. -All queues that are declared are bound to that default `Exchange` with their names as routing keys. -You can learn more about the default Exchange's usage within Spring AMQP in <>. - -===== Queue - -The `Queue` class represents the component from which a message consumer receives messages. -Like the various `Exchange` classes, our implementation is intended to be an abstract representation of this core AMQP type. -The following listing shows the `Queue` class: - -==== -[source,java] ----- -public class Queue { - - private final String name; - - private volatile boolean durable; - - private volatile boolean exclusive; - - private volatile boolean autoDelete; - - private volatile Map arguments; - - /** - * The queue is durable, non-exclusive and non auto-delete. - * - * @param name the name of the queue. - */ - public Queue(String name) { - this(name, true, false, false); - } - - // Getters and Setters omitted for brevity - -} ----- -==== - -Notice that the constructor takes the queue name. -Depending on the implementation, the admin template may provide methods for generating a uniquely named queue. -Such queues can be useful as a "`reply-to`" address or in other *temporary* situations. -For that reason, the 'exclusive' and 'autoDelete' properties of an auto-generated queue would both be set to 'true'. - -NOTE: See the section on queues in <> for information about declaring queues by using namespace support, including queue arguments. - -===== Binding - -Given that a producer sends to an exchange and a consumer receives from a queue, the bindings that connect queues to exchanges are critical for connecting those producers and consumers via messaging. -In Spring AMQP, we define a `Binding` class to represent those connections. -This section reviews the basic options for binding queues to exchanges. - -You can bind a queue to a `DirectExchange` with a fixed routing key, as the following example shows: - -==== -[source,java] ----- -new Binding(someQueue, someDirectExchange, "foo.bar"); ----- -==== - -You can bind a queue to a `TopicExchange` with a routing pattern, as the following example shows: - -==== -[source,java] ----- -new Binding(someQueue, someTopicExchange, "foo.*"); ----- -==== - -You can bind a queue to a `FanoutExchange` with no routing key, as the following example shows: - -==== -[source,java] ----- -new Binding(someQueue, someFanoutExchange); ----- -==== - -We also provide a `BindingBuilder` to facilitate a "`fluent API`" style, as the following example shows: - -==== -[source,java] ----- -Binding b = BindingBuilder.bind(someQueue).to(someTopicExchange).with("foo.*"); ----- -==== - -NOTE: For clarity, the preceding example shows the `BindingBuilder` class, but this style works well when using a static import for the 'bind()' method. - -By itself, an instance of the `Binding` class only holds the data about a connection. -In other words, it is not an "`active`" component. -However, as you will see later in <>, the `AmqpAdmin` class can use `Binding` instances to actually trigger the binding actions on the broker. -Also, as you can see in that same section, you can define the `Binding` instances by using Spring's `@Bean` annotations within `@Configuration` classes. -There is also a convenient base class that further simplifies that approach for generating AMQP-related bean definitions and recognizes the queues, exchanges, and bindings so that they are all declared on the AMQP broker upon application startup. - -The `AmqpTemplate` is also defined within the core package. -As one of the main components involved in actual AMQP messaging, it is discussed in detail in its own section (see <>). - -[[connections]] -==== Connection and Resource Management - -Whereas the AMQP model we described in the previous section is generic and applicable to all implementations, when we get into the management of resources, the details are specific to the broker implementation. -Therefore, in this section, we focus on code that exists only within our "`spring-rabbit`" module since, at this point, RabbitMQ is the only supported implementation. - -The central component for managing a connection to the RabbitMQ broker is the `ConnectionFactory` interface. -The responsibility of a `ConnectionFactory` implementation is to provide an instance of `org.springframework.amqp.rabbit.connection.Connection`, which is a wrapper for `com.rabbitmq.client.Connection`. - -[[choosing-factory]] -===== Choosing a Connection Factory - -There are three connection factories to chose from - -* `PooledChannelConnectionFactory` -* `ThreadChannelConnectionFactory` -* `CachingConnectionFactory` - -The first two were added in version 2.3. - -For most use cases, the `CachingConnectionFactory` should be used. -The `ThreadChannelConnectionFactory` can be used if you want to ensure strict message ordering without the need to use <>. -The `PooledChannelConnectionFactory` is similar to the `CachingConnectionFactory` in that it uses a single connection and a pool of channels. -It's implementation is simpler but it doesn't support correlated publisher confirmations. - -Simple publisher confirmations are supported by all three factories. - -When configuring a `RabbitTemplate` to use a <>, you can now, starting with version 2.3.2, configure the publishing connection factory to be a different type. -By default, the publishing factory is the same type and any properties set on the main factory are also propagated to the publishing factory. - -====== `PooledChannelConnectionFactory` - -This factory manages a single connection and two pools of channels, based on the Apache Pool2. -One pool is for transactional channels, the other is for non-transactional channels. -The pools are `GenericObjectPool` s with default configuration; a callback is provided to configure the pools; refer to the Apache documentation for more information. - -The Apache `commons-pool2` jar must be on the class path to use this factory. - -==== -[source, java] ----- -@Bean -PooledChannelConnectionFactory pcf() throws Exception { - ConnectionFactory rabbitConnectionFactory = new ConnectionFactory(); - rabbitConnectionFactory.setHost("localhost"); - PooledChannelConnectionFactory pcf = new PooledChannelConnectionFactory(rabbitConnectionFactory); - pcf.setPoolConfigurer((pool, tx) -> { - if (tx) { - // configure the transactional pool - } - else { - // configure the non-transactional pool - } - }); - return pcf; -} ----- -==== - -====== `ThreadChannelConnectionFactory` - -This factory manages a single connection and two `ThreadLocal` s, one for transactional channels, the other for non-transactional channels. -This factory ensures that all operations on the same thread use the same channel (as long as it remains open). -This facilitates strict message ordering without the need for <>. -To avoid memory leaks, if your application uses many short-lived threads, you must call the factory's `closeThreadChannel()` to release the channel resource. -Starting with version 2.3.7, a thread can transfer its channel(s) to another thread. -See <> for more information. - -====== `CachingConnectionFactory` - -The third implementation provided is the `CachingConnectionFactory`, which, by default, establishes a single connection proxy that can be shared by the application. -Sharing of the connection is possible since the "`unit of work`" for messaging with AMQP is actually a "`channel`" (in some ways, this is similar to the relationship between a connection and a session in JMS). -The connection instance provides a `createChannel` method. -The `CachingConnectionFactory` implementation supports caching of those channels, and it maintains separate caches for channels based on whether they are transactional. -When creating an instance of `CachingConnectionFactory`, you can provide the 'hostname' through the constructor. -You should also provide the 'username' and 'password' properties. -To configure the size of the channel cache (the default is 25), you can call the -`setChannelCacheSize()` method. - -Starting with version 1.3, you can configure the `CachingConnectionFactory` to cache connections as well as only channels. -In this case, each call to `createConnection()` creates a new connection (or retrieves an idle one from the cache). -Closing a connection returns it to the cache (if the cache size has not been reached). -Channels created on such connections are also cached. -The use of separate connections might be useful in some environments, such as consuming from an HA cluster, in -conjunction with a load balancer, to connect to different cluster members, and others. -To cache connections, set the `cacheMode` to `CacheMode.CONNECTION`. - -NOTE: This does not limit the number of connections. -Rather, it specifies how many idle open connections are allowed. - -Starting with version 1.5.5, a new property called `connectionLimit` is provided. -When this property is set, it limits the total number of connections allowed. -When set, if the limit is reached, the `channelCheckoutTimeLimit` is used to wait for a connection to become idle. -If the time is exceeded, an `AmqpTimeoutException` is thrown. - -[IMPORTANT] -====== -When the cache mode is `CONNECTION`, automatic declaration of queues and others -(See <>) is NOT supported. - -Also, at the time of this writing, the `amqp-client` library by default creates a fixed thread pool for each connection (default size: `Runtime.getRuntime().availableProcessors() * 2` threads). -When using a large number of connections, you should consider setting a custom `executor` on the `CachingConnectionFactory`. -Then, the same executor can be used by all connections and its threads can be shared. -The executor's thread pool should be unbounded or set appropriately for the expected use (usually, at least one thread per connection). -If multiple channels are created on each connection, the pool size affects the concurrency, so a variable (or simple cached) thread pool executor would be most suitable. -====== - -It is important to understand that the cache size is (by default) not a limit but is merely the number of channels that can be cached. -With a cache size of, say, 10, any number of channels can actually be in use. -If more than 10 channels are being used and they are all returned to the cache, 10 go in the cache. -The remainder are physically closed. - -Starting with version 1.6, the default channel cache size has been increased from 1 to 25. -In high volume, multi-threaded environments, a small cache means that channels are created and closed at a high rate. -Increasing the default cache size can avoid this overhead. -You should monitor the channels in use through the RabbitMQ Admin UI and consider increasing the cache size further if you -see many channels being created and closed. -The cache grows only on-demand (to suit the concurrency requirements of the application), so this change does not -impact existing low-volume applications. - -Starting with version 1.4.2, the `CachingConnectionFactory` has a property called `channelCheckoutTimeout`. -When this property is greater than zero, the `channelCacheSize` becomes a limit on the number of channels that can be created on a connection. -If the limit is reached, calling threads block until a channel is available or this timeout is reached, in which case a `AmqpTimeoutException` is thrown. - -WARNING: Channels used within the framework (for example, -`RabbitTemplate`) are reliably returned to the cache. -If you create channels outside of the framework, (for example, -by accessing the connections directly and invoking `createChannel()`), you must return them (by closing) reliably, perhaps in a `finally` block, to avoid running out of channels. - -The following example shows how to create a new `connection`: - -==== -[source,java] ----- -CachingConnectionFactory connectionFactory = new CachingConnectionFactory("somehost"); -connectionFactory.setUsername("guest"); -connectionFactory.setPassword("guest"); - -Connection connection = connectionFactory.createConnection(); ----- -==== - -==== -When using XML, the configuration might look like the following example: - -[source,xml] ----- - - - - - ----- -==== - -NOTE: There is also a `SingleConnectionFactory` implementation that is available only in the unit test code of the framework. -It is simpler than `CachingConnectionFactory`, since it does not cache channels, but it is not intended for practical usage outside of simple tests due to its lack of performance and resilience. -If you need to implement your own `ConnectionFactory` for some reason, the `AbstractConnectionFactory` base class may provide a nice starting point. - -A `ConnectionFactory` can be created quickly and conveniently by using the rabbit namespace, as follows: - -==== -[source,xml] ----- - ----- -==== - -In most cases, this approach is preferable, since the framework can choose the best defaults for you. -The created instance is a `CachingConnectionFactory`. -Keep in mind that the default cache size for channels is 25. -If you want more channels to be cached, set a larger value by setting the 'channelCacheSize' property. -In XML it would look like as follows: - -==== -[source,xml] ----- - - - - - - ----- -==== - -Also, with the namespace, you can add the 'channel-cache-size' attribute, as follows: - -==== -[source,xml] ----- - ----- -==== - -The default cache mode is `CHANNEL`, but you can configure it to cache connections instead. -In the following example, we use `connection-cache-size`: - -==== -[source,xml] ----- - ----- -==== - -You can provide host and port attributes by using the namespace, as follows: - -==== -[source,xml] ----- - ----- -==== - -Alternatively, if running in a clustered environment, you can use the addresses attribute, as follows: - -==== -[source,xml] ----- - ----- -==== - -See <> for information about `address-shuffle-mode`. - -The following example with a custom thread factory that prefixes thread names with `rabbitmq-`: - -==== -[source, xml] ----- - - - - - - ----- -==== - -===== AddressResolver - -Starting with version 2.1.15, you can now use an `AddressResolver` to resolve the connection address(es). -This will override any settings of the `addresses` and `host/port` properties. - -===== Naming Connections - -Starting with version 1.7, a `ConnectionNameStrategy` is provided for the injection into the `AbstractionConnectionFactory`. -The generated name is used for the application-specific identification of the target RabbitMQ connection. -The connection name is displayed in the management UI if the RabbitMQ server supports it. -This value does not have to be unique and cannot be used as a connection identifier -- for example, in HTTP API requests. -This value is supposed to be human-readable and is a part of `ClientProperties` under the `connection_name` key. -You can use a simple Lambda, as follows: - -==== -[source, java] ----- -connectionFactory.setConnectionNameStrategy(connectionFactory -> "MY_CONNECTION"); ----- -==== - -The `ConnectionFactory` argument can be used to distinguish target connection names by some logic. -By default, the `beanName` of the `AbstractConnectionFactory`, a hex string representing the object, and an internal counter are used to generate the `connection_name`. -The `` namespace component is also supplied with the `connection-name-strategy` attribute. - -An implementation of `SimplePropertyValueConnectionNameStrategy` sets the connection name to an application property. -You can declare it as a `@Bean` and inject it into the connection factory, as the following example shows: - -==== -[source, java] ----- -@Bean -public SimplePropertyValueConnectionNameStrategy cns() { - return new SimplePropertyValueConnectionNameStrategy("spring.application.name"); -} - -@Bean -public ConnectionFactory rabbitConnectionFactory(ConnectionNameStrategy cns) { - CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); - ... - connectionFactory.setConnectionNameStrategy(cns); - return connectionFactory; -} ----- -==== - -The property must exist in the application context's `Environment`. - -NOTE: When using Spring Boot and its autoconfigured connection factory, you need only declare the `ConnectionNameStrategy` `@Bean`. -Boot auto-detects the bean and wires it into the factory. - -===== Blocked Connections and Resource Constraints - -The connection might be blocked for interaction from the broker that corresponds to the https://www.rabbitmq.com/memory.html[Memory Alarm]. -Starting with version 2.0, the `org.springframework.amqp.rabbit.connection.Connection` can be supplied with `com.rabbitmq.client.BlockedListener` instances to be notified for connection blocked and unblocked events. -In addition, the `AbstractConnectionFactory` emits a `ConnectionBlockedEvent` and `ConnectionUnblockedEvent`, respectively, through its internal `BlockedListener` implementation. -These let you provide application logic to react appropriately to problems on the broker and (for example) take some corrective actions. - -IMPORTANT: When the application is configured with a single `CachingConnectionFactory`, as it is by default with Spring Boot auto-configuration, the application stops working when the connection is blocked by the Broker. -And when it is blocked by the Broker, any of its clients stop to work. -If we have producers and consumers in the same application, we may end up with a deadlock when producers are blocking the connection (because there are no resources on the Broker any more) and consumers cannot free them (because the connection is blocked). -To mitigate the problem, we suggest having one more separate `CachingConnectionFactory` instance with the same options -- one for producers and one for consumers. -A separate `CachingConnectionFactory` is not possible for transactional producers that execute on a consumer thread, since they should reuse the `Channel` associated with the consumer transactions. - -Starting with version 2.0.2, the `RabbitTemplate` has a configuration option to automatically use a second connection factory, unless transactions are being used. -See <> for more information. -The `ConnectionNameStrategy` for the publisher connection is the same as the primary strategy with `.publisher` appended to the result of calling the method. - -Starting with version 1.7.7, an `AmqpResourceNotAvailableException` is provided, which is thrown when `SimpleConnection.createChannel()` cannot create a `Channel` (for example, because the `channelMax` limit is reached and there are no available channels in the cache). -You can use this exception in the `RetryPolicy` to recover the operation after some back-off. - -[[connection-factory]] -===== Configuring the Underlying Client Connection Factory - -The `CachingConnectionFactory` uses an instance of the Rabbit client `ConnectionFactory`. -A number of configuration properties are passed through (`host`, `port`, `userName`, `password`, `requestedHeartBeat`, and `connectionTimeout` for example) when setting the equivalent property on the `CachingConnectionFactory`. -To set other properties (`clientProperties`, for example), you can define an instance of the Rabbit factory and provide a reference to it by using the appropriate constructor of the `CachingConnectionFactory`. -When using the namespace (<>), you need to provide a reference to the configured factory in the `connection-factory` attribute. -For convenience, a factory bean is provided to assist in configuring the connection factory in a Spring application context, as discussed in <>. - -==== -[source,xml] ----- - ----- -==== - -NOTE: The 4.0.x client enables automatic recovery by default. -While compatible with this feature, Spring AMQP has its own recovery mechanisms and the client recovery feature generally is not needed. -We recommend disabling `amqp-client` automatic recovery, to avoid getting `AutoRecoverConnectionNotCurrentlyOpenException` instances when the broker is available but the connection has not yet recovered. -You may notice this exception, for example, when a `RetryTemplate` is configured in a `RabbitTemplate`, even when failing over to another broker in a cluster. -Since the auto-recovering connection recovers on a timer, the connection may be recovered more quickly by using Spring AMQP's recovery mechanisms. -Starting with version 1.7.1, Spring AMQP disables `amqp-client` automatic recovery unless you explicitly create your own RabbitMQ connection factory and provide it to the `CachingConnectionFactory`. -RabbitMQ `ConnectionFactory` instances created by the `RabbitConnectionFactoryBean` also have the option disabled by default. - -[[rabbitconnectionfactorybean-configuring-ssl]] -===== `RabbitConnectionFactoryBean` and Configuring SSL - -Starting with version 1.4, a convenient `RabbitConnectionFactoryBean` is provided to enable convenient configuration of SSL properties on the underlying client connection factory by using dependency injection. -Other setters delegate to the underlying factory. -Previously, you had to configure the SSL options programmatically. -The following example shows how to configure a `RabbitConnectionFactoryBean`: - -==== -[source,java,role=primary] -.Java ----- -@Bean -RabbitConnectionFactoryBean rabbitConnectionFactory() { - RabbitConnectionFactoryBean factoryBean = new RabbitConnectionFactoryBean(); - factoryBean.setUseSSL(true); - factoryBean.setSslPropertiesLocation(new ClassPathResource("secrets/rabbitSSL.properties")); - return factoryBean; -} - -@Bean -CachingConnectionFactory connectionFactory(ConnectionFactory rabbitConnectionFactory) { - CachingConnectionFactory ccf = new CachingConnectionFactory(rabbitConnectionFactory); - ccf.setHost("..."); - // ... - return ccf; -} ----- -[source,properties,role=secondary] -.Boot application.properties ----- -spring.rabbitmq.ssl.enabled:true -spring.rabbitmq.ssl.keyStore=... -spring.rabbitmq.ssl.keyStoreType=jks -spring.rabbitmq.ssl.keyStorePassword=... -spring.rabbitmq.ssl.trustStore=... -spring.rabbitmq.ssl.trustStoreType=jks -spring.rabbitmq.ssl.trustStorePassword=... -spring.rabbitmq.host=... -... ----- -[source,xml,role=secondary] -.XML ----- - - - - - - ----- -==== - -See the https://www.rabbitmq.com/ssl.html[RabbitMQ Documentation] for information about configuring SSL. -Omit the `keyStore` and `trustStore` configuration to connect over SSL without certificate validation. -The next example shows how you can provide key and trust store configuration. - -The `sslPropertiesLocation` property is a Spring `Resource` pointing to a properties file containing the following keys: - -==== -[source] ----- -keyStore=file:/secret/keycert.p12 -trustStore=file:/secret/trustStore -keyStore.passPhrase=secret -trustStore.passPhrase=secret ----- -==== - -The `keyStore` and `truststore` are Spring `Resources` pointing to the stores. -Typically this properties file is secured by the operating system with the application having read access. - -Starting with Spring AMQP version 1.5,you can set these properties directly on the factory bean. -If both discrete properties and `sslPropertiesLocation` is provided, properties in the latter override the -discrete values. - -IMPORTANT: Starting with version 2.0, the server certificate is validated by default because it is more secure. -If you wish to skip this validation for some reason, set the factory bean's `skipServerCertificateValidation` property to `true`. -Starting with version 2.1, the `RabbitConnectionFactoryBean` now calls `enableHostnameVerification()` by default. -To revert to the previous behavior, set the `enableHostnameVerification` property to `false`. - -IMPORTANT: Starting with version 2.2.5, the factory bean will always use TLS v1.2 by default; previously, it used v1.1 in some cases and v1.2 in others (depending on other properties). -If you need to use v1.1 for some reason, set the `sslAlgorithm` property: `setSslAlgorithm("TLSv1.1")`. - -[[cluster]] -===== Connecting to a Cluster - -To connect to a cluster, configure the `addresses` property on the `CachingConnectionFactory`: - -==== -[source, java] ----- -@Bean -public CachingConnectionFactory ccf() { - CachingConnectionFactory ccf = new CachingConnectionFactory(); - ccf.setAddresses("host1:5672,host2:5672,host3:5672"); - return ccf; -} ----- -==== - -Starting with version 3.0, the underlying connection factory will attempt to connect to a host, by choosing a random address, whenever a new connection is established. -To revert to the previous behavior of attempting to connect from first to last, set the `addressShuffleMode` property to `AddressShuffleMode.NONE`. - -Starting with version 2.3, the `INORDER` shuffle mode was added, which means the first address is moved to the end after a connection is created. -You may wish to use this mode with the https://github.com/rabbitmq/rabbitmq-sharding[RabbitMQ Sharding Plugin] with `CacheMode.CONNECTION` and suitable concurrency if you wish to consume from all shards on all nodes. - -==== -[source, java] ----- -@Bean -public CachingConnectionFactory ccf() { - CachingConnectionFactory ccf = new CachingConnectionFactory(); - ccf.setAddresses("host1:5672,host2:5672,host3:5672"); - ccf.setAddressShuffleMode(AddressShuffleMode.INORDER); - return ccf; -} ----- -==== - -[[routing-connection-factory]] -===== Routing Connection Factory - -Starting with version 1.3, the `AbstractRoutingConnectionFactory` has been introduced. -This factory provides a mechanism to configure mappings for several `ConnectionFactories` and determine a target `ConnectionFactory` by some `lookupKey` at runtime. -Typically, the implementation checks a thread-bound context. -For convenience, Spring AMQP provides the `SimpleRoutingConnectionFactory`, which gets the current thread-bound `lookupKey` from the `SimpleResourceHolder`. -The following examples shows how to configure a `SimpleRoutingConnectionFactory` in both XML and Java: - -==== -[source,xml] ----- - - - - - - - - - - ----- - -[source,java] ----- -public class MyService { - - @Autowired - private RabbitTemplate rabbitTemplate; - - public void service(String vHost, String payload) { - SimpleResourceHolder.bind(rabbitTemplate.getConnectionFactory(), vHost); - rabbitTemplate.convertAndSend(payload); - SimpleResourceHolder.unbind(rabbitTemplate.getConnectionFactory()); - } - -} ----- -==== - -It is important to unbind the resource after use. -For more information, see the https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/rabbit/connection/AbstractRoutingConnectionFactory.html[JavaDoc] for `AbstractRoutingConnectionFactory`. - -Starting with version 1.4, `RabbitTemplate` supports the SpEL `sendConnectionFactorySelectorExpression` and `receiveConnectionFactorySelectorExpression` properties, which are evaluated on each AMQP protocol interaction operation (`send`, `sendAndReceive`, `receive`, or `receiveAndReply`), resolving to a `lookupKey` value for the provided `AbstractRoutingConnectionFactory`. -You can use bean references, such as `@vHostResolver.getVHost(#root)` in the expression. -For `send` operations, the message to be sent is the root evaluation object. -For `receive` operations, the `queueName` is the root evaluation object. - -The routing algorithm is as follows: If the selector expression is `null` or is evaluated to `null` or the provided `ConnectionFactory` is not an instance of `AbstractRoutingConnectionFactory`, everything works as before, relying on the provided `ConnectionFactory` implementation. -The same occurs if the evaluation result is not `null`, but there is no target `ConnectionFactory` for that `lookupKey` and the `AbstractRoutingConnectionFactory` is configured with `lenientFallback = true`. -In the case of an `AbstractRoutingConnectionFactory`, it does fallback to its `routing` implementation based on `determineCurrentLookupKey()`. -However, if `lenientFallback = false`, an `IllegalStateException` is thrown. - -The namespace support also provides the `send-connection-factory-selector-expression` and `receive-connection-factory-selector-expression` attributes on the `` component. - -Also, starting with version 1.4, you can configure a routing connection factory in a listener container. -In that case, the list of queue names is used as the lookup key. -For example, if you configure the container with `setQueueNames("thing1", "thing2")`, the lookup key is `[thing1,thing]"` (note that there is no space in the key). - -Starting with version 1.6.9, you can add a qualifier to the lookup key by using `setLookupKeyQualifier` on the listener container. -Doing so enables, for example, listening to queues with the same name but in a different virtual host (where you would have a connection factory for each). - -For example, with lookup key qualifier `thing1` and a container listening to queue `thing2`, the lookup key you could register the target connection factory with could be `thing1[thing2]`. - -IMPORTANT: The target (and default, if provided) connection factories must have the same settings for publisher confirms and returns. -See <>. - -Starting with version 2.4.4, this validation can be disabled. -If you have a case that the values between confirms and returns need to be unequal, you can use `AbstractRoutingConnectionFactory#setConsistentConfirmsReturns` to turn of the validation. -Note that the first connection factory added to `AbstractRoutingConnectionFactory` will determine the general values of `confirms` and `returns`. - -It may be useful if you have a case that certain messages you would to check confirms/returns and others you don't. -For example: - -==== -[source, java] ----- -@Bean -public RabbitTemplate rabbitTemplate() { - final com.rabbitmq.client.ConnectionFactory cf = new com.rabbitmq.client.ConnectionFactory(); - cf.setHost("localhost"); - cf.setPort(5672); - - CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(cf); - cachingConnectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED); - - PooledChannelConnectionFactory pooledChannelConnectionFactory = new PooledChannelConnectionFactory(cf); - - final Map connectionFactoryMap = new HashMap<>(2); - connectionFactoryMap.put("true", cachingConnectionFactory); - connectionFactoryMap.put("false", pooledChannelConnectionFactory); - - final AbstractRoutingConnectionFactory routingConnectionFactory = new SimpleRoutingConnectionFactory(); - routingConnectionFactory.setConsistentConfirmsReturns(false); - routingConnectionFactory.setDefaultTargetConnectionFactory(pooledChannelConnectionFactory); - routingConnectionFactory.setTargetConnectionFactories(connectionFactoryMap); - - final RabbitTemplate rabbitTemplate = new RabbitTemplate(routingConnectionFactory); - - final Expression sendExpression = new SpelExpressionParser().parseExpression( - "messageProperties.headers['x-use-publisher-confirms'] ?: false"); - rabbitTemplate.setSendConnectionFactorySelectorExpression(sendExpression); -} ----- -==== - -This way messages with the header `x-use-publisher-confirms: true` will be sent through the caching connection and you can ensure the message delivery. -See <> for more information about ensuring message delivery. - -[[queue-affinity]] -===== Queue Affinity and the `LocalizedQueueConnectionFactory` - -When using HA queues in a cluster, for the best performance, you may want to connect to the physical broker -where the lead queue resides. -The `CachingConnectionFactory` can be configured with multiple broker addresses. -This is to fail over and the client attempts to connect in accordance with the configured `AddressShuffleMode` order. -The `LocalizedQueueConnectionFactory` uses the REST API provided by the management plugin to determine which node is the lead for the queue. -It then creates (or retrieves from a cache) a `CachingConnectionFactory` that connects to just that node. -If the connection fails, the new lead node is determined and the consumer connects to it. -The `LocalizedQueueConnectionFactory` is configured with a default connection factory, in case the physical location of the queue cannot be determined, in which case it connects as normal to the cluster. - -The `LocalizedQueueConnectionFactory` is a `RoutingConnectionFactory` and the `SimpleMessageListenerContainer` uses the queue names as the lookup key as discussed in <> above. - -NOTE: For this reason (the use of the queue name for the lookup), the `LocalizedQueueConnectionFactory` can only be used if the container is configured to listen to a single queue. - -NOTE: The RabbitMQ management plugin must be enabled on each node. - -CAUTION: This connection factory is intended for long-lived connections, such as those used by the `SimpleMessageListenerContainer`. -It is not intended for short connection use, such as with a `RabbitTemplate` because of the overhead of invoking the REST API before making the connection. -Also, for publish operations, the queue is unknown, and the message is published to all cluster members anyway, so the logic of looking up the node has little value. - -The following example configuration shows how to configure the factories: - -==== -[source, java] ----- -@Autowired -private ConfigurationProperties props; - -@Bean -public CachingConnectionFactory defaultConnectionFactory() { - CachingConnectionFactory cf = new CachingConnectionFactory(); - cf.setAddresses(this.props.getAddresses()); - cf.setUsername(this.props.getUsername()); - cf.setPassword(this.props.getPassword()); - cf.setVirtualHost(this.props.getVirtualHost()); - return cf; -} - -@Bean -public LocalizedQueueConnectionFactory queueAffinityCF( - @Qualifier("defaultConnectionFactory") ConnectionFactory defaultCF) { - return new LocalizedQueueConnectionFactory(defaultCF, - StringUtils.commaDelimitedListToStringArray(this.props.getAddresses()), - StringUtils.commaDelimitedListToStringArray(this.props.getAdminUris()), - StringUtils.commaDelimitedListToStringArray(this.props.getNodes()), - this.props.getVirtualHost(), this.props.getUsername(), this.props.getPassword(), - false, null); -} ----- -==== - -Notice that the first three parameters are arrays of `addresses`, `adminUris`, and `nodes`. -These are positional in that, when a container attempts to connect to a queue, it uses the admin API to determine which node is the lead for the queue and connects to the address in the same array position as that node. - -IMPORTANT: Starting with version 3.0, the RabbitMQ `http-client` is no longer used to access the Rest API. -Instead, by default, the `WebClient` from Spring Webflux is used if `spring-webflux` is on the class path; otherwise a `RestTemplate` is used. - -To add `WebFlux` to the class path: - -.Maven -==== -[source,xml,subs="+attributes"] ----- - - org.springframework.amqp - spring-rabbit - ----- -==== -.Gradle -==== -[source,groovy,subs="+attributes"] ----- -compile 'org.springframework.amqp:spring-rabbit' ----- -==== - -You can also use other REST technology by implementing `LocalizedQueueConnectionFactory.NodeLocator` and overriding its `createClient, ``restCall`, and optionally, `close` methods. - -==== -[source, java] ----- -lqcf.setNodeLocator(new NodeLocator() { - - @Override - public MyClient createClient(String userName, String password) { - ... - } - - @Override - public HashMap restCall(MyClient client, URI uri) { - ... - }); - -}); ----- -==== - -The framework provides the `WebFluxNodeLocator` and `RestTemplateNodeLocator`, with the default as discussed above. - -[[cf-pub-conf-ret]] -===== Publisher Confirms and Returns - -Confirmed (with correlation) and returned messages are supported by setting the `CachingConnectionFactory` property `publisherConfirmType` to `ConfirmType.CORRELATED` and the `publisherReturns` property to 'true'. - -When these options are set, `Channel` instances created by the factory are wrapped in an `PublisherCallbackChannel`, which is used to facilitate the callbacks. -When such a channel is obtained, the client can register a `PublisherCallbackChannel.Listener` with the `Channel`. -The `PublisherCallbackChannel` implementation contains logic to route a confirm or return to the appropriate listener. -These features are explained further in the following sections. - -See also <> and `simplePublisherConfirms` in <>. - -TIP: For some more background information, see the blog post by the RabbitMQ team titled https://www.rabbitmq.com/blog/2011/02/10/introducing-publisher-confirms/[Introducing Publisher Confirms]. - -[[connection-channel-listeners]] -===== Connection and Channel Listeners - -The connection factory supports registering `ConnectionListener` and `ChannelListener` implementations. -This allows you to receive notifications for connection and channel related events. -(A `ConnectionListener` is used by the `RabbitAdmin` to perform declarations when the connection is established - see <> for more information). -The following listing shows the `ConnectionListener` interface definition: - -==== -[source, java] ----- -@FunctionalInterface -public interface ConnectionListener { - - void onCreate(Connection connection); - - default void onClose(Connection connection) { - } - - default void onShutDown(ShutdownSignalException signal) { - } - -} ----- -==== - -Starting with version 2.0, the `org.springframework.amqp.rabbit.connection.Connection` object can be supplied with `com.rabbitmq.client.BlockedListener` instances to be notified for connection blocked and unblocked events. -The following example shows the ChannelListener interface definition: - -==== -[source, java] ----- -@FunctionalInterface -public interface ChannelListener { - - void onCreate(Channel channel, boolean transactional); - - default void onShutDown(ShutdownSignalException signal) { - } - -} ----- -==== - -See <> for one scenario where you might want to register a `ChannelListener`. - -[[channel-close-logging]] -===== Logging Channel Close Events - -Version 1.5 introduced a mechanism to enable users to control logging levels. - -The `AbstractConnectionFactory` uses a default strategy to log channel closures as follows: - -* Normal channel closes (200 OK) are not logged. -* If a channel is closed due to a failed passive queue declaration, it is logged at DEBUG level. -* If a channel is closed because the `basic.consume` is refused due to an exclusive consumer condition, it is logged at -DEBUG level (since 3.1, previously INFO). -* All others are logged at ERROR level. - -To modify this behavior, you can inject a custom `ConditionalExceptionLogger` into the -`CachingConnectionFactory` in its `closeExceptionLogger` property. - -Also, the `AbstractConnectionFactory.DefaultChannelCloseLogger` is now public, allowing it to be sub classed. - -See also <>. - -[[runtime-cache-properties]] -===== Runtime Cache Properties - -Staring with version 1.6, the `CachingConnectionFactory` now provides cache statistics through the `getCacheProperties()` -method. -These statistics can be used to tune the cache to optimize it in production. -For example, the high water marks can be used to determine whether the cache size should be increased. -If it equals the cache size, you might want to consider increasing further. -The following table describes the `CacheMode.CHANNEL` properties: - -.Cache properties for CacheMode.CHANNEL -[cols="2l,4", options="header"] -|=== -|Property - -|Meaning - -|connectionName - -|The name of the connection generated by the `ConnectionNameStrategy`. - -|channelCacheSize - -|The currently configured maximum channels that are allowed to be idle. - -|localPort - -|The local port for the connection (if available). -This can be used to correlate with connections and channels on the RabbitMQ Admin UI. - -|idleChannelsTx - -|The number of transactional channels that are currently idle (cached). - -|idleChannelsNotTx - -|The number of non-transactional channels that are currently idle (cached). - -|idleChannelsTxHighWater - -|The maximum number of transactional channels that have been concurrently idle (cached). - -|idleChannelsNotTxHighWater - -|The maximum number of non-transactional channels have been concurrently idle (cached). - -|=== - -The following table describes the `CacheMode.CONNECTION` properties: - -.Cache properties for CacheMode.CONNECTION -[cols="2l,4", options="header"] -|=== -|Property - -|Meaning - -|connectionName: - -|The name of the connection generated by the `ConnectionNameStrategy`. - -|openConnections - -|The number of connection objects representing connections to brokers. - -|channelCacheSize - -|The currently configured maximum channels that are allowed to be idle. - -|connectionCacheSize - -|The currently configured maximum connections that are allowed to be idle. - -|idleConnections - -|The number of connections that are currently idle. - -|idleConnectionsHighWater - -|The maximum number of connections that have been concurrently idle. - -|idleChannelsTx: - -|The number of transactional channels that are currently idle (cached) for this connection. -You can use the `localPort` part of the property name to correlate with connections and channels on the RabbitMQ Admin UI. - -|idleChannelsNotTx: - -|The number of non-transactional channels that are currently idle (cached) for this connection. -The `localPort` part of the property name can be used to correlate with connections and channels on the RabbitMQ Admin UI. - -|idleChannelsTxHighWater: - -|The maximum number of transactional channels that have been concurrently idle (cached). -The localPort part of the property name can be used to correlate with connections and channels on the RabbitMQ Admin UI. - -|idleChannelsNotTxHighWater: - -|The maximum number of non-transactional channels have been concurrently idle (cached). -You can use the `localPort` part of the property name to correlate with connections and channels on the RabbitMQ Admin UI. - -|=== - -The `cacheMode` property (`CHANNEL` or `CONNECTION`) is also included. - -.JVisualVM Example -image::images/cacheStats.png[align="center"] - -[[auto-recovery]] -===== RabbitMQ Automatic Connection/Topology recovery - -Since the first version of Spring AMQP, the framework has provided its own connection and channel recovery in the event of a broker failure. -Also, as discussed in <>, the `RabbitAdmin` re-declares any infrastructure beans (queues and others) when the connection is re-established. -It therefore does not rely on the https://www.rabbitmq.com/api-guide.html#recovery[auto-recovery] that is now provided by the `amqp-client` library. -The `amqp-client`, has auto recovery enabled by default. -There are some incompatibilities between the two recovery mechanisms so, by default, Spring sets the `automaticRecoveryEnabled` property on the underlying `RabbitMQ connectionFactory` to `false`. -Even if the property is `true`, Spring effectively disables it, by immediately closing any recovered connections. - -IMPORTANT: By default, only elements (queues, exchanges, bindings) that are defined as beans will be re-declared after a connection failure. -See <> for how to change that behavior. - -[[custom-client-props]] -==== Adding Custom Client Connection Properties - -The `CachingConnectionFactory` now lets you access the underlying connection factory to allow, for example, -setting custom client properties. -The following example shows how to do so: - -[source, java] ----- -connectionFactory.getRabbitConnectionFactory().getClientProperties().put("thing1", "thing2"); ----- - -These properties appear in the RabbitMQ Admin UI when viewing the connection. - -[[amqp-template]] -==== `AmqpTemplate` - -As with many other high-level abstractions provided by the Spring Framework and related projects, Spring AMQP provides a "`template`" that plays a central role. -The interface that defines the main operations is called `AmqpTemplate`. -Those operations cover the general behavior for sending and receiving messages. -In other words, they are not unique to any implementation -- hence the "`AMQP`" in the name. -On the other hand, there are implementations of that interface that are tied to implementations of the AMQP protocol. -Unlike JMS, which is an interface-level API itself, AMQP is a wire-level protocol. -The implementations of that protocol provide their own client libraries, so each implementation of the template interface depends on a particular client library. -Currently, there is only a single implementation: `RabbitTemplate`. -In the examples that follow, we often use an `AmqpTemplate`. -However, when you look at the configuration examples or any code excerpts where the template is instantiated or setters are invoked, you can see the implementation type (for example, `RabbitTemplate`). - -As mentioned earlier, the `AmqpTemplate` interface defines all of the basic operations for sending and receiving messages. -We will explore message sending and reception, respectively, in <> and <>. - -See also <>. - -[[template-retry]] -===== Adding Retry Capabilities - -Starting with version 1.3, you can now configure the `RabbitTemplate` to use a `RetryTemplate` to help with handling problems with broker connectivity. -See the https://github.com/spring-projects/spring-retry[spring-retry] project for complete information. -The following is only one example that uses an exponential back off policy and the default `SimpleRetryPolicy`, which makes three tries before throwing the exception to the caller. - -The following example uses the XML namespace: - -==== -[source,xml] ----- - - - - - - - - - - - ----- -==== - -The following example uses the `@Configuration` annotation in Java: - -==== -[source,java] ----- -@Bean -public RabbitTemplate rabbitTemplate() { - RabbitTemplate template = new RabbitTemplate(connectionFactory()); - RetryTemplate retryTemplate = new RetryTemplate(); - ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); - backOffPolicy.setInitialInterval(500); - backOffPolicy.setMultiplier(10.0); - backOffPolicy.setMaxInterval(10000); - retryTemplate.setBackOffPolicy(backOffPolicy); - template.setRetryTemplate(retryTemplate); - return template; -} ----- -==== - -Starting with version 1.4, in addition to the `retryTemplate` property, the `recoveryCallback` option is supported on the `RabbitTemplate`. -It is used as a second argument for the `RetryTemplate.execute(RetryCallback retryCallback, RecoveryCallback recoveryCallback)`. - -NOTE: The `RecoveryCallback` is somewhat limited, in that the retry context contains only the `lastThrowable` field. -For more sophisticated use cases, you should use an external `RetryTemplate` so that you can convey additional information to the `RecoveryCallback` through the context's attributes. -The following example shows how to do so: - -==== -[source,java] ----- -retryTemplate.execute( - new RetryCallback() { - - @Override - public Object doWithRetry(RetryContext context) throws Exception { - context.setAttribute("message", message); - return rabbitTemplate.convertAndSend(exchange, routingKey, message); - } - - }, new RecoveryCallback() { - - @Override - public Object recover(RetryContext context) throws Exception { - Object message = context.getAttribute("message"); - Throwable t = context.getLastThrowable(); - // Do something with message - return null; - } - }); -} ----- -==== - -In this case, you would *not* inject a `RetryTemplate` into the `RabbitTemplate`. - -[[publishing-is-async]] -===== Publishing is Asynchronous -- How to Detect Successes and Failures - -Publishing messages is an asynchronous mechanism and, by default, messages that cannot be routed are dropped by RabbitMQ. -For successful publishing, you can receive an asynchronous confirm, as described in <>. -Consider two failure scenarios: - -* Publish to an exchange but there is no matching destination queue. -* Publish to a non-existent exchange. - -The first case is covered by publisher returns, as described in <>. - -For the second case, the message is dropped and no return is generated. -The underlying channel is closed with an exception. -By default, this exception is logged, but you can register a `ChannelListener` with the `CachingConnectionFactory` to obtain notifications of such events. -The following example shows how to add a `ConnectionListener`: - -==== -[source, java] ----- -this.connectionFactory.addConnectionListener(new ConnectionListener() { - - @Override - public void onCreate(Connection connection) { - } - - @Override - public void onShutDown(ShutdownSignalException signal) { - ... - } - -}); ----- -==== - -You can examine the signal's `reason` property to determine the problem that occurred. - -To detect the exception on the sending thread, you can `setChannelTransacted(true)` on the `RabbitTemplate` and the exception is detected on the `txCommit()`. -However, *transactions significantly impede performance*, so consider this carefully before enabling transactions for just this one use case. - -[[template-confirms]] -===== Correlated Publisher Confirms and Returns - -The `RabbitTemplate` implementation of `AmqpTemplate` supports publisher confirms and returns. - -For returned messages, the template's `mandatory` property must be set to `true` or the `mandatory-expression` -must evaluate to `true` for a particular message. -This feature requires a `CachingConnectionFactory` that has its `publisherReturns` property set to `true` (see <>). -Returns are sent to the client by it registering a `RabbitTemplate.ReturnsCallback` by calling `setReturnsCallback(ReturnsCallback callback)`. -The callback must implement the following method: - -==== -[source,java] ----- -void returnedMessage(ReturnedMessage returned); ----- -==== - -The `ReturnedMessage` has the following properties: - -- `message` - the returned message itself -- `replyCode` - a code indicating the reason for the return -- `replyText` - a textual reason for the return - e.g. `NO_ROUTE` -- `exchange` - the exchange to which the message was sent -- `routingKey` - the routing key that was used - -Only one `ReturnsCallback` is supported by each `RabbitTemplate`. -See also <>. - -For publisher confirms (also known as publisher acknowledgements), the template requires a `CachingConnectionFactory` that has its `publisherConfirm` property set to `ConfirmType.CORRELATED`. -Confirms are sent to the client by it registering a `RabbitTemplate.ConfirmCallback` by calling `setConfirmCallback(ConfirmCallback callback)`. -The callback must implement this method: - -==== -[source,java] ----- -void confirm(CorrelationData correlationData, boolean ack, String cause); ----- -==== - -The `CorrelationData` is an object supplied by the client when sending the original message. -The `ack` is true for an `ack` and false for a `nack`. -For `nack` instances, the cause may contain a reason for the `nack`, if it is available when the `nack` is generated. -An example is when sending a message to a non-existent exchange. -In that case, the broker closes the channel. -The reason for the closure is included in the `cause`. -The `cause` was added in version 1.4. - -Only one `ConfirmCallback` is supported by a `RabbitTemplate`. - -NOTE: When a rabbit template send operation completes, the channel is closed. -This precludes the reception of confirms or returns when the connection factory cache is full (when there is space in the cache, the channel is not physically closed and the returns and confirms proceed normally). -When the cache is full, the framework defers the close for up to five seconds, in order to allow time for the confirms and returns to be received. -When using confirms, the channel is closed when the last confirm is received. -When using only returns, the channel remains open for the full five seconds. -We generally recommend setting the connection factory's `channelCacheSize` to a large enough value so that the channel on which a message is published is returned to the cache instead of being closed. -You can monitor channel usage by using the RabbitMQ management plugin. -If you see channels being opened and closed rapidly, you should consider increasing the cache size to reduce overhead on the server. - -IMPORTANT: Before version 2.1, channels enabled for publisher confirms were returned to the cache before the confirms were received. -Some other process could check out the channel and perform some operation that causes the channel to close -- such as publishing a message to a non-existent exchange. -This could cause the confirm to be lost. -Version 2.1 and later no longer return the channel to the cache while confirms are outstanding. -The `RabbitTemplate` performs a logical `close()` on the channel after each operation. -In general, this means that only one confirm is outstanding on a channel at a time. - -NOTE: Starting with version 2.2, the callbacks are invoked on one of the connection factory's `executor` threads. -This is to avoid a potential deadlock if you perform Rabbit operations from within the callback. -With previous versions, the callbacks were invoked directly on the `amqp-client` connection I/O thread; this would deadlock if you perform some RPC operation (such as opening a new channel) since the I/O thread blocks waiting for the result, but the result needs to be processed by the I/O thread itself. -With those versions, it was necessary to hand off work (such as sending a messasge) to another thread within the callback. -This is no longer necessary since the framework now hands off the callback invocation to the executor. - -IMPORTANT: The guarantee of receiving a returned message before the ack is still maintained as long as the return callback executes in 60 seconds or less. -The confirm is scheduled to be delivered after the return callback exits or after 60 seconds, whichever comes first. - -The `CorrelationData` object has a `CompletableFuture` that you can use to get the result, instead of using a `ConfirmCallback` on the template. -The following example shows how to configure a `CorrelationData` instance: - -==== -[source, java] ----- -CorrelationData cd1 = new CorrelationData(); -this.templateWithConfirmsEnabled.convertAndSend("exchange", queue.getName(), "foo", cd1); -assertTrue(cd1.getFuture().get(10, TimeUnit.SECONDS).isAck()); -ReturnedMessage = cd1.getReturn(); -... ----- -==== - -Since it is a `CompletableFuture`, you can either `get()` the result when ready or use `whenComplete()` for an asynchronous callback. -The `Confirm` object is a simple bean with 2 properties: `ack` and `reason` (for `nack` instances). -The reason is not populated for broker-generated `nack` instances. -It is populated for `nack` instances generated by the framework (for example, closing the connection while `ack` instances are outstanding). - -In addition, when both confirms and returns are enabled, the `CorrelationData` `return` property is populated with the returned message, if it couldn't be routed to any queue. -It is guaranteed that the returned message property is set before the future is set with the `ack`. -`CorrelationData.getReturn()` returns a `ReturnMessage` with properties: - -* message (the returned message) -* replyCode -* replyText -* exchange -* routingKey - -See also <> for a simpler mechanism for waiting for publisher confirms. - -[[scoped-operations]] -===== Scoped Operations - -Normally, when using the template, a `Channel` is checked out of the cache (or created), used for the operation, and returned to the cache for reuse. -In a multi-threaded environment, there is no guarantee that the next operation uses the same channel. -There may be times, however, where you want to have more control over the use of a channel and ensure that a number of operations are all performed on the same channel. - -Starting with version 2.0, a new method called `invoke` is provided, with an `OperationsCallback`. -Any operations performed within the scope of the callback and on the provided `RabbitOperations` argument use the same dedicated `Channel`, which will be closed at the end (not returned to a cache). -If the channel is a `PublisherCallbackChannel`, it is returned to the cache after all confirms have been received (see <>). - -==== -[source, java] ----- -@FunctionalInterface -public interface OperationsCallback { - - T doInRabbit(RabbitOperations operations); - -} ----- -==== - -One example of why you might need this is if you wish to use the `waitForConfirms()` method on the underlying `Channel`. -This method was not previously exposed by the Spring API because the channel is, generally, cached and shared, as discussed earlier. -The `RabbitTemplate` now provides `waitForConfirms(long timeout)` and `waitForConfirmsOrDie(long timeout)`, which delegate to the dedicated channel used within the scope of the `OperationsCallback`. -The methods cannot be used outside of that scope, for obvious reasons. - -Note that a higher-level abstraction that lets you correlate confirms to requests is provided elsewhere (see <>). -If you want only to wait until the broker has confirmed delivery, you can use the technique shown in the following example: - -==== -[source, java] ----- -Collection messages = getMessagesToSend(); -Boolean result = this.template.invoke(t -> { - messages.forEach(m -> t.convertAndSend(ROUTE, m)); - t.waitForConfirmsOrDie(10_000); - return true; -}); ----- -==== - -If you wish `RabbitAdmin` operations to be invoked on the same channel within the scope of the `OperationsCallback`, the admin must have been constructed by using the same `RabbitTemplate` that was used for the `invoke` operation. - -NOTE: The preceding discussion is moot if the template operations are already performed within the scope of an existing transaction -- for example, when running on a transacted listener container thread and performing operations on a transacted template. -In that case, the operations are performed on that channel and committed when the thread returns to the container. -It is not necessary to use `invoke` in that scenario. - -When using confirms in this way, much of the infrastructure set up for correlating confirms to requests is not really needed (unless returns are also enabled). -Starting with version 2.2, the connection factory supports a new property called `publisherConfirmType`. -When this is set to `ConfirmType.SIMPLE`, the infrastructure is avoided and the confirm processing can be more efficient. - -Furthermore, the `RabbitTemplate` sets the `publisherSequenceNumber` property in the sent message `MessageProperties`. -If you wish to check (or log or otherwise use) specific confirms, you can do so with an overloaded `invoke` method, as the following example shows: - -==== -[source, java] ----- -public T invoke(OperationsCallback action, com.rabbitmq.client.ConfirmCallback acks, - com.rabbitmq.client.ConfirmCallback nacks); ----- -==== - -NOTE: These `ConfirmCallback` objects (for `ack` and `nack` instances) are the Rabbit client callbacks, not the template callback. - -The following example logs `ack` and `nack` instances: - -==== -[source, java] ----- -Collection messages = getMessagesToSend(); -Boolean result = this.template.invoke(t -> { - messages.forEach(m -> t.convertAndSend(ROUTE, m)); - t.waitForConfirmsOrDie(10_000); - return true; -}, (tag, multiple) -> { - log.info("Ack: " + tag + ":" + multiple); -}, (tag, multiple) -> { - log.info("Nack: " + tag + ":" + multiple); -})); ----- -==== - -IMPORTANT: Scoped operations are bound to a thread. -See <> for a discussion about strict ordering in a multi-threaded environment. - -[[multi-strict]] -===== Strict Message Ordering in a Multi-Threaded Environment - -The discussion in <> applies only when the operations are performed on the same thread. - -Consider the following situation: - -* `thread-1` sends a message to a queue and hands off work to `thread-2` -* `thread-2` sends a message to the same queue - -Because of the async nature of RabbitMQ and the use of cached channels; it is not certain that the same channel will be used and therefore the order in which the messages arrive in the queue is not guaranteed. -(In most cases they will arrive in order, but the probability of out-of-order delivery is not zero). -To solve this use case, you can use a bounded channel cache with size `1` (together with a `channelCheckoutTimeout`) to ensure the messages are always published on the same channel, and order will be guaranteed. -To do this, if you have other uses for the connection factory, such as consumers, you should either use a dedicated connection factory for the template, or configure the template to use the publisher connection factory embedded in the main connection factory (see <>). - -This is best illustrated with a simple Spring Boot Application: - -==== -[source, java] ----- -@SpringBootApplication -public class Application { - - private static final Logger log = LoggerFactory.getLogger(Application.class); - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - @Bean - TaskExecutor exec() { - ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor(); - exec.setCorePoolSize(10); - return exec; - } - - @Bean - CachingConnectionFactory ccf() { - CachingConnectionFactory ccf = new CachingConnectionFactory("localhost"); - CachingConnectionFactory publisherCF = (CachingConnectionFactory) ccf.getPublisherConnectionFactory(); - publisherCF.setChannelCacheSize(1); - publisherCF.setChannelCheckoutTimeout(1000L); - return ccf; - } - - @RabbitListener(queues = "queue") - void listen(String in) { - log.info(in); - } - - @Bean - Queue queue() { - return new Queue("queue"); - } - - - @Bean - public ApplicationRunner runner(Service service, TaskExecutor exec) { - return args -> { - exec.execute(() -> service.mainService("test")); - }; - } - -} - -@Component -class Service { - - private static final Logger LOG = LoggerFactory.getLogger(Service.class); - - private final RabbitTemplate template; - - private final TaskExecutor exec; - - Service(RabbitTemplate template, TaskExecutor exec) { - template.setUsePublisherConnection(true); - this.template = template; - this.exec = exec; - } - - void mainService(String toSend) { - LOG.info("Publishing from main service"); - this.template.convertAndSend("queue", toSend); - this.exec.execute(() -> secondaryService(toSend.toUpperCase())); - } - - void secondaryService(String toSend) { - LOG.info("Publishing from secondary service"); - this.template.convertAndSend("queue", toSend); - } - -} ----- -==== - -Even though the publishing is performed on two different threads, they will both use the same channel because the cache is capped at a single channel. - -Starting with version 2.3.7, the `ThreadChannelConnectionFactory` supports transferring a thread's channel(s) to another thread, using the `prepareContextSwitch` and `switchContext` methods. -The first method returns a context which is passed to the second thread which calls the second method. -A thread can have either a non-transactional channel or a transactional channel (or one of each) bound to it; you cannot transfer them individually, unless you use two connection factories. -An example follows: - -==== -[source, java] ----- -@SpringBootApplication -public class Application { - - private static final Logger log = LoggerFactory.getLogger(Application.class); - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - @Bean - TaskExecutor exec() { - ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor(); - exec.setCorePoolSize(10); - return exec; - } - - @Bean - ThreadChannelConnectionFactory tccf() { - ConnectionFactory rabbitConnectionFactory = new ConnectionFactory(); - rabbitConnectionFactory.setHost("localhost"); - return new ThreadChannelConnectionFactory(rabbitConnectionFactory); - } - - @RabbitListener(queues = "queue") - void listen(String in) { - log.info(in); - } - - @Bean - Queue queue() { - return new Queue("queue"); - } - - - @Bean - public ApplicationRunner runner(Service service, TaskExecutor exec) { - return args -> { - exec.execute(() -> service.mainService("test")); - }; - } - -} - -@Component -class Service { - - private static final Logger LOG = LoggerFactory.getLogger(Service.class); - - private final RabbitTemplate template; - - private final TaskExecutor exec; - - private final ThreadChannelConnectionFactory connFactory; - - Service(RabbitTemplate template, TaskExecutor exec, - ThreadChannelConnectionFactory tccf) { - - this.template = template; - this.exec = exec; - this.connFactory = tccf; - } - - void mainService(String toSend) { - LOG.info("Publishing from main service"); - this.template.convertAndSend("queue", toSend); - Object context = this.connFactory.prepareSwitchContext(); - this.exec.execute(() -> secondaryService(toSend.toUpperCase(), context)); - } - - void secondaryService(String toSend, Object threadContext) { - LOG.info("Publishing from secondary service"); - this.connFactory.switchContext(threadContext); - this.template.convertAndSend("queue", toSend); - this.connFactory.closeThreadChannel(); - } - -} ----- -==== - -IMPORTANT: Once the `prepareSwitchContext` is called, if the current thread performs any more operations, they will be performed on a new channel. -It is important to close the thread-bound channel when it is no longer needed. - -[[template-messaging]] -===== Messaging Integration - -Starting with version 1.4, `RabbitMessagingTemplate` (built on top of `RabbitTemplate`) provides an integration with the Spring Framework messaging abstraction -- that is, -`org.springframework.messaging.Message`. -This lets you send and receive messages by using the `spring-messaging` `Message` abstraction. -This abstraction is used by other Spring projects, such as Spring Integration and Spring's STOMP support. -There are two message converters involved: one to convert between a spring-messaging `Message` and Spring AMQP's `Message` abstraction and one to convert between Spring AMQP's `Message` abstraction and the format required by the underlying RabbitMQ client library. -By default, the message payload is converted by the provided `RabbitTemplate` instance's message converter. -Alternatively, you can inject a custom `MessagingMessageConverter` with some other payload converter, as the following example shows: - -==== -[source, java] ----- -MessagingMessageConverter amqpMessageConverter = new MessagingMessageConverter(); -amqpMessageConverter.setPayloadConverter(myPayloadConverter); -rabbitMessagingTemplate.setAmqpMessageConverter(amqpMessageConverter); ----- -==== - -[[template-user-id]] -===== Validated User Id - -Starting with version 1.6, the template now supports a `user-id-expression` (`userIdExpression` when using Java configuration). -If a message is sent, the user id property is set (if not already set) after evaluating this expression. -The root object for the evaluation is the message to be sent. - -The following examples show how to use the `user-id-expression` attribute: - -==== -[source, xml] ----- - - - ----- -==== - -The first example is a literal expression. -The second obtains the `username` property from a connection factory bean in the application context. - -[[separate-connection]] -===== Using a Separate Connection - -Starting with version 2.0.2, you can set the `usePublisherConnection` property to `true` to use a different connection to that used by listener containers, when possible. -This is to avoid consumers being blocked when a producer is blocked for any reason. -The connection factories maintain a second internal connection factory for this purpose; by default it is the same type as the main factory, but can be set explicitly if you wish to use a different factory type for publishing. -If the rabbit template is running in a transaction started by the listener container, the container's channel is used, regardless of this setting. - -IMPORTANT: In general, you should not use a `RabbitAdmin` with a template that has this set to `true`. -Use the `RabbitAdmin` constructor that takes a connection factory. -If you use the other constructor that takes a template, ensure the template's property is `false`. -This is because, often, an admin is used to declare queues for listener containers. -Using a template that has the property set to `true` would mean that exclusive queues (such as `AnonymousQueue`) would be declared on a different connection to that used by listener containers. -In that case, the queues cannot be used by the containers. - -[[sending-messages]] -==== Sending Messages - -When sending a message, you can use any of the following methods: - -==== -[source,java] ----- -void send(Message message) throws AmqpException; - -void send(String routingKey, Message message) throws AmqpException; - -void send(String exchange, String routingKey, Message message) throws AmqpException; ----- -==== - -We can begin our discussion with the last method in the preceding listing, since it is actually the most explicit. -It lets an AMQP exchange name (along with a routing key)be provided at runtime. -The last parameter is the callback that is responsible for actual creating the message instance. -An example of using this method to send a message might look like this: -The following example shows how to use the `send` method to send a message: - -==== -[source,java] ----- -amqpTemplate.send("marketData.topic", "quotes.nasdaq.THING1", - new Message("12.34".getBytes(), someProperties)); ----- -==== - -You can set the `exchange` property on the template itself if you plan to use that template instance to send to the same exchange most or all of the time. -In such cases, you can use the second method in the preceding listing. -The following example is functionally equivalent to the previous example: - -==== -[source,java] ----- -amqpTemplate.setExchange("marketData.topic"); -amqpTemplate.send("quotes.nasdaq.FOO", new Message("12.34".getBytes(), someProperties)); ----- -==== - -If both the `exchange` and `routingKey` properties are set on the template, you can use the method that accepts only the `Message`. -The following example shows how to do so: - -==== -[source,java] ----- -amqpTemplate.setExchange("marketData.topic"); -amqpTemplate.setRoutingKey("quotes.nasdaq.FOO"); -amqpTemplate.send(new Message("12.34".getBytes(), someProperties)); ----- -==== - -A better way of thinking about the exchange and routing key properties is that the explicit method parameters always override the template's default values. -In fact, even if you do not explicitly set those properties on the template, there are always default values in place. -In both cases, the default is an empty `String`, but that is actually a sensible default. -As far as the routing key is concerned, it is not always necessary in the first place (for example, for -a `Fanout` exchange). -Furthermore, a queue may be bound to an exchange with an empty `String`. -Those are both legitimate scenarios for reliance on the default empty `String` value for the routing key property of the template. -As far as the exchange name is concerned, the empty `String` is commonly used because the AMQP specification defines the "`default exchange`" as having no name. -Since all queues are automatically bound to that default exchange (which is a direct exchange), using their name as the binding value, the second method in the preceding listing can be used for simple point-to-point messaging to any queue through the default exchange. -You can provide the queue name as the `routingKey`, either by providing the method parameter at runtime. -The following example shows how to do so: - -==== -[source,java] ----- -RabbitTemplate template = new RabbitTemplate(); // using default no-name Exchange -template.send("queue.helloWorld", new Message("Hello World".getBytes(), someProperties)); ----- -==== - -Alternately, you can create a template that can be used for publishing primarily or exclusively to a single Queue. -The following example shows how to do so: - -==== -[source,java] ----- -RabbitTemplate template = new RabbitTemplate(); // using default no-name Exchange -template.setRoutingKey("queue.helloWorld"); // but we'll always send to this Queue -template.send(new Message("Hello World".getBytes(), someProperties)); ----- -==== - -[[message-builder]] -===== Message Builder API - -Starting with version 1.3, a message builder API is provided by the `MessageBuilder` and `MessagePropertiesBuilder`. -These methods provide a convenient "`fluent`" means of creating a message or message properties. -The following examples show the fluent API in action: - -==== -[source,java] ----- -Message message = MessageBuilder.withBody("foo".getBytes()) - .setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN) - .setMessageId("123") - .setHeader("bar", "baz") - .build(); ----- - -[source,java] ----- -MessageProperties props = MessagePropertiesBuilder.newInstance() - .setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN) - .setMessageId("123") - .setHeader("bar", "baz") - .build(); -Message message = MessageBuilder.withBody("foo".getBytes()) - .andProperties(props) - .build(); ----- -==== - -Each of the properties defined on the https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/core/MessageProperties.html[`MessageProperties`] can be set. -Other methods include `setHeader(String key, String value)`, `removeHeader(String key)`, `removeHeaders()`, and `copyProperties(MessageProperties properties)`. -Each property setting method has a `set*IfAbsent()` variant. -In the cases where a default initial value exists, the method is named `set*IfAbsentOrDefault()`. - -Five static methods are provided to create an initial message builder: - -==== -[source,java] ----- -public static MessageBuilder withBody(byte[] body) <1> - -public static MessageBuilder withClonedBody(byte[] body) <2> - -public static MessageBuilder withBody(byte[] body, int from, int to) <3> - -public static MessageBuilder fromMessage(Message message) <4> - -public static MessageBuilder fromClonedMessage(Message message) <5> ----- - -<1> The message created by the builder has a body that is a direct reference to the argument. -<2> The message created by the builder has a body that is a new array containing a copy of bytes in the argument. -<3> The message created by the builder has a body that is a new array containing the range of bytes from the argument. -See https://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html[`Arrays.copyOfRange()`] for more details. -<4> The message created by the builder has a body that is a direct reference to the body of the argument. -The argument's properties are copied to a new `MessageProperties` object. -<5> The message created by the builder has a body that is a new array containing a copy of the argument's body. -The argument's properties are copied to a new `MessageProperties` object. -==== - -Three static methods are provided to create a `MessagePropertiesBuilder` instance: - -==== -[source,java] ----- -public static MessagePropertiesBuilder newInstance() <1> - -public static MessagePropertiesBuilder fromProperties(MessageProperties properties) <2> - -public static MessagePropertiesBuilder fromClonedProperties(MessageProperties properties) <3> ----- - -<1> A new message properties object is initialized with default values. -<2> The builder is initialized with, and `build()` will return, the provided properties object., -<3> The argument's properties are copied to a new `MessageProperties` object. -==== - -With the `RabbitTemplate` implementation of `AmqpTemplate`, each of the `send()` methods has an overloaded version that takes an additional `CorrelationData` object. -When publisher confirms are enabled, this object is returned in the callback described in <>. -This lets the sender correlate a confirm (`ack` or `nack`) with the sent message. - -Starting with version 1.6.7, the `CorrelationAwareMessagePostProcessor` interface was introduced, allowing the correlation data to be modified after the message has been converted. -The following example shows how to use it: - -==== -[source, java] ----- -Message postProcessMessage(Message message, Correlation correlation); ----- -==== - -In version 2.0, this interface is deprecated. -The method has been moved to `MessagePostProcessor` with a default implementation that delegates to `postProcessMessage(Message message)`. - -Also starting with version 1.6.7, a new callback interface called `CorrelationDataPostProcessor` is provided. -This is invoked after all `MessagePostProcessor` instances (provided in the `send()` method as well as those provided in `setBeforePublishPostProcessors()`). -Implementations can update or replace the correlation data supplied in the `send()` method (if any). -The `Message` and original `CorrelationData` (if any) are provided as arguments. -The following example shows how to use the `postProcess` method: - -==== -[source, java] ----- -CorrelationData postProcess(Message message, CorrelationData correlationData); ----- -==== - -===== Publisher Returns - -When the template's `mandatory` property is `true`, returned messages are provided by the callback described in <>. - -Starting with version 1.4, the `RabbitTemplate` supports the SpEL `mandatoryExpression` property, which is evaluated against each request message as the root evaluation object, resolving to a `boolean` value. -Bean references, such as `@myBean.isMandatory(#root)`, can be used in the expression. - -Publisher returns can also be used internally by the `RabbitTemplate` in send and receive operations. -See <> for more information. - -[[template-batching]] -===== Batching - -Version 1.4.2 introduced the `BatchingRabbitTemplate`. -This is a subclass of `RabbitTemplate` with an overridden `send` method that batches messages according to the `BatchingStrategy`. -Only when a batch is complete is the message sent to RabbitMQ. -The following listing shows the `BatchingStrategy` interface definition: - -==== -[source, java] ----- -public interface BatchingStrategy { - - MessageBatch addToBatch(String exchange, String routingKey, Message message); - - Date nextRelease(); - - Collection releaseBatches(); - -} ----- -==== - -CAUTION: Batched data is held in memory. -Unsent messages can be lost in the event of a system failure. - -A `SimpleBatchingStrategy` is provided. -It supports sending messages to a single exchange or routing key. -It has the following properties: - -* `batchSize`: The number of messages in a batch before it is sent. -* `bufferLimit`: The maximum size of the batched message. -This preempts the `batchSize`, if exceeded, and causes a partial batch to be sent. -* `timeout`: A time after which a partial batch is sent when there is no new activity adding messages to the batch. - -The `SimpleBatchingStrategy` formats the batch by preceding each embedded message with a four-byte binary length. -This is communicated to the receiving system by setting the `springBatchFormat` message property to `lengthHeader4`. - -IMPORTANT: Batched messages are automatically de-batched by listener containers by default (by using the `springBatchFormat` message header). -Rejecting any message from a batch causes the entire batch to be rejected. - -However, see <> for more information. - -[[receiving-messages]] -==== Receiving Messages - -Message reception is always a little more complicated than sending. -There are two ways to receive a `Message`. -The simpler option is to poll for one `Message` at a time with a polling method call. -The more complicated yet more common approach is to register a listener that receives `Messages` on-demand, asynchronously. -We consider an example of each approach in the next two sub-sections. - -[[polling-consumer]] -===== Polling Consumer - -The `AmqpTemplate` itself can be used for polled `Message` reception. -By default, if no message is available, `null` is returned immediately. -There is no blocking. -Starting with version 1.5, you can set a `receiveTimeout`, in milliseconds, and the receive methods block for up to that long, waiting for a message. -A value less than zero means block indefinitely (or at least until the connection to the broker is lost). -Version 1.6 introduced variants of the `receive` methods that allows the timeout be passed in on each call. - -CAUTION: Since the receive operation creates a new `QueueingConsumer` for each message, this technique is not really appropriate for high-volume environments. -Consider using an asynchronous consumer or a `receiveTimeout` of zero for those use cases. - -Starting with version 2.4.8, when using a non-zero timeout, you can specify arguments passed into the `basicConsume` method used to associate the consumer with the channel. -For example: `template.addConsumerArg("x-priority", 10)`. - -There are four simple `receive` methods available. -As with the `Exchange` on the sending side, there is a method that requires that a default queue property has been set -directly on the template itself, and there is a method that accepts a queue parameter at runtime. -Version 1.6 introduced variants to accept `timeoutMillis` to override `receiveTimeout` on a per-request basis. -The following listing shows the definitions of the four methods: - -==== -[source,java] ----- -Message receive() throws AmqpException; - -Message receive(String queueName) throws AmqpException; - -Message receive(long timeoutMillis) throws AmqpException; - -Message receive(String queueName, long timeoutMillis) throws AmqpException; ----- -==== - -As in the case of sending messages, the `AmqpTemplate` has some convenience methods for receiving POJOs instead of `Message` instances, and implementations provide a way to customize the `MessageConverter` used to create the `Object` returned: -The following listing shows those methods: - -==== -[source,java] ----- -Object receiveAndConvert() throws AmqpException; - -Object receiveAndConvert(String queueName) throws AmqpException; - -Object receiveAndConvert(long timeoutMillis) throws AmqpException; - -Object receiveAndConvert(String queueName, long timeoutMillis) throws AmqpException; ----- -==== - -Starting with version 2.0, there are variants of these methods that take an additional `ParameterizedTypeReference` argument to convert complex types. -The template must be configured with a `SmartMessageConverter`. -See <> for more information. - -Similar to `sendAndReceive` methods, beginning with version 1.3, the `AmqpTemplate` has several convenience `receiveAndReply` methods for synchronously receiving, processing and replying to messages. -The following listing shows those method definitions: - -==== -[source,java] ----- - boolean receiveAndReply(ReceiveAndReplyCallback callback) - throws AmqpException; - - boolean receiveAndReply(String queueName, ReceiveAndReplyCallback callback) - throws AmqpException; - - boolean receiveAndReply(ReceiveAndReplyCallback callback, - String replyExchange, String replyRoutingKey) throws AmqpException; - - boolean receiveAndReply(String queueName, ReceiveAndReplyCallback callback, - String replyExchange, String replyRoutingKey) throws AmqpException; - - boolean receiveAndReply(ReceiveAndReplyCallback callback, - ReplyToAddressCallback replyToAddressCallback) throws AmqpException; - - boolean receiveAndReply(String queueName, ReceiveAndReplyCallback callback, - ReplyToAddressCallback replyToAddressCallback) throws AmqpException; ----- -==== - -The `AmqpTemplate` implementation takes care of the `receive` and `reply` phases. -In most cases, you should provide only an implementation of `ReceiveAndReplyCallback` to perform some business logic for the received message and build a reply object or message, if needed. -Note, a `ReceiveAndReplyCallback` may return `null`. -In this case, no reply is sent and `receiveAndReply` works like the `receive` method. -This lets the same queue be used for a mixture of messages, some of which may not need a reply. - -Automatic message (request and reply) conversion is applied only if the provided callback is not an instance of `ReceiveAndReplyMessageCallback`, which provides a raw message exchange contract. - -The `ReplyToAddressCallback` is useful for cases requiring custom logic to determine the `replyTo` address at runtime against the received message and reply from the `ReceiveAndReplyCallback`. -By default, `replyTo` information in the request message is used to route the reply. - -The following listing shows an example of POJO-based receive and reply: - -==== -[source,java] ----- -boolean received = - this.template.receiveAndReply(ROUTE, new ReceiveAndReplyCallback() { - - public Invoice handle(Order order) { - return processOrder(order); - } - }); -if (received) { - log.info("We received an order!"); -} ----- -==== - -[[async-consumer]] -===== Asynchronous Consumer - -IMPORTANT: Spring AMQP also supports annotated listener endpoints through the use of the `@RabbitListener` annotation and provides an open infrastructure to register endpoints programmatically. -This is by far the most convenient way to setup an asynchronous consumer. -See <> for more details. - -[IMPORTANT] -==== -The prefetch default value used to be 1, which could lead to under-utilization of efficient consumers. -Starting with version 2.0, the default prefetch value is now 250, which should keep consumers busy in most common scenarios and -thus improve throughput. - -There are, nevertheless, scenarios where the prefetch value should be low: - -* For large messages, especially if the processing is slow (messages could add up to a large amount of memory in the client process) -* When strict message ordering is necessary (the prefetch value should be set back to 1 in this case) -* Other special cases - -Also, with low-volume messaging and multiple consumers (including concurrency within a single listener container instance), you may wish to reduce the prefetch to get a more even distribution of messages across consumers. - -See <>. - -For more background about prefetch, see this post about https://www.rabbitmq.com/blog/2014/04/14/finding-bottlenecks-with-rabbitmq-3-3/[consumer utilization in RabbitMQ] -and this post about https://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/[queuing theory]. -==== - -====== Message Listener - -For asynchronous `Message` reception, a dedicated component (not the `AmqpTemplate`) is involved. -That component is a container for a `Message`-consuming callback. -We consider the container and its properties later in this section. -First, though, we should look at the callback, since that is where your application code is integrated with the messaging system. -There are a few options for the callback, starting with an implementation of the `MessageListener` interface, which the following listing shows: - -==== -[source,java] ----- -public interface MessageListener { - void onMessage(Message message); -} ----- -==== - -If your callback logic depends on the AMQP Channel instance for any reason, you may instead use the `ChannelAwareMessageListener`. -It looks similar but has an extra parameter. -The following listing shows the `ChannelAwareMessageListener` interface definition: - -==== -[source,java] ----- -public interface ChannelAwareMessageListener { - void onMessage(Message message, Channel channel) throws Exception; -} ----- -==== - -IMPORTANT: In version 2.1, this interface moved from package `o.s.amqp.rabbit.core` to `o.s.amqp.rabbit.listener.api`. - -[[message-listener-adapter]] -====== `MessageListenerAdapter` - -If you prefer to maintain a stricter separation between your application logic and the messaging API, you can rely upon an adapter implementation that is provided by the framework. -This is often referred to as "`Message-driven POJO`" support. - -NOTE: Version 1.5 introduced a more flexible mechanism for POJO messaging, the `@RabbitListener` annotation. -See <> for more information. - -When using the adapter, you need to provide only a reference to the instance that the adapter itself should invoke. -The following example shows how to do so: - -==== -[source,java] ----- -MessageListenerAdapter listener = new MessageListenerAdapter(somePojo); -listener.setDefaultListenerMethod("myMethod"); ----- -==== - -You can subclass the adapter and provide an implementation of `getListenerMethodName()` to dynamically select different methods based on the message. -This method has two parameters, `originalMessage` and `extractedMessage`, the latter being the result of any conversion. -By default, a `SimpleMessageConverter` is configured. -See <> for more information and information about other converters available. - -Starting with version 1.4.2, the original message has `consumerQueue` and `consumerTag` properties, which can be used to determine the queue from which a message was received. - -Starting with version 1.5, you can configure a map of consumer queue or tag to method name, to dynamically select the method to call. -If no entry is in the map, we fall back to the default listener method. -The default listener method (if not set) is `handleMessage`. - -Starting with version 2.0, a convenient `FunctionalInterface` has been provided. -The following listing shows the definition of `FunctionalInterface`: - -==== -[source, java] ----- -@FunctionalInterface -public interface ReplyingMessageListener { - - R handleMessage(T t); - -} ----- -==== - -This interface facilitates convenient configuration of the adapter by using Java 8 lambdas, as the following example shows: - -==== -[source, java] ----- -new MessageListenerAdapter((ReplyingMessageListener) data -> { - ... - return result; -})); ----- -==== - -Starting with version 2.2, the `buildListenerArguments(Object)` has been deprecated and new `buildListenerArguments(Object, Channel, Message)` one has been introduced instead. -The new method helps listener to get `Channel` and `Message` arguments to do more, such as calling `channel.basicReject(long, boolean)` in manual acknowledge mode. -The following listing shows the most basic example: - -==== -[source,java] ----- -public class ExtendedListenerAdapter extends MessageListenerAdapter { - - @Override - protected Object[] buildListenerArguments(Object extractedMessage, Channel channel, Message message) { - return new Object[]{extractedMessage, channel, message}; - } - -} ----- -==== - -Now you could configure `ExtendedListenerAdapter` as same as `MessageListenerAdapter` if you need to receive "`channel`" and "`message`". -Parameters of listener should be set as `buildListenerArguments(Object, Channel, Message)` returned, as the following example of listener shows: - -==== -[source,java] ----- -public void handleMessage(Object object, Channel channel, Message message) throws IOException { - ... -} ----- -==== - -====== Container - -Now that you have seen the various options for the `Message`-listening callback, we can turn our attention to the container. -Basically, the container handles the "`active`" responsibilities so that the listener callback can remain passive. -The container is an example of a "`lifecycle`" component. -It provides methods for starting and stopping. -When configuring the container, you essentially bridge the gap between an AMQP Queue and the `MessageListener` instance. -You must provide a reference to the `ConnectionFactory` and the queue names or Queue instances from which that listener should consume messages. - -Prior to version 2.0, there was one listener container, the `SimpleMessageListenerContainer`. -There is now a second container, the `DirectMessageListenerContainer`. -The differences between the containers and criteria you might apply when choosing which to use are described in <>. - -The following listing shows the most basic example, which works by using the, `SimpleMessageListenerContainer`: - -==== -[source,java] ----- -SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); -container.setConnectionFactory(rabbitConnectionFactory); -container.setQueueNames("some.queue"); -container.setMessageListener(new MessageListenerAdapter(somePojo)); ----- -==== - -As an "`active`" component, it is most common to create the listener container with a bean definition so that it can run in the background. -The following example shows one way to do so with XML: - -==== -[source,xml] ----- - - - ----- -==== - -The following listing shows another way to do so with XML: - -==== -[source,xml] ----- - - - ----- -==== - -Both of the preceding examples create a `DirectMessageListenerContainer` (notice the `type` attribute -- it defaults to `simple`). - -Alternately, you may prefer to use Java configuration, which looks similar to the preceding code snippet: - -==== -[source,java] ----- -@Configuration -public class ExampleAmqpConfiguration { - - @Bean - public SimpleMessageListenerContainer messageListenerContainer() { - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); - container.setConnectionFactory(rabbitConnectionFactory()); - container.setQueueName("some.queue"); - container.setMessageListener(exampleListener()); - return container; - } - - @Bean - public CachingConnectionFactory rabbitConnectionFactory() { - CachingConnectionFactory connectionFactory = - new CachingConnectionFactory("localhost"); - connectionFactory.setUsername("guest"); - connectionFactory.setPassword("guest"); - return connectionFactory; - } - - @Bean - public MessageListener exampleListener() { - return new MessageListener() { - public void onMessage(Message message) { - System.out.println("received: " + message); - } - }; - } -} ----- -==== - -[[consumer-priority]] -====== Consumer Priority - -Starting with RabbitMQ Version 3.2, the broker now supports consumer priority (see https://www.rabbitmq.com/blog/2013/12/16/using-consumer-priorities-with-rabbitmq/[Using Consumer Priorities with RabbitMQ]). -This is enabled by setting the `x-priority` argument on the consumer. -The `SimpleMessageListenerContainer` now supports setting consumer arguments, as the following example shows: - -==== -[source,java] ----- - -container.setConsumerArguments(Collections. - singletonMap("x-priority", Integer.valueOf(10))); ----- -==== - -For convenience, the namespace provides the `priority` attribute on the `listener` element, as the following example shows: - -==== -[source,xml] ----- - - - ----- -==== - -Starting with version 1.3, you can modify the queues on which the container listens at runtime. -See <>. - -[[lc-auto-delete]] -====== `auto-delete` Queues - -When a container is configured to listen to `auto-delete` queues, the queue has an `x-expires` option, or the https://www.rabbitmq.com/ttl.html[Time-To-Live] policy is configured on the Broker, the queue is removed by the broker when the container is stopped (that is, when the last consumer is cancelled). -Before version 1.3, the container could not be restarted because the queue was missing. -The `RabbitAdmin` only automatically redeclares queues and so on when the connection is closed or when it opens, which does not happen when the container is stopped and started. - -Starting with version 1.3, the container uses a `RabbitAdmin` to redeclare any missing queues during startup. - -You can also use conditional declaration (see <>) together with an `auto-startup="false"` admin to defer queue declaration until the container is started. -The following example shows how to do so: - -==== -[source,xml] ----- - - - - - - - - - - - - - ----- -==== - -In this case, the queue and exchange are declared by `containerAdmin`, which has `auto-startup="false"` so that the elements are not declared during context initialization. -Also, the container is not started for the same reason. -When the container is later started, it uses its reference to `containerAdmin` to declare the elements. - -[[de-batching]] -===== Batched Messages - -Batched messages (created by a producer) are automatically de-batched by listener containers (using the `springBatchFormat` message header). -Rejecting any message from a batch causes the entire batch to be rejected. -See <> for more information about batching. - -Starting with version 2.2, the `SimpleMessageListenerContainer` can be use to create batches on the consumer side (where the producer sent discrete messages). - -Set the container property `consumerBatchEnabled` to enable this feature. -`deBatchingEnabled` must also be true so that the container is responsible for processing batches of both types. -Implement `BatchMessageListener` or `ChannelAwareBatchMessageListener` when `consumerBatchEnabled` is true. -Starting with version 2.2.7 both the `SimpleMessageListenerContainer` and `DirectMessageListenerContainer` can debatch <> as `List`. -See <> for information about using this feature with `@RabbitListener`. - -[[consumer-events]] -===== Consumer Events - -The containers publish application events whenever a listener -(consumer) experiences a failure of some kind. -The event `ListenerContainerConsumerFailedEvent` has the following properties: - -* `container`: The listener container where the consumer experienced the problem. -* `reason`: A textual reason for the failure. -* `fatal`: A boolean indicating whether the failure was fatal. -With non-fatal exceptions, the container tries to restart the consumer, according to the `recoveryInterval` or `recoveryBackoff` (for the `SimpleMessageListenerContainer`) or the `monitorInterval` (for the `DirectMessageListenerContainer`). -* `throwable`: The `Throwable` that was caught. - -These events can be consumed by implementing `ApplicationListener`. - -NOTE: System-wide events (such as connection failures) are published by all consumers when `concurrentConsumers` is greater than 1. - -If a consumer fails because one if its queues is being used exclusively, by default, as well as publishing the event, a `DEBUG` log is issued (since 3.1, previously WARN). -To change this logging behavior, provide a custom `ConditionalExceptionLogger` in the `AbstractMessageListenerContainer` instance's `exclusiveConsumerExceptionLogger` property. -In addition, the `SimpleMessageListenerContainer` consumer restart after such an exception is now logged at DEBUG level by default (previously INFO). -A new method `logRestart()` has been added to the `ConditionalExceptionLogger` to allow this to be changed. - -Also, the `AbstractMessageListenerContainer.DefaultExclusiveConsumerLogger` is now public, allowing it to be sub classed. - -See also <>. - -Fatal errors are always logged at the `ERROR` level. -This it not modifiable. - -Several other events are published at various stages of the container lifecycle: - -* `AsyncConsumerStartedEvent`: When the consumer is started. -* `AsyncConsumerRestartedEvent`: When the consumer is restarted after a failure - `SimpleMessageListenerContainer` only. -* `AsyncConsumerTerminatedEvent`: When a consumer is stopped normally. -* `AsyncConsumerStoppedEvent`: When the consumer is stopped - `SimpleMessageListenerContainer` only. -* `ConsumeOkEvent`: When a `consumeOk` is received from the broker, contains the queue name and `consumerTag` -* `ListenerContainerIdleEvent`: See <>. -* `MissingQueueEvent`: When a missing queue is detected. - -[[consumerTags]] -===== Consumer Tags - -You can provide a strategy to generate consumer tags. -By default, the consumer tag is generated by the broker. -The following listing shows the `ConsumerTagStrategy` interface definition: - -==== -[source,java] ----- -public interface ConsumerTagStrategy { - - String createConsumerTag(String queue); - -} ----- -==== - -The queue is made available so that it can (optionally) be used in the tag. - -See <>. - -[[async-annotation-driven]] -===== Annotation-driven Listener Endpoints - -The easiest way to receive a message asynchronously is to use the annotated listener endpoint infrastructure. -In a nutshell, it lets you expose a method of a managed bean as a Rabbit listener endpoint. -The following example shows how to use the `@RabbitListener` annotation: - -==== -[source,java] ----- - -@Component -public class MyService { - - @RabbitListener(queues = "myQueue") - public void processOrder(String data) { - ... - } - -} ----- -==== - -The idea of the preceding example is that, whenever a message is available on the queue named `myQueue`, the `processOrder` method is invoked accordingly (in this case, with the payload of the message). - -The annotated endpoint infrastructure creates a message listener container behind the scenes for each annotated method, by using a `RabbitListenerContainerFactory`. - -In the preceding example, `myQueue` must already exist and be bound to some exchange. -The queue can be declared and bound automatically, as long as a `RabbitAdmin` exists in the application context. - -NOTE: Property placeholders (`${some.property}`) or SpEL expressions (`#{someExpression}`) can be specified for the annotation properties (`queues` etc). -See <> for an example of why you might use SpEL instead of a property placeholder. -The following listing shows three examples of how to declare a Rabbit listener: - -==== -[source,java] ----- - -@Component -public class MyService { - - @RabbitListener(bindings = @QueueBinding( - value = @Queue(value = "myQueue", durable = "true"), - exchange = @Exchange(value = "auto.exch", ignoreDeclarationExceptions = "true"), - key = "orderRoutingKey") - ) - public void processOrder(Order order) { - ... - } - - @RabbitListener(bindings = @QueueBinding( - value = @Queue, - exchange = @Exchange(value = "auto.exch"), - key = "invoiceRoutingKey") - ) - public void processInvoice(Invoice invoice) { - ... - } - - @RabbitListener(queuesToDeclare = @Queue(name = "${my.queue}", durable = "true")) - public String handleWithSimpleDeclare(String data) { - ... - } - -} ----- -==== - -In the first example, a queue `myQueue` is declared automatically (durable) together with the exchange, if needed, -and bound to the exchange with the routing key. -In the second example, an anonymous (exclusive, auto-delete) queue is declared and bound; the queue name is created by the framework using the `Base64UrlNamingStrategy`. -You cannot declare broker-named queues using this technique; they need to be declared as bean definitions; see <>. -Multiple `QueueBinding` entries can be provided, letting the listener listen to multiple queues. -In the third example, a queue with the name retrieved from property `my.queue` is declared, if necessary, with the default binding to the default exchange using the queue name as the routing key. - -Since version 2.0, the `@Exchange` annotation supports any exchange types, including custom. -For more information, see https://www.rabbitmq.com/tutorials/amqp-concepts.html[AMQP Concepts]. - -You can use normal `@Bean` definitions when you need more advanced configuration. - -Notice `ignoreDeclarationExceptions` on the exchange in the first example. -This allows, for example, binding to an existing exchange that might have different settings (such as `internal`). -By default, the properties of an existing exchange must match. - -Starting with version 2.0, you can now bind a queue to an exchange with multiple routing keys, as the following example shows: - -==== -[source, java] ----- -... - key = { "red", "yellow" } -... ----- -==== - -You can also specify arguments within `@QueueBinding` annotations for queues, exchanges, -and bindings, as the following example shows: - -==== -[source, java] ----- -@RabbitListener(bindings = @QueueBinding( - value = @Queue(value = "auto.headers", autoDelete = "true", - arguments = @Argument(name = "x-message-ttl", value = "10000", - type = "java.lang.Integer")), - exchange = @Exchange(value = "auto.headers", type = ExchangeTypes.HEADERS, autoDelete = "true"), - arguments = { - @Argument(name = "x-match", value = "all"), - @Argument(name = "thing1", value = "somevalue"), - @Argument(name = "thing2") - }) -) -public String handleWithHeadersExchange(String foo) { - ... -} ----- -==== - -Notice that the `x-message-ttl` argument is set to 10 seconds for the queue. -Since the argument type is not `String`, we have to specify its type -- in this case, `Integer`. -As with all such declarations, if the queue already exists, the arguments must match those on the queue. -For the header exchange, we set the binding arguments to match messages that have the `thing1` header set to `somevalue`, and -the `thing2` header must be present with any value. -The `x-match` argument means both conditions must be satisfied. - -The argument name, value, and type can be property placeholders (`${...}`) or SpEL expressions (`#{...}`). -The `name` must resolve to a `String`. -The expression for `type` must resolve to a `Class` or the fully-qualified name of a class. -The `value` must resolve to something that can be converted by the `DefaultConversionService` to the type (such as the `x-message-ttl` in the preceding example). - -If a name resolves to `null` or an empty `String`, that `@Argument` is ignored. - -[[meta-annotation-driven]] -====== Meta-annotations - -Sometimes you may want to use the same configuration for multiple listeners. -To reduce the boilerplate configuration, you can use meta-annotations to create your own listener annotation. -The following example shows how to do so: - -==== -[source, java] ----- -@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@RabbitListener(bindings = @QueueBinding( - value = @Queue, - exchange = @Exchange(value = "metaFanout", type = ExchangeTypes.FANOUT))) -public @interface MyAnonFanoutListener { -} - -public class MetaListener { - - @MyAnonFanoutListener - public void handle1(String foo) { - ... - } - - @MyAnonFanoutListener - public void handle2(String foo) { - ... - } - -} ----- -==== - -In the preceding example, each listener created by the `@MyAnonFanoutListener` annotation binds an anonymous, auto-delete -queue to the fanout exchange, `metaFanout`. -Starting with version 2.2.3, `@AliasFor` is supported to allow overriding properties on the meta-annotated annotation. -Also, user annotations can now be `@Repeatable`, allowing multiple containers to be created for a method. - -==== -[source, java] ----- -@Component -static class MetaAnnotationTestBean { - - @MyListener("queue1") - @MyListener("queue2") - public void handleIt(String body) { - } - -} - - -@RabbitListener -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Repeatable(MyListeners.class) -static @interface MyListener { - - @AliasFor(annotation = RabbitListener.class, attribute = "queues") - String[] value() default {}; - -} - -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -static @interface MyListeners { - - MyListener[] value(); - -} ----- -==== - - -[[async-annotation-driven-enable]] -====== Enable Listener Endpoint Annotations - -To enable support for `@RabbitListener` annotations, you can add `@EnableRabbit` to one of your `@Configuration` classes. -The following example shows how to do so: - -==== -[source,java] ----- -@Configuration -@EnableRabbit -public class AppConfig { - - @Bean - public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() { - SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); - factory.setConnectionFactory(connectionFactory()); - factory.setConcurrentConsumers(3); - factory.setMaxConcurrentConsumers(10); - factory.setContainerCustomizer(container -> /* customize the container */); - return factory; - } -} ----- -==== - -Since version 2.0, a `DirectMessageListenerContainerFactory` is also available. -It creates `DirectMessageListenerContainer` instances. - -NOTE: For information to help you choose between `SimpleRabbitListenerContainerFactory` and `DirectRabbitListenerContainerFactory`, see <>. - -Starting with version 2.2.2, you can provide a `ContainerCustomizer` implementation (as shown above). -This can be used to further configure the container after it has been created and configured; you can use this, for example, to set properties that are not exposed by the container factory. - -Version 2.4.8 provides the `CompositeContainerCustomizer` for situations where you wish to apply multiple customizers. - -By default, the infrastructure looks for a bean named `rabbitListenerContainerFactory` as the source for the factory to use to create message listener containers. -In this case, and ignoring the RabbitMQ infrastructure setup, the `processOrder` method can be invoked with a core poll size of three threads and a maximum pool size of ten threads. - -You can customize the listener container factory to use for each annotation, or you can configure an explicit default by implementing the `RabbitListenerConfigurer` interface. -The default is required only if at least one endpoint is registered without a specific container factory. -See the https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/rabbit/annotation/RabbitListenerConfigurer.html[Javadoc] for full details and examples. - -The container factories provide methods for adding `MessagePostProcessor` instances that are applied after receiving messages (before invoking the listener) and before sending replies. - -See <> for information about replies. - -Starting with version 2.0.6, you can add a `RetryTemplate` and `RecoveryCallback` to the listener container factory. -It is used when sending replies. -The `RecoveryCallback` is invoked when retries are exhausted. -You can use a `SendRetryContextAccessor` to get information from the context. -The following example shows how to do so: - -==== -[source, java] ----- -factory.setRetryTemplate(retryTemplate); -factory.setReplyRecoveryCallback(ctx -> { - Message failed = SendRetryContextAccessor.getMessage(ctx); - Address replyTo = SendRetryContextAccessor.getAddress(ctx); - Throwable t = ctx.getLastThrowable(); - ... - return null; -}); ----- -==== - -If you prefer XML configuration, you can use the `` element. -Any beans annotated with `@RabbitListener` are detected. - -For `SimpleRabbitListenerContainer` instances, you can use XML similar to the following: - -==== -[source,xml] ----- - - - - - - - ----- -==== - -For `DirectMessageListenerContainer` instances, you can use XML similar to the following: - -==== -[source,xml] ----- - - - - - - ----- -==== - -[[listener-property-overrides]] - -Starting with version 2.0, the `@RabbitListener` annotation has a `concurrency` property. -It supports SpEL expressions (`#{...}`) and property placeholders (`${...}`). -Its meaning and allowed values depend on the container type, as follows: - -* For the `DirectMessageListenerContainer`, the value must be a single integer value, which sets the `consumersPerQueue` property on the container. -* For the `SimpleRabbitListenerContainer`, the value can be a single integer value, which sets the `concurrentConsumers` property on the container, or it can have the form, `m-n`, where `m` is the `concurrentConsumers` property and `n` is the `maxConcurrentConsumers` property. - -In either case, this setting overrides the settings on the factory. -Previously you had to define different container factories if you had listeners that required different concurrency. - -The annotation also allows overriding the factory `autoStartup` and `taskExecutor` properties via the `autoStartup` and `executor` (since 2.2) annotation properties. -Using a different executor for each might help with identifying threads associated with each listener in logs and thread dumps. - -Version 2.2 also added the `ackMode` property, which allows you to override the container factory's `acknowledgeMode` property. - -==== -[source, java] ----- -@RabbitListener(id = "manual.acks.1", queues = "manual.acks.1", ackMode = "MANUAL") -public void manual1(String in, Channel channel, - @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException { - - ... - channel.basicAck(tag, false); -} ----- -==== - -[[async-annotation-conversion]] -====== Message Conversion for Annotated Methods - -There are two conversion steps in the pipeline before invoking the listener. -The first step uses a `MessageConverter` to convert the incoming Spring AMQP `Message` to a Spring-messaging `Message`. -When the target method is invoked, the message payload is converted, if necessary, to the method parameter type. - -The default `MessageConverter` for the first step is a Spring AMQP `SimpleMessageConverter` that handles conversion to -`String` and `java.io.Serializable` objects. -All others remain as a `byte[]`. -In the following discussion, we call this the "`message converter`". - -The default converter for the second step is a `GenericMessageConverter`, which delegates to a conversion service -(an instance of `DefaultFormattingConversionService`). -In the following discussion, we call this the "`method argument converter`". - -To change the message converter, you can add it as a property to the container factory bean. -The following example shows how to do so: - -==== -[source, java] ----- -@Bean -public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() { - SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); - ... - factory.setMessageConverter(new Jackson2JsonMessageConverter()); - ... - return factory; -} ----- -==== - -This configures a Jackson2 converter that expects header information to be present to guide the conversion. - -You can also use a `ContentTypeDelegatingMessageConverter`, which can handle conversion of different content types. - -Starting with version 2.3, you can override the factory converter by specifying a bean name in the `messageConverter` property. - -==== -[source, java] ----- -@Bean -public Jackson2JsonMessageConverter jsonConverter() { - return new Jackson2JsonMessageConverter(); -} - -@RabbitListener(..., messageConverter = "jsonConverter") -public void listen(String in) { - ... -} ----- -==== - -This avoids having to declare a different container factory just to change the converter. - -In most cases, it is not necessary to customize the method argument converter unless, for example, you want to use -a custom `ConversionService`. - -In versions prior to 1.6, the type information to convert the JSON had to be provided in message headers, or a -custom `ClassMapper` was required. -Starting with version 1.6, if there are no type information headers, the type can be inferred from the target -method arguments. - -NOTE: This type inference works only for `@RabbitListener` at the method level. - -See <> for more information. - -If you wish to customize the method argument converter, you can do so as follows: - -==== -[source, java] ----- -@Configuration -@EnableRabbit -public class AppConfig implements RabbitListenerConfigurer { - - ... - - @Bean - public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() { - DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); - factory.setMessageConverter(new GenericMessageConverter(myConversionService())); - return factory; - } - - @Bean - public DefaultConversionService myConversionService() { - DefaultConversionService conv = new DefaultConversionService(); - conv.addConverter(mySpecialConverter()); - return conv; - } - - @Override - public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) { - registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory()); - } - - ... - -} ----- -==== - -IMPORTANT: For multi-method listeners (see <>), the method selection is based on the payload of the message *after the message conversion*. -The method argument converter is called only after the method has been selected. - -[[custom-argument-resolver]] -====== Adding a Custom `HandlerMethodArgumentResolver` to @RabbitListener - -Starting with version 2.3.7 you are able to add your own `HandlerMethodArgumentResolver` and resolve custom method parameters. -All you need is to implement `RabbitListenerConfigurer` and use method `setCustomMethodArgumentResolvers()` from class `RabbitListenerEndpointRegistrar`. - -==== -[source, java] ----- -@Configuration -class CustomRabbitConfig implements RabbitListenerConfigurer { - - @Override - public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) { - registrar.setCustomMethodArgumentResolvers( - new HandlerMethodArgumentResolver() { - - @Override - public boolean supportsParameter(MethodParameter parameter) { - return CustomMethodArgument.class.isAssignableFrom(parameter.getParameterType()); - } - - @Override - public Object resolveArgument(MethodParameter parameter, org.springframework.messaging.Message message) { - return new CustomMethodArgument( - (String) message.getPayload(), - message.getHeaders().get("customHeader", String.class) - ); - } - - } - ); - } - -} ----- -==== - -[[async-annotation-driven-registration]] -====== Programmatic Endpoint Registration - -`RabbitListenerEndpoint` provides a model of a Rabbit endpoint and is responsible for configuring the container for that model. -The infrastructure lets you configure endpoints programmatically in addition to the ones that are detected by the `RabbitListener` annotation. -The following example shows how to do so: - -==== -[source,java] ----- -@Configuration -@EnableRabbit -public class AppConfig implements RabbitListenerConfigurer { - - @Override - public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) { - SimpleRabbitListenerEndpoint endpoint = new SimpleRabbitListenerEndpoint(); - endpoint.setQueueNames("anotherQueue"); - endpoint.setMessageListener(message -> { - // processing - }); - registrar.registerEndpoint(endpoint); - } -} ----- -==== - -In the preceding example, we used `SimpleRabbitListenerEndpoint`, which provides the actual `MessageListener` to invoke, but you could just as well build your own endpoint variant to describe a custom invocation mechanism. - -It should be noted that you could just as well skip the use of `@RabbitListener` altogether and register your endpoints programmatically through `RabbitListenerConfigurer`. - -[[async-annotation-driven-enable-signature]] -====== Annotated Endpoint Method Signature - -So far, we have been injecting a simple `String` in our endpoint, but it can actually have a very flexible method signature. -The following example rewrites it to inject the `Order` with a custom header: - -==== -[source,java] ----- -@Component -public class MyService { - - @RabbitListener(queues = "myQueue") - public void processOrder(Order order, @Header("order_type") String orderType) { - ... - } -} ----- -==== - -The following list shows the arguments that are available to be matched with parameters in listener endpoints: - -* The raw `org.springframework.amqp.core.Message`. -* The `MessageProperties` from the raw `Message`. -* The `com.rabbitmq.client.Channel` on which the message was received. -* The `org.springframework.messaging.Message` converted from the incoming AMQP message. -* `@Header`-annotated method arguments to extract a specific header value, including standard AMQP headers. -* `@Headers`-annotated argument that must also be assignable to `java.util.Map` for getting access to all headers. -* The converted payload - -A non-annotated element that is not one of the supported types (that is, -`Message`, `MessageProperties`, `Message` and `Channel`) is matched with the payload. -You can make that explicit by annotating the parameter with `@Payload`. -You can also turn on validation by adding an extra `@Valid`. - -The ability to inject Spring’s message abstraction is particularly useful to benefit from all the information stored in the transport-specific message without relying on the transport-specific API. -The following example shows how to do so: - -==== -[source,java] ----- - -@RabbitListener(queues = "myQueue") -public void processOrder(Message order) { ... -} - ----- -==== - -Handling of method arguments is provided by `DefaultMessageHandlerMethodFactory`, which you can further customize to support additional method arguments. -The conversion and validation support can be customized there as well. - -For instance, if we want to make sure our `Order` is valid before processing it, we can annotate the payload with `@Valid` and configure the necessary validator, as follows: - -==== -[source,java] ----- - -@Configuration -@EnableRabbit -public class AppConfig implements RabbitListenerConfigurer { - - @Override - public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) { - registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory()); - } - - @Bean - public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() { - DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); - factory.setValidator(myValidator()); - return factory; - } -} ----- -==== - -[[rabbit-validation]] -====== @RabbitListener @Payload Validation - -Starting with version 2.3.7, it is now easier to add a `Validator` to validate `@RabbitListener` and `@RabbitHandler` `@Payload` arguments. -Now, you can simply add the validator to the registrar itself. - -==== -[source, java] ----- -@Configuration -@EnableRabbit -public class Config implements RabbitListenerConfigurer { - ... - @Override - public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) { - registrar.setValidator(new MyValidator()); - } -} ----- -==== - -NOTE: When using Spring Boot with the validation starter, a `LocalValidatorFactoryBean` is auto-configured: - -==== -[source, java] ----- -@Configuration -@EnableRabbit -public class Config implements RabbitListenerConfigurer { - @Autowired - private LocalValidatorFactoryBean validator; - ... - @Override - public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) { - registrar.setValidator(this.validator); - } -} ----- -==== - -To validate: - -==== -[source, java] ----- -public static class ValidatedClass { - @Max(10) - private int bar; - public int getBar() { - return this.bar; - } - public void setBar(int bar) { - this.bar = bar; - } -} ----- -==== - -and - -==== -[source, java] ----- -@RabbitListener(id="validated", queues = "queue1", errorHandler = "validationErrorHandler", - containerFactory = "jsonListenerContainerFactory") -public void validatedListener(@Payload @Valid ValidatedClass val) { - ... -} -@Bean -public RabbitListenerErrorHandler validationErrorHandler() { - return (m, e) -> { - ... - }; -} ----- -==== - -[[annotation-multiple-queues]] -====== Listening to Multiple Queues - -When you use the `queues` attribute, you can specify that the associated container can listen to multiple queues. -You can use a `@Header` annotation to make the queue name from which a message was received available to the POJO -method. -The following example shows how to do so: - -==== -[source, java] ----- -@Component -public class MyService { - - @RabbitListener(queues = { "queue1", "queue2" } ) - public void processOrder(String data, @Header(AmqpHeaders.CONSUMER_QUEUE) String queue) { - ... - } - -} ----- -==== - -Starting with version 1.5, you can externalize the queue names by using property placeholders and SpEL. -The following example shows how to do so: - -==== -[source, java] ----- -@Component -public class MyService { - - @RabbitListener(queues = "#{'${property.with.comma.delimited.queue.names}'.split(',')}" ) - public void processOrder(String data, @Header(AmqpHeaders.CONSUMER_QUEUE) String queue) { - ... - } - -} ----- -==== - -Prior to version 1.5, only a single queue could be specified this way. -Each queue needed a separate property. - -[[async-annotation-driven-reply]] -====== Reply Management - -The existing support in `MessageListenerAdapter` already lets your method have a non-void return type. -When that is the case, the result of the invocation is encapsulated in a message sent to the address specified in the `ReplyToAddress` header of the original message, or to the default address configured on the listener. -You can set that default address by using the `@SendTo` annotation of the messaging abstraction. - -Assuming our `processOrder` method should now return an `OrderStatus`, we can write it as follows to automatically send a reply: - -==== -[source,java] ----- -@RabbitListener(destination = "myQueue") -@SendTo("status") -public OrderStatus processOrder(Order order) { - // order processing - return status; -} ----- -==== - -If you need to set additional headers in a transport-independent manner, you could return a `Message` instead, something like the following: - -==== -[source,java] ----- - -@RabbitListener(destination = "myQueue") -@SendTo("status") -public Message processOrder(Order order) { - // order processing - return MessageBuilder - .withPayload(status) - .setHeader("code", 1234) - .build(); -} ----- -==== - -Alternatively, you can use a `MessagePostProcessor` in the `beforeSendReplyMessagePostProcessors` container factory property to add more headers. -Starting with version 2.2.3, the called bean/method is made available in the reply message, which can be used in a message post processor to communicate the information back to the caller: - -==== -[source, java] ----- -factory.setBeforeSendReplyPostProcessors(msg -> { - msg.getMessageProperties().setHeader("calledBean", - msg.getMessageProperties().getTargetBean().getClass().getSimpleName()); - msg.getMessageProperties().setHeader("calledMethod", - msg.getMessageProperties().getTargetMethod().getName()); - return m; -}); ----- -==== - -Starting with version 2.2.5, you can configure a `ReplyPostProcessor` to modify the reply message before it is sent; it is called after the `correlationId` header has been set up to match the request. - -==== -[source, java] ----- -@RabbitListener(queues = "test.header", group = "testGroup", replyPostProcessor = "echoCustomHeader") -public String capitalizeWithHeader(String in) { - return in.toUpperCase(); -} - -@Bean -public ReplyPostProcessor echoCustomHeader() { - return (req, resp) -> { - resp.getMessageProperties().setHeader("myHeader", req.getMessageProperties().getHeader("myHeader")); - return resp; - }; -} ----- -==== - -Starting with version 3.0, you can configure the post processor on the container factory instead of on the annotation. - -==== -[source, java] ----- -factory.setReplyPostProcessorProvider(id -> (req, resp) -> { - resp.getMessageProperties().setHeader("myHeader", req.getMessageProperties().getHeader("myHeader")); - return resp; -}); ----- -==== - -The `id` parameter is the listener id. - -A setting on the annotation will supersede the factory setting. - -The `@SendTo` value is assumed as a reply `exchange` and `routingKey` pair that follows the `exchange/routingKey` pattern, -where one of those parts can be omitted. -The valid values are as follows: - -* `thing1/thing2`: The `replyTo` exchange and the `routingKey`. -`thing1/`: The `replyTo` exchange and the default (empty) `routingKey`. -`thing2` or `/thing2`: The `replyTo` `routingKey` and the default (empty) exchange. -`/` or empty: The `replyTo` default exchange and the default `routingKey`. - -Also, you can use `@SendTo` without a `value` attribute. -This case is equal to an empty `sendTo` pattern. -`@SendTo` is used only if the inbound message does not have a `replyToAddress` property. - -Starting with version 1.5, the `@SendTo` value can be a bean initialization SpEL Expression, as shown in the following example: - -==== -[source, java] ----- -@RabbitListener(queues = "test.sendTo.spel") -@SendTo("#{spelReplyTo}") -public String capitalizeWithSendToSpel(String foo) { - return foo.toUpperCase(); -} -... -@Bean -public String spelReplyTo() { - return "test.sendTo.reply.spel"; -} ----- -==== - -The expression must evaluate to a `String`, which can be a simple queue name (sent to the default exchange) or with -the form `exchange/routingKey` as discussed prior to the preceding example. - -NOTE: The `#{...}` expression is evaluated once, during initialization. - -For dynamic reply routing, the message sender should include a `reply_to` message property or use the alternate -runtime SpEL expression (described after the next example). - -Starting with version 1.6, the `@SendTo` can be a SpEL expression that is evaluated at runtime against the request -and reply, as the following example shows: - -==== -[source, java] ----- -@RabbitListener(queues = "test.sendTo.spel") -@SendTo("!{'some.reply.queue.with.' + result.queueName}") -public Bar capitalizeWithSendToSpel(Foo foo) { - return processTheFooAndReturnABar(foo); -} ----- -==== - -The runtime nature of the SpEL expression is indicated with `!{...}` delimiters. -The evaluation context `#root` object for the expression has three properties: - -* `request`: The `o.s.amqp.core.Message` request object. -* `source`: The `o.s.messaging.Message` after conversion. -* `result`: The method result. - -The context has a map property accessor, a standard type converter, and a bean resolver, which lets other beans in the -context be referenced (for example, `@someBeanName.determineReplyQ(request, result)`). - -In summary, `#{...}` is evaluated once during initialization, with the `#root` object being the application context. -Beans are referenced by their names. -`!{...}` is evaluated at runtime for each message, with the root object having the properties listed earlier. -Beans are referenced with their names, prefixed by `@`. - -Starting with version 2.1, simple property placeholders are also supported (for example, `${some.reply.to}`). -With earlier versions, the following can be used as a work around, as the following example shows: - -==== -[source, java] ----- -@RabbitListener(queues = "foo") -@SendTo("#{environment['my.send.to']}") -public String listen(Message in) { - ... - return ... -} ----- -==== - -[[reply-content-type]] -====== Reply ContentType - -If you are using a sophisticated message converter, such as the `ContentTypeDelegatingMessageConverter`, you can control the content type of the reply by setting the `replyContentType` property on the listener. -This allows the converter to select the appropriate delegate converter for the reply. - -==== -[source, java] ----- -@RabbitListener(queues = "q1", messageConverter = "delegating", - replyContentType = "application/json") -public Thing2 listen(Thing1 in) { - ... -} ----- -==== - -By default, for backwards compatibility, any content type property set by the converter will be overwritten by this value after conversion. -Converters such as the `SimpleMessageConverter` use the reply type rather than the content type to determine the conversion needed and sets the content type in the reply message appropriately. -This may not be the desired action and can be overridden by setting the `converterWinsContentType` property to `false`. -For example, if you return a `String` containing JSON, the `SimpleMessageConverter` will set the content type in the reply to `text/plain`. -The following configuration will ensure the content type is set properly, even if the `SimpleMessageConverter` is used. - -==== -[source, java] ----- -@RabbitListener(queues = "q1", replyContentType = "application/json", - converterWinsContentType = "false") -public String listen(Thing in) { - ... - return someJsonString; -} ----- -==== - -These properties (`replyContentType` and `converterWinsContentType`) do not apply when the return type is a Spring AMQP `Message` or a Spring Messaging `Message`. -In the first case, there is no conversion involved; simply set the `contentType` message property. -In the second case, the behavior is controlled using message headers: - -==== -[source, java] ----- -@RabbitListener(queues = "q1", messageConverter = "delegating") -@SendTo("q2") -public Message listen(String in) { - ... - return MessageBuilder.withPayload(in.toUpperCase()) - .setHeader(MessageHeaders.CONTENT_TYPE, "application/xml") - .build(); -} ----- -==== - -This content type will be passed in the `MessageProperties` to the converter. -By default, for backwards compatibility, any content type property set by the converter will be overwritten by this value after conversion. -If you wish to override that behavior, also set the `AmqpHeaders.CONTENT_TYPE_CONVERTER_WINS` to `true` and any value set by the converter will be retained. - -[[annotation-method-selection]] -====== Multi-method Listeners - -Starting with version 1.5.0, you can specify the `@RabbitListener` annotation at the class level. -Together with the new `@RabbitHandler` annotation, this lets a single listener invoke different methods, based on -the payload type of the incoming message. -This is best described using an example: - -==== -[source, java] ----- -@RabbitListener(id="multi", queues = "someQueue") -@SendTo("my.reply.queue") -public class MultiListenerBean { - - @RabbitHandler - public String thing2(Thing2 thing2) { - ... - } - - @RabbitHandler - public String cat(Cat cat) { - ... - } - - @RabbitHandler - public String hat(@Header("amqp_receivedRoutingKey") String rk, @Payload Hat hat) { - ... - } - - @RabbitHandler(isDefault = true) - public String defaultMethod(Object object) { - ... - } - -} ----- -==== - -In this case, the individual `@RabbitHandler` methods are invoked if the converted payload is a `Thing2`, a `Cat`, or a `Hat`. -You should understand that the system must be able to identify a unique method based on the payload type. -The type is checked for assignability to a single parameter that has no annotations or that is annotated with the `@Payload` annotation. -Notice that the same method signatures apply, as discussed in the method-level `@RabbitListener` (<>). - -Starting with version 2.0.3, a `@RabbitHandler` method can be designated as the default method, which is invoked if there is no match on other methods. -At most, one method can be so designated. - -IMPORTANT: `@RabbitHandler` is intended only for processing message payloads after conversion, if you wish to receive the unconverted raw `Message` object, you must use `@RabbitListener` on the method, not the class. - -[[repeatable-rabbit-listener]] -====== `@Repeatable` `@RabbitListener` - -Starting with version 1.6, the `@RabbitListener` annotation is marked with `@Repeatable`. -This means that the annotation can appear on the same annotated element (method or class) multiple times. -In this case, a separate listener container is created for each annotation, each of which invokes the same listener -`@Bean`. -Repeatable annotations can be used with Java 8 or above. - -====== Proxy `@RabbitListener` and Generics - -If your service is intended to be proxied (for example, in the case of `@Transactional`), you should keep in mind some considerations when -the interface has generic parameters. -Consider the following example: - -==== -[source, java] ----- -interface TxService

{ - - String handle(P payload, String header); - -} - -static class TxServiceImpl implements TxService { - - @Override - @RabbitListener(...) - public String handle(Thing thing, String rk) { - ... - } - -} ----- -==== - -With a generic interface and a particular implementation, you are forced to switch to the CGLIB target class proxy because the actual implementation of the interface -`handle` method is a bridge method. -In the case of transaction management, the use of CGLIB is configured by using -an annotation option: `@EnableTransactionManagement(proxyTargetClass = true)`. -And in this case, all annotations have to be declared on the target method in the implementation, as the following example shows: - -==== -[source, java] ----- -static class TxServiceImpl implements TxService { - - @Override - @Transactional - @RabbitListener(...) - public String handle(@Payload Foo foo, @Header("amqp_receivedRoutingKey") String rk) { - ... - } - -} ----- -==== - -[[annotation-error-handling]] -====== Handling Exceptions - -By default, if an annotated listener method throws an exception, it is thrown to the container and the message are requeued and redelivered, discarded, or routed to a dead letter exchange, depending on the container and broker configuration. -Nothing is returned to the sender. - -Starting with version 2.0, the `@RabbitListener` annotation has two new attributes: `errorHandler` and `returnExceptions`. - -These are not configured by default. - -You can use the `errorHandler` to provide the bean name of a `RabbitListenerErrorHandler` implementation. -This functional interface has one method, as follows: - -[source, java] ----- -@FunctionalInterface -public interface RabbitListenerErrorHandler { - - Object handleError(Message amqpMessage, org.springframework.messaging.Message message, - ListenerExecutionFailedException exception) throws Exception; - -} ----- - -As you can see, you have access to the raw message received from the container, the spring-messaging `Message` object produced by the message converter, and the exception that was thrown by the listener (wrapped in a `ListenerExecutionFailedException`). -The error handler can either return some result (which is sent as the reply) or throw the original or a new exception (which is thrown to the container or returned to the sender, depending on the `returnExceptions` setting). - -The `returnExceptions` attribute, when `true`, causes exceptions to be returned to the sender. -The exception is wrapped in a `RemoteInvocationResult` object. -On the sender side, there is an available `RemoteInvocationAwareMessageConverterAdapter`, which, if configured into the `RabbitTemplate`, re-throws the server-side exception, wrapped in an `AmqpRemoteException`. -The stack trace of the server exception is synthesized by merging the server and client stack traces. - -IMPORTANT: This mechanism generally works only with the default `SimpleMessageConverter`, which uses Java serialization. -Exceptions are generally not "`Jackson-friendly`" and cannot be serialized to JSON. -If you use JSON, consider using an `errorHandler` to return some other Jackson-friendly `Error` object when an exception is thrown. - -IMPORTANT: In version 2.1, this interface moved from package `o.s.amqp.rabbit.listener` to `o.s.amqp.rabbit.listener.api`. - -Starting with version 2.1.7, the `Channel` is available in a messaging message header; this allows you to ack or nack the failed messasge when using `AcknowledgeMode.MANUAL`: - -==== -[source, java] ----- -public Object handleError(Message amqpMessage, org.springframework.messaging.Message message, - ListenerExecutionFailedException exception) { - ... - message.getHeaders().get(AmqpHeaders.CHANNEL, Channel.class) - .basicReject(message.getHeaders().get(AmqpHeaders.DELIVERY_TAG, Long.class), - true); - } ----- -==== - -Starting with version 2.2.18, if a message conversion exception is thrown, the error handler will be called, with `null` in the `message` argument. -This allows the application to send some result to the caller, indicating that a badly-formed message was received. -Previously, such errors were thrown and handled by the container. - -====== Container Management - -Containers created for annotations are not registered with the application context. -You can obtain a collection of all containers by invoking `getListenerContainers()` on the -`RabbitListenerEndpointRegistry` bean. -You can then iterate over this collection, for example, to stop or start all containers or invoke the `Lifecycle` methods -on the registry itself, which will invoke the operations on each container. - -You can also get a reference to an individual container by using its `id`, using `getListenerContainer(String id)` -- for -example, `registry.getListenerContainer("multi")` for the container created by the snippet above. - -Starting with version 1.5.2, you can obtain the `id` values of the registered containers with `getListenerContainerIds()`. - -Starting with version 1.5, you can now assign a `group` to the container on the `RabbitListener` endpoint. -This provides a mechanism to get a reference to a subset of containers. -Adding a `group` attribute causes a bean of type `Collection` to be registered with the context with the group name. - -By default, stopping a container will cancel the consumer and process all prefetched messages before stopping. -Starting with versions 2.4.14, 3.0.6, you can set the <> container property to true to stop immediately after the current message is processed, causing any prefetched messages to be requeued. -This is useful, for example, if exclusive or single-active consumers are being used. - -[[receiving-batch]] -===== @RabbitListener with Batching - -When receiving a <> of messages, the de-batching is normally performed by the container and the listener is invoked with one message at at time. -Starting with version 2.2, you can configure the listener container factory and listener to receive the entire batch in one call, simply set the factory's `batchListener` property, and make the method payload parameter a `List` or `Collection`: - -==== -[source, java] ----- -@Bean -public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() { - SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); - factory.setConnectionFactory(connectionFactory()); - factory.setBatchListener(true); - return factory; -} - -@RabbitListener(queues = "batch.1") -public void listen1(List in) { - ... -} - -// or - -@RabbitListener(queues = "batch.2") -public void listen2(List> in) { - ... -} ----- -==== - -Setting the `batchListener` property to true automatically turns off the `deBatchingEnabled` container property in containers that the factory creates (unless `consumerBatchEnabled` is `true` - see below). Effectively, the debatching is moved from the container to the listener adapter and the adapter creates the list that is passed to the listener. - -A batch-enabled factory cannot be used with a <>. - -Also starting with version 2.2. when receiving batched messages one-at-a-time, the last message contains a boolean header set to `true`. -This header can be obtained by adding the `@Header(AmqpHeaders.LAST_IN_BATCH)` boolean last` parameter to your listener method. -The header is mapped from `MessageProperties.isLastInBatch()`. -In addition, `AmqpHeaders.BATCH_SIZE` is populated with the size of the batch in every message fragment. - -In addition, a new property `consumerBatchEnabled` has been added to the `SimpleMessageListenerContainer`. -When this is true, the container will create a batch of messages, up to `batchSize`; a partial batch is delivered if `receiveTimeout` elapses with no new messages arriving. -If a producer-created batch is received, it is debatched and added to the consumer-side batch; therefore the actual number of messages delivered may exceed `batchSize`, which represents the number of messages received from the broker. -`deBatchingEnabled` must be true when `consumerBatchEnabled` is true; the container factory will enforce this requirement. - -==== -[source, java] ----- -@Bean -public SimpleRabbitListenerContainerFactory consumerBatchContainerFactory() { - SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); - factory.setConnectionFactory(rabbitConnectionFactory()); - factory.setConsumerTagStrategy(consumerTagStrategy()); - factory.setBatchListener(true); // configures a BatchMessageListenerAdapter - factory.setBatchSize(2); - factory.setConsumerBatchEnabled(true); - return factory; -} ----- -==== - -When using `consumerBatchEnabled` with `@RabbitListener`: - -==== -[source, java] ----- -@RabbitListener(queues = "batch.1", containerFactory = "consumerBatchContainerFactory") -public void consumerBatch1(List amqpMessages) { - ... -} - -@RabbitListener(queues = "batch.2", containerFactory = "consumerBatchContainerFactory") -public void consumerBatch2(List> messages) { - ... -} - -@RabbitListener(queues = "batch.3", containerFactory = "consumerBatchContainerFactory") -public void consumerBatch3(List strings) { - ... -} ----- -==== - -* the first is called with the raw, unconverted `org.springframework.amqp.core.Message` s received. -* the second is called with the `org.springframework.messaging.Message` s with converted payloads and mapped headers/properties. -* the third is called with the converted payloads, with no access to headers/properties. - -You can also add a `Channel` parameter, often used when using `MANUAL` ack mode. -This is not very useful with the third example because you don't have access to the `delivery_tag` property. - -Spring Boot provides a configuration property for `consumerBatchEnabled` and `batchSize`, but not for `batchListener`. -Starting with version 3.0, setting `consumerBatchEnabled` to `true` on the container factory also sets `batchListener` to `true`. -When `consumerBatchEnabled` is `true`, the listener **must** be a batch listener. - -Starting with version 3.0, listener methods can consume `Collection` or `List`. - -[[using-container-factories]] -===== Using Container Factories - -Listener container factories were introduced to support the `@RabbitListener` and registering containers with the `RabbitListenerEndpointRegistry`, as discussed in <>. - -Starting with version 2.1, they can be used to create any listener container -- even a container without a listener (such as for use in Spring Integration). -Of course, a listener must be added before the container is started. - -There are two ways to create such containers: - -* Use a SimpleRabbitListenerEndpoint -* Add the listener after creation - -The following example shows how to use a `SimpleRabbitListenerEndpoint` to create a listener container: - -==== -[source, java] ----- -@Bean -public SimpleMessageListenerContainer factoryCreatedContainerSimpleListener( - SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory) { - SimpleRabbitListenerEndpoint endpoint = new SimpleRabbitListenerEndpoint(); - endpoint.setQueueNames("queue.1"); - endpoint.setMessageListener(message -> { - ... - }); - return rabbitListenerContainerFactory.createListenerContainer(endpoint); -} ----- -==== - -The following example shows how to add the listener after creation: - -==== -[source, java] ----- -@Bean -public SimpleMessageListenerContainer factoryCreatedContainerNoListener( - SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory) { - SimpleMessageListenerContainer container = rabbitListenerContainerFactory.createListenerContainer(); - container.setMessageListener(message -> { - ... - }); - container.setQueueNames("test.no.listener.yet"); - return container; -} ----- -==== - -In either case, the listener can also be a `ChannelAwareMessageListener`, since it is now a sub-interface of `MessageListener`. - -These techniques are useful if you wish to create several containers with similar properties or use a pre-configured container factory such as the one provided by Spring Boot auto configuration or both. - -IMPORTANT: Containers created this way are normal `@Bean` instances and are not registered in the `RabbitListenerEndpointRegistry`. - -[[async-returns]] -===== Asynchronous `@RabbitListener` Return Types - -`@RabbitListener` (and `@RabbitHandler`) methods can be specified with asynchronous return types `CompletableFuture` and `Mono`, letting the reply be sent asynchronously. -`ListenableFuture` is no longer supported; it has been deprecated by Spring Framework. - -IMPORTANT: The listener container factory must be configured with `AcknowledgeMode.MANUAL` so that the consumer thread will not ack the message; instead, the asynchronous completion will ack or nack the message when the async operation completes. -When the async result is completed with an error, whether the message is requeued or not depends on the exception type thrown, the container configuration, and the container error handler. -By default, the message will be requeued, unless the container's `defaultRequeueRejected` property is set to `false` (it is `true` by default). -If the async result is completed with an `AmqpRejectAndDontRequeueException`, the message will not be requeued. -If the container's `defaultRequeueRejected` property is `false`, you can override that by setting the future's exception to a `ImmediateRequeueException` and the message will be requeued. -If some exception occurs within the listener method that prevents creation of the async result object, you MUST catch that exception and return an appropriate return object that will cause the message to be acknowledged or requeued. - -Starting with versions 2.2.21, 2.3.13, 2.4.1, the `AcknowledgeMode` will be automatically set the `MANUAL` when async return types are detected. -In addition, incoming messages with fatal exceptions will be negatively acknowledged individually, previously any prior unacknowledged message were also negatively acknowledged. - -Starting with version 3.0.5, the `@RabbitListener` (and `@RabbitHandler`) methods can be marked with Kotlin `suspend` and the whole handling process and reply producing (optional) happens on respective Kotlin coroutine. -All the mentioned rules about `AcknowledgeMode.MANUAL` are still apply. -The `org.jetbrains.kotlinx:kotlinx-coroutines-reactor` dependency must be present in classpath to allow `suspend` function invocations. - -Also starting with version 3.0.5, if a `RabbitListenerErrorHandler` is configured on a listener with an async return type (including Kotlin suspend functions), the error handler is invoked after a failure. -See <> for more information about this error handler and its purpose. - -[[threading]] -===== Threading and Asynchronous Consumers - -A number of different threads are involved with asynchronous consumers. - -Threads from the `TaskExecutor` configured in the `SimpleMessageListenerContainer` are used to invoke the `MessageListener` when a new message is delivered by `RabbitMQ Client`. -If not configured, a `SimpleAsyncTaskExecutor` is used. -If you use a pooled executor, you need to ensure the pool size is sufficient to handle the configured concurrency. -With the `DirectMessageListenerContainer`, the `MessageListener` is invoked directly on a `RabbitMQ Client` thread. -In this case, the `taskExecutor` is used for the task that monitors the consumers. - -NOTE: When using the default `SimpleAsyncTaskExecutor`, for the threads the listener is invoked on, the listener container `beanName` is used in the `threadNamePrefix`. -This is useful for log analysis. -We generally recommend always including the thread name in the logging appender configuration. -When a `TaskExecutor` is specifically provided through the `taskExecutor` property on the container, it is used as is, without modification. -It is recommended that you use a similar technique to name the threads created by a custom `TaskExecutor` bean definition, to aid with thread identification in log messages. - -The `Executor` configured in the `CachingConnectionFactory` is passed into the `RabbitMQ Client` when creating the connection, and its threads are used to deliver new messages to the listener container. -If this is not configured, the client uses an internal thread pool executor with (at the time of writing) a pool size of `Runtime.getRuntime().availableProcessors() * 2` for each connection. - -If you have a large number of factories or are using `CacheMode.CONNECTION`, you may wish to consider using a shared `ThreadPoolTaskExecutor` with enough threads to satisfy your workload. - -IMPORTANT: With the `DirectMessageListenerContainer`, you need to ensure that the connection factory is configured with a task executor that has sufficient threads to support your desired concurrency across all listener containers that use that factory. -The default pool size (at the time of writing) is `Runtime.getRuntime().availableProcessors() * 2`. - -The `RabbitMQ client` uses a `ThreadFactory` to create threads for low-level I/O (socket) operations. -To modify this factory, you need to configure the underlying RabbitMQ `ConnectionFactory`, as discussed in <>. - -[[choose-container]] -===== Choosing a Container - -Version 2.0 introduced the `DirectMessageListenerContainer` (DMLC). -Previously, only the `SimpleMessageListenerContainer` (SMLC) was available. -The SMLC uses an internal queue and a dedicated thread for each consumer. -If a container is configured to listen to multiple queues, the same consumer thread is used to process all the queues. -Concurrency is controlled by `concurrentConsumers` and other properties. -As messages arrive from the RabbitMQ client, the client thread hands them off to the consumer thread through the queue. -This architecture was required because, in early versions of the RabbitMQ client, multiple concurrent deliveries were not possible. -Newer versions of the client have a revised threading model and can now support concurrency. -This has allowed the introduction of the DMLC where the listener is now invoked directly on the RabbitMQ Client thread. -Its architecture is, therefore, actually "`simpler`" than the SMLC. -However, there are some limitations with this approach, and certain features of the SMLC are not available with the DMLC. -Also, concurrency is controlled by `consumersPerQueue` (and the client library's thread pool). -The `concurrentConsumers` and associated properties are not available with this container. - -The following features are available with the SMLC but not the DMLC: - -* `batchSize`: With the SMLC, you can set this to control how many messages are delivered in a transaction or to reduce the number of acks, but it may cause the number of duplicate deliveries to increase after a failure. -(The DMLC does have `messagesPerAck`, which you can use to reduce the acks, the same as with `batchSize` and the SMLC, but it cannot be used with transactions -- each message is delivered and ack'd in a separate transaction). -* `consumerBatchEnabled`: enables batching of discrete messages in the consumer; see <> for more information. -* `maxConcurrentConsumers` and consumer scaling intervals or triggers -- there is no auto-scaling in the DMLC. -It does, however, let you programmatically change the `consumersPerQueue` property and the consumers are adjusted accordingly. - -However, the DMLC has the following benefits over the SMLC: - -* Adding and removing queues at runtime is more efficient. -With the SMLC, the entire consumer thread is restarted (all consumers canceled and re-created). -With the DMLC, unaffected consumers are not canceled. -* The context switch between the RabbitMQ Client thread and the consumer thread is avoided. -* Threads are shared across consumers rather than having a dedicated thread for each consumer in the SMLC. -However, see the IMPORTANT note about the connection factory configuration in <>. - -See <> for information about which configuration properties apply to each container. - -[[idle-containers]] -===== Detecting Idle Asynchronous Consumers - -While efficient, one problem with asynchronous consumers is detecting when they are idle -- users might want to take -some action if no messages arrive for some period of time. - -Starting with version 1.6, it is now possible to configure the listener container to publish a -`ListenerContainerIdleEvent` when some time passes with no message delivery. -While the container is idle, an event is published every `idleEventInterval` milliseconds. - -To configure this feature, set `idleEventInterval` on the container. -The following example shows how to do so in XML and in Java (for both a `SimpleMessageListenerContainer` and a `SimpleRabbitListenerContainerFactory`): - -==== -[source, xml] ----- - - - ----- - -[source, java] ----- -@Bean -public SimpleMessageListenerContainer(ConnectionFactory connectionFactory) { - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory); - ... - container.setIdleEventInterval(60000L); - ... - return container; -} ----- - -[source, java] ----- -@Bean -public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() { - SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); - factory.setConnectionFactory(rabbitConnectionFactory()); - factory.setIdleEventInterval(60000L); - ... - return factory; -} ----- -==== - -In each of these cases, an event is published once per minute while the container is idle. - -====== Event Consumption - -You can capture idle events by implementing `ApplicationListener` -- either a general listener, or one narrowed to only -receive this specific event. -You can also use `@EventListener`, introduced in Spring Framework 4.2. - -The following example combines the `@RabbitListener` and `@EventListener` into a single class. -You need to understand that the application listener gets events for all containers, so you may need to -check the listener ID if you want to take specific action based on which container is idle. -You can also use the `@EventListener` `condition` for this purpose. - -The events have four properties: - -* `source`: The listener container instance -* `id`: The listener ID (or container bean name) -* `idleTime`: The time the container had been idle when the event was published -* `queueNames`: The names of the queue(s) that the container listens to - -The following example shows how to create listeners by using both the `@RabbitListener` and the `@EventListener` annotations: - -==== -[source, Java] ----- -public class Listener { - - @RabbitListener(id="someId", queues="#{queue.name}") - public String listen(String foo) { - return foo.toUpperCase(); - } - - @EventListener(condition = "event.listenerId == 'someId'") - public void onApplicationEvent(ListenerContainerIdleEvent event) { - ... - } - -} ----- -==== - -IMPORTANT: Event listeners see events for all containers. -Consequently, in the preceding example, we narrow the events received based on the listener ID. - -CAUTION: If you wish to use the idle event to stop the lister container, you should not call `container.stop()` on the thread that calls the listener. -Doing so always causes delays and unnecessary log messages. -Instead, you should hand off the event to a different thread that can then stop the container. - -[[micrometer]] -===== Monitoring Listener Performance - -Starting with version 2.2, the listener containers will automatically create and update Micrometer `Timer` s for the listener, if `Micrometer` is detected on the class path, and a single `MeterRegistry` is present in the application context (or exactly one is annotated `@Primary`, such as when using Spring Boot). -The timers can be disabled by setting the container property `micrometerEnabled` to `false`. - -Two timers are maintained - one for successful calls to the listener and one for failures. -With a simple `MessageListener`, there is a pair of timers for each configured queue. - -The timers are named `spring.rabbitmq.listener` and have the following tags: - -* `listenerId` : (listener id or container bean name) -* `queue` : (the queue name for a simple listener or list of configured queue names when `consumerBatchEnabled` is `true` - because a batch may contain messages from multiple queues) -* `result` : `success` or `failure` -* `exception` : `none` or `ListenerExecutionFailedException` - -You can add additional tags using the `micrometerTags` container property. - -Also see <>. - -[[micrometer-observation]] -===== Micrometer Observation - -Using Micrometer for observation is now supported, since version 3.0, for the `RabbitTemplate` and listener containers. - -Set `observationEnabled` on each component to enable observation; this will disable <> because the timers will now be managed with each observation. -When using annotated listeners, set `observationEnabled` on the container factory. - -Refer to https://micrometer.io/docs/tracing[Micrometer Tracing] for more information. - -To add tags to timers/traces, configure a custom `RabbitTemplateObservationConvention` or `RabbitListenerObservationConvention` to the template or listener container, respectively. - -The default implementations add the `name` tag for template observations and `listener.id` tag for containers. - -You can either subclass `DefaultRabbitTemplateObservationConvention` or `DefaultRabbitListenerObservationConvention` or provide completely new implementations. - -See <> for more details. - -[[containers-and-broker-named-queues]] -==== Containers and Broker-Named queues - -While it is preferable to use `AnonymousQueue` instances as auto-delete queues, starting with version 2.1, you can use broker named queues with listener containers. -The following example shows how to do so: - -==== -[source, java] ----- -@Bean -public Queue queue() { - return new Queue("", false, true, true); -} - -@Bean -public SimpleMessageListenerContainer container() { - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(cf()); - container.setQueues(queue()); - container.setMessageListener(m -> { - ... - }); - container.setMissingQueuesFatal(false); - return container; -} ----- -==== - -Notice the empty `String` for the name. -When the `RabbitAdmin` declares queues, it updates the `Queue.actualName` property with the name returned by the broker. -You must use `setQueues()` when you configure the container for this to work, so that the container can access the declared name at runtime. -Just setting the names is insufficient. - -NOTE: You cannot add broker-named queues to the containers while they are running. - -IMPORTANT: When a connection is reset and a new one is established, the new queue gets a new name. -Since there is a race condition between the container restarting and the queue being re-declared, it is important to set the container's `missingQueuesFatal` property to `false`, since the container is likely to initially try to reconnect to the old queue. - -[[message-converters]] -==== Message Converters - -The `AmqpTemplate` also defines several methods for sending and receiving messages that delegate to a `MessageConverter`. -The `MessageConverter` provides a single method for each direction: one for converting *to* a `Message` and another for converting *from* a `Message`. -Notice that, when converting to a `Message`, you can also provide properties in addition to the object. -The `object` parameter typically corresponds to the Message body. -The following listing shows the `MessageConverter` interface definition: - -==== -[source,java] ----- -public interface MessageConverter { - - Message toMessage(Object object, MessageProperties messageProperties) - throws MessageConversionException; - - Object fromMessage(Message message) throws MessageConversionException; - -} ----- -==== - -The relevant `Message`-sending methods on the `AmqpTemplate` are simpler than the methods we discussed previously, because they do not require the `Message` instance. -Instead, the `MessageConverter` is responsible for "`creating`" each `Message` by converting the provided object to the byte array for the `Message` body and then adding any provided `MessageProperties`. -The following listing shows the definitions of the various methods: - -==== -[source,java] ----- -void convertAndSend(Object message) throws AmqpException; - -void convertAndSend(String routingKey, Object message) throws AmqpException; - -void convertAndSend(String exchange, String routingKey, Object message) - throws AmqpException; - -void convertAndSend(Object message, MessagePostProcessor messagePostProcessor) - throws AmqpException; - -void convertAndSend(String routingKey, Object message, - MessagePostProcessor messagePostProcessor) throws AmqpException; - -void convertAndSend(String exchange, String routingKey, Object message, - MessagePostProcessor messagePostProcessor) throws AmqpException; ----- -==== - -On the receiving side, there are only two methods: one that accepts the queue name and one that relies on the template's "`queue`" property having been set. -The following listing shows the definitions of the two methods: - -==== -[source,java] ----- -Object receiveAndConvert() throws AmqpException; - -Object receiveAndConvert(String queueName) throws AmqpException; ----- -==== - -NOTE: The `MessageListenerAdapter` mentioned in <> also uses a `MessageConverter`. - -[[simple-message-converter]] -===== `SimpleMessageConverter` - -The default implementation of the `MessageConverter` strategy is called `SimpleMessageConverter`. -This is the converter that is used by an instance of `RabbitTemplate` if you do not explicitly configure an alternative. -It handles text-based content, serialized Java objects, and byte arrays. - -====== Converting From a `Message` - -If the content type of the input `Message` begins with "text" (for example, -"text/plain"), it also checks for the content-encoding property to determine the charset to be used when converting the `Message` body byte array to a Java `String`. -If no content-encoding property had been set on the input `Message`, it uses the UTF-8 charset by default. -If you need to override that default setting, you can configure an instance of `SimpleMessageConverter`, set its `defaultCharset` property, and inject that into a `RabbitTemplate` instance. - -If the content-type property value of the input `Message` is set to "application/x-java-serialized-object", the `SimpleMessageConverter` tries to deserialize (rehydrate) the byte array into a Java object. -While that might be useful for simple prototyping, we do not recommend relying on Java serialization, since it leads to tight coupling between the producer and the consumer. -Of course, it also rules out usage of non-Java systems on either side. -With AMQP being a wire-level protocol, it would be unfortunate to lose much of that advantage with such restrictions. -In the next two sections, we explore some alternatives for passing rich domain object content without relying on Java serialization. - -For all other content-types, the `SimpleMessageConverter` returns the `Message` body content directly as a byte array. - -See <> for important information. - -====== Converting To a `Message` - -When converting to a `Message` from an arbitrary Java Object, the `SimpleMessageConverter` likewise deals with byte arrays, strings, and serializable instances. -It converts each of these to bytes (in the case of byte arrays, there is nothing to convert), and it sets the content-type property accordingly. -If the `Object` to be converted does not match one of those types, the `Message` body is null. - -[[serializer-message-converter]] -===== `SerializerMessageConverter` - -This converter is similar to the `SimpleMessageConverter` except that it can be configured with other Spring Framework -`Serializer` and `Deserializer` implementations for `application/x-java-serialized-object` conversions. - -See <> for important information. - -[[json-message-converter]] -===== Jackson2JsonMessageConverter - -This section covers using the `Jackson2JsonMessageConverter` to convert to and from a `Message`. -It has the following sections: - -* <> -* <> - -[[Jackson2JsonMessageConverter-to-message]] -====== Converting to a `Message` - -As mentioned in the previous section, relying on Java serialization is generally not recommended. -One rather common alternative that is more flexible and portable across different languages and platforms is JSON -(JavaScript Object Notation). -The converter can be configured on any `RabbitTemplate` instance to override its usage of the `SimpleMessageConverter` -default. -The `Jackson2JsonMessageConverter` uses the `com.fasterxml.jackson` 2.x library. -The following example configures a `Jackson2JsonMessageConverter`: - -==== -[source,xml] ----- - - - - - - - - - ----- -==== - -As shown above, `Jackson2JsonMessageConverter` uses a `DefaultClassMapper` by default. -Type information is added to (and retrieved from) `MessageProperties`. -If an inbound message does not contain type information in `MessageProperties`, but you know the expected type, you -can configure a static type by using the `defaultType` property, as the following example shows: - -==== -[source,xml] ----- - - - - - - - ----- -==== - -In addition, you can provide custom mappings from the value in the `__TypeId__` header. -The following example shows how to do so: - -==== -[source, java] ----- -@Bean -public Jackson2JsonMessageConverter jsonMessageConverter() { - Jackson2JsonMessageConverter jsonConverter = new Jackson2JsonMessageConverter(); - jsonConverter.setClassMapper(classMapper()); - return jsonConverter; -} - -@Bean -public DefaultClassMapper classMapper() { - DefaultClassMapper classMapper = new DefaultClassMapper(); - Map> idClassMapping = new HashMap<>(); - idClassMapping.put("thing1", Thing1.class); - idClassMapping.put("thing2", Thing2.class); - classMapper.setIdClassMapping(idClassMapping); - return classMapper; -} ----- -==== - -Now, if the sending system sets the header to `thing1`, the converter creates a `Thing1` object, and so on. -See the <> sample application for a complete discussion about converting messages from non-Spring applications. - -Starting with version 2.4.3, the converter will not add a `contentEncoding` message property if the `supportedMediaType` has a `charset` parameter; this is also used for the encoding. -A new method `setSupportedMediaType` has been added: - -==== -[source, java] ----- -String utf16 = "application/json; charset=utf-16"; -converter.setSupportedContentType(MimeTypeUtils.parseMimeType(utf16)); ----- -==== - -[[Jackson2JsonMessageConverter-from-message]] -====== Converting from a `Message` - -Inbound messages are converted to objects according to the type information added to headers by the sending system. - -Starting with version 2.4.3, if there is no `contentEncoding` message property, the converter will attempt to detect a `charset` parameter in the `contentType` message property and use that. -If neither exist, if the `supportedMediaType` has a `charset` parameter, it will be used for decoding, with a final fallback to the `defaultCharset` property. -A new method `setSupportedMediaType` has been added: - -==== -[source, java] ----- -String utf16 = "application/json; charset=utf-16"; -converter.setSupportedContentType(MimeTypeUtils.parseMimeType(utf16)); ----- -==== - -In versions prior to 1.6, if type information is not present, conversion would fail. -Starting with version 1.6, if type information is missing, the converter converts the JSON by using Jackson defaults (usually a map). - -Also, starting with version 1.6, when you use `@RabbitListener` annotations (on methods), the inferred type information is added to the `MessageProperties`. -This lets the converter convert to the argument type of the target method. -This only applies if there is one parameter with no annotations or a single parameter with the `@Payload` annotation. -Parameters of type `Message` are ignored during the analysis. - -IMPORTANT: By default, the inferred type information will override the inbound `__TypeId__` and related headers created -by the sending system. -This lets the receiving system automatically convert to a different domain object. -This applies only if the parameter type is concrete (not abstract or an interface) or it is from the `java.util` -package. -In all other cases, the `__TypeId__` and related headers is used. -There are cases where you might wish to override the default behavior and always use the `__TypeId__` information. -For example, suppose you have a `@RabbitListener` that takes a `Thing1` argument but the message contains a `Thing2` that -is a subclass of `Thing1` (which is concrete). -The inferred type would be incorrect. -To handle this situation, set the `TypePrecedence` property on the `Jackson2JsonMessageConverter` to `TYPE_ID` instead -of the default `INFERRED`. -(The property is actually on the converter's `DefaultJackson2JavaTypeMapper`, but a setter is provided on the converter -for convenience.) -If you inject a custom type mapper, you should set the property on the mapper instead. - -NOTE: When converting from the `Message`, an incoming `MessageProperties.getContentType()` must be JSON-compliant (`contentType.contains("json")` is used to check). -Starting with version 2.2, `application/json` is assumed if there is no `contentType` property, or it has the default value `application/octet-stream`. -To revert to the previous behavior (return an unconverted `byte[]`), set the converter's `assumeSupportedContentType` property to `false`. -If the content type is not supported, a `WARN` log message `Could not convert incoming message with content-type [...]`, is emitted and `message.getBody()` is returned as is -- as a `byte[]`. -So, to meet the `Jackson2JsonMessageConverter` requirements on the consumer side, the producer must add the `contentType` message property -- for example, as `application/json` or `text/x-json` or by using the `Jackson2JsonMessageConverter`, which sets the header automatically. -The following listing shows a number of converter calls: - -==== -[source, java] ----- -@RabbitListener -public void thing1(Thing1 thing1) {...} - -@RabbitListener -public void thing1(@Payload Thing1 thing1, @Header("amqp_consumerQueue") String queue) {...} - -@RabbitListener -public void thing1(Thing1 thing1, o.s.amqp.core.Message message) {...} - -@RabbitListener -public void thing1(Thing1 thing1, o.s.messaging.Message message) {...} - -@RabbitListener -public void thing1(Thing1 thing1, String bar) {...} - -@RabbitListener -public void thing1(Thing1 thing1, o.s.messaging.Message message) {...} ----- -==== - -In the first four cases in the preceding listing, the converter tries to convert to the `Thing1` type. -The fifth example is invalid because we cannot determine which argument should receive the message payload. -With the sixth example, the Jackson defaults apply due to the generic type being a `WildcardType`. - -You can, however, create a custom converter and use the `targetMethod` message property to decide which type to convert -the JSON to. - -NOTE: This type inference can only be achieved when the `@RabbitListener` annotation is declared at the method level. -With class-level `@RabbitListener`, the converted type is used to select which `@RabbitHandler` method to invoke. -For this reason, the infrastructure provides the `targetObject` message property, which you can use in a custom -converter to determine the type. - -IMPORTANT: Starting with version 1.6.11, `Jackson2JsonMessageConverter` and, therefore, `DefaultJackson2JavaTypeMapper` (`DefaultClassMapper`) provide the `trustedPackages` option to overcome https://pivotal.io/security/cve-2017-4995[Serialization Gadgets] vulnerability. -By default and for backward compatibility, the `Jackson2JsonMessageConverter` trusts all packages -- that is, it uses `*` for the option. - -Starting with version 2.4.7, the converter can be configured to return `Optional.empty()` if Jackson returns `null` after deserializing the message body. -This facilitates `@RabbitListener` s to receive null payloads, in two ways: - -==== -[source, java] ----- -@RabbitListener(queues = "op.1") -void listen(@Payload(required = false) Thing payload) { - handleOptional(payload); // payload might be null -} - -@RabbitListener(queues = "op.2") -void listen(Optional optional) { - handleOptional(optional.orElse(this.emptyThing)); -} ----- -==== - -To enable this feature, set `setNullAsOptionalEmpty` to `true`; when `false` (default), the converter falls back to the raw message body (`byte[]`). - -==== -[source, java] ----- -@Bean -Jackson2JsonMessageConverter converter() { - Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(); - converter.setNullAsOptionalEmpty(true); - return converter; -} ----- -==== - -[[jackson-abstract]] -====== Deserializing Abstract Classes - -Prior to version 2.2.8, if the inferred type of a `@RabbitListener` was an abstract class (including interfaces), the converter would fall back to looking for type information in the headers and, if present, used that information; if that was not present, it would try to create the abstract class. -This caused a problem when a custom `ObjectMapper` that is configured with a custom deserializer to handle the abstract class is used, but the incoming message has invalid type headers. - -Starting with version 2.2.8, the previous behavior is retained by default. If you have such a custom `ObjectMapper` and you want to ignore type headers, and always use the inferred type for conversion, set the `alwaysConvertToInferredType` to `true`. -This is needed for backwards compatibility and to avoid the overhead of an attempted conversion when it would fail (with a standard `ObjectMapper`). - -[[data-projection]] -====== Using Spring Data Projection Interfaces - -Starting with version 2.2, you can convert JSON to a Spring Data Projection interface instead of a concrete type. -This allows very selective, and low-coupled bindings to data, including the lookup of values from multiple places inside the JSON document. -For example the following interface can be defined as message payload type: - -==== -[source, java] ----- -interface SomeSample { - - @JsonPath({ "$.username", "$.user.name" }) - String getUsername(); - -} ----- -==== - -==== -[source, java] ----- -@RabbitListener(queues = "projection") -public void projection(SomeSample in) { - String username = in.getUsername(); - ... -} ----- -==== - -Accessor methods will be used to lookup the property name as field in the received JSON document by default. -The `@JsonPath` expression allows customization of the value lookup, and even to define multiple JSON path expressions, to lookup values from multiple places until an expression returns an actual value. - -To enable this feature, set the `useProjectionForInterfaces` to `true` on the message converter. -You must also add `spring-data:spring-data-commons` and `com.jayway.jsonpath:json-path` to the class path. - -When used as the parameter to a `@RabbitListener` method, the interface type is automatically passed to the converter as normal. - -[[json-complex]] -====== Converting From a `Message` With `RabbitTemplate` - -As mentioned earlier, type information is conveyed in message headers to assist the converter when converting from a message. -This works fine in most cases. -However, when using generic types, it can only convert simple objects and known "`container`" objects (lists, arrays, and maps). -Starting with version 2.0, the `Jackson2JsonMessageConverter` implements `SmartMessageConverter`, which lets it be used with the new `RabbitTemplate` methods that take a `ParameterizedTypeReference` argument. -This allows conversion of complex generic types, as shown in the following example: - -==== -[source, java] ----- -Thing1> thing1 = - rabbitTemplate.receiveAndConvert(new ParameterizedTypeReference>>() { }); ----- -==== - -NOTE: Starting with version 2.1, the `AbstractJsonMessageConverter` class has been removed. -It is no longer the base class for `Jackson2JsonMessageConverter`. -It has been replaced by `AbstractJackson2MessageConverter`. - -===== `MarshallingMessageConverter` - -Yet another option is the `MarshallingMessageConverter`. -It delegates to the Spring OXM library's implementations of the `Marshaller` and `Unmarshaller` strategy interfaces. -You can read more about that library https://docs.spring.io/spring/docs/current/spring-framework-reference/html/oxm.html[here]. -In terms of configuration, it is most common to provide only the constructor argument, since most implementations of `Marshaller` also implement `Unmarshaller`. -The following example shows how to configure a `MarshallingMessageConverter`: - -==== -[source,xml] ----- - - - - - - - - ----- -==== - -[[jackson2xml]] -===== `Jackson2XmlMessageConverter` - -This class was introduced in version 2.1 and can be used to convert messages from and to XML. - -Both `Jackson2XmlMessageConverter` and `Jackson2JsonMessageConverter` have the same base class: `AbstractJackson2MessageConverter`. - -NOTE: The `AbstractJackson2MessageConverter` class is introduced to replace a removed class: `AbstractJsonMessageConverter`. - -The `Jackson2XmlMessageConverter` uses the `com.fasterxml.jackson` 2.x library. - -You can use it the same way as `Jackson2JsonMessageConverter`, except it supports XML instead of JSON. -The following example configures a `Jackson2JsonMessageConverter`: - -[source,xml] ----- - - - - - - - ----- -See <> for more information. - -NOTE: Starting with version 2.2, `application/xml` is assumed if there is no `contentType` property, or it has the default value `application/octet-stream`. -To revert to the previous behavior (return an unconverted `byte[]`), set the converter's `assumeSupportedContentType` property to `false`. - -===== `ContentTypeDelegatingMessageConverter` - -This class was introduced in version 1.4.2 and allows delegation to a specific `MessageConverter` based on the content type property in the `MessageProperties`. -By default, it delegates to a `SimpleMessageConverter` if there is no `contentType` property or there is a value that matches none of the configured converters. -The following example configures a `ContentTypeDelegatingMessageConverter`: - -==== -[source,xml] ----- - - - - - - - - ----- -==== - -[[java-deserialization]] -===== Java Deserialization - -This section covers how to deserialize Java objects. - -[IMPORTANT] -==== -There is a possible vulnerability when deserializing java objects from untrusted sources. - -If you accept messages from untrusted sources with a `content-type` of `application/x-java-serialized-object`, you should -consider configuring which packages and classes are allowed to be deserialized. -This applies to both the `SimpleMessageConverter` and `SerializerMessageConverter` when it is configured to use a -`DefaultDeserializer` either implicitly or via configuration. - -By default, the allowed list is empty, meaning no classes will be deserialized. - -You can set a list of patterns, such as `thing1.*`, `thing1.thing2.Cat` or `*.MySafeClass`. - -The patterns are checked in order until a match is found. -If there is no match, a `SecurityException` is thrown. - -You can set the patterns using the `allowedListPatterns` property on these converters. -Alternatively, if you trust all message originators, you can set the environment variable `SPRING_AMQP_DESERIALIZATION_TRUST_ALL` or system property `spring.amqp.deserialization.trust.all` to `true`. -==== - -[[message-properties-converters]] -===== Message Properties Converters - -The `MessagePropertiesConverter` strategy interface is used to convert between the Rabbit Client `BasicProperties` and Spring AMQP `MessageProperties`. -The default implementation (`DefaultMessagePropertiesConverter`) is usually sufficient for most purposes, but you can implement your own if needed. -The default properties converter converts `BasicProperties` elements of type `LongString` to `String` instances when the size is not greater than `1024` bytes. -Larger `LongString` instances are not converted (see the next paragraph). -This limit can be overridden with a constructor argument. - -Starting with version 1.6, headers longer than the long string limit (default: 1024) are now left as -`LongString` instances by default by the `DefaultMessagePropertiesConverter`. -You can access the contents through the `getBytes[]`, `toString()`, or `getStream()` methods. - -Previously, the `DefaultMessagePropertiesConverter` "`converted`" such headers to a `DataInputStream` (actually it just referenced the `LongString` instance's `DataInputStream`). -On output, this header was not converted (except to a String -- for example, `java.io.DataInputStream@1d057a39` by calling `toString()` on the stream). - -Large incoming `LongString` headers are now correctly "`converted`" on output, too (by default). - -A new constructor is provided to let you configure the converter to work as before. -The following listing shows the Javadoc comment and declaration of the method: - -==== -[source, java] ----- -/** - * Construct an instance where LongStrings will be returned - * unconverted or as a java.io.DataInputStream when longer than this limit. - * Use this constructor with 'true' to restore pre-1.6 behavior. - * @param longStringLimit the limit. - * @param convertLongLongStrings LongString when false, - * DataInputStream when true. - * @since 1.6 - */ -public DefaultMessagePropertiesConverter(int longStringLimit, boolean convertLongLongStrings) { ... } ----- -==== - -Also starting with version 1.6, a new property called `correlationIdString` has been added to `MessageProperties`. -Previously, when converting to and from `BasicProperties` used by the RabbitMQ client, an unnecessary `byte[] <-> String` conversion was performed because `MessageProperties.correlationId` is a `byte[]`, but `BasicProperties` uses a `String`. -(Ultimately, the RabbitMQ client uses UTF-8 to convert the `String` to bytes to put in the protocol message). - -To provide maximum backwards compatibility, a new property called `correlationIdPolicy` has been added to the -`DefaultMessagePropertiesConverter`. -This takes a `DefaultMessagePropertiesConverter.CorrelationIdPolicy` enum argument. -By default it is set to `BYTES`, which replicates the previous behavior. - -For inbound messages: - -* `STRING`: Only the `correlationIdString` property is mapped -* `BYTES`: Only the `correlationId` property is mapped -* `BOTH`: Both properties are mapped - -For outbound messages: - -* `STRING`: Only the `correlationIdString` property is mapped -* `BYTES`: Only the `correlationId` property is mapped -* `BOTH`: Both properties are considered, with the `String` property taking precedence - -Also starting with version 1.6, the inbound `deliveryMode` property is no longer mapped to `MessageProperties.deliveryMode`. -It is mapped to `MessageProperties.receivedDeliveryMode` instead. -Also, the inbound `userId` property is no longer mapped to `MessageProperties.userId`. -It is mapped to `MessageProperties.receivedUserId` instead. -These changes are to avoid unexpected propagation of these properties if the same `MessageProperties` object is used for an outbound message. - -Starting with version 2.2, the `DefaultMessagePropertiesConverter` converts any custom headers with values of type `Class` using `getName()` instead of `toString()`; this avoids consuming application having to parse the class name out of the `toString()` representation. -For rolling upgrades, you may need to change your consumers to understand both formats until all producers are upgraded. - -[[post-processing]] -==== Modifying Messages - Compression and More - -A number of extension points exist. -They let you perform some processing on a message, either before it is sent to RabbitMQ or immediately after it is received. - -As can be seen in <>, one such extension point is in the `AmqpTemplate` `convertAndReceive` operations, where you can provide a `MessagePostProcessor`. -For example, after your POJO has been converted, the `MessagePostProcessor` lets you set custom headers or properties on the `Message`. - -Starting with version 1.4.2, additional extension points have been added to the `RabbitTemplate` - `setBeforePublishPostProcessors()` and `setAfterReceivePostProcessors()`. -The first enables a post processor to run immediately before sending to RabbitMQ. -When using batching (see <>), this is invoked after the batch is assembled and before the batch is sent. -The second is invoked immediately after a message is received. - -These extension points are used for such features as compression and, for this purpose, several `MessagePostProcessor` implementations are provided. -`GZipPostProcessor`, `ZipPostProcessor` and `DeflaterPostProcessor` compress messages before sending, and `GUnzipPostProcessor`, `UnzipPostProcessor` and `InflaterPostProcessor` decompress received messages. - -NOTE: Starting with version 2.1.5, the `GZipPostProcessor` can be configured with the `copyProperties = true` option to make a copy of the original message properties. -By default, these properties are reused for performance reasons, and modified with compression content encoding and the optional `MessageProperties.SPRING_AUTO_DECOMPRESS` header. -If you retain a reference to the original outbound message, its properties will change as well. -So, if your application retains a copy of an outbound message with these message post processors, consider turning the `copyProperties` option on. - -IMPORTANT: Starting with version 2.2.12, you can configure the delimiter that the compressing post processors use between content encoding elements. -With versions 2.2.11 and before, this was hard-coded as `:`, it is now set to `, ` by default. -The decompressors will work with both delimiters. -However, if you publish messages with 2.3 or later and consume with 2.2.11 or earlier, you MUST set the `encodingDelimiter` property on the compressor(s) to `:`. -When your consumers are upgraded to 2.2.11 or later, you can revert to the default of `, `. - -Similarly, the `SimpleMessageListenerContainer` also has a `setAfterReceivePostProcessors()` method, letting the decompression be performed after messages are received by the container. - -Starting with version 2.1.4, `addBeforePublishPostProcessors()` and `addAfterReceivePostProcessors()` have been added to the `RabbitTemplate` to allow appending new post processors to the list of before publish and after receive post processors respectively. -Also there are methods provided to remove the post processors. -Similarly, `AbstractMessageListenerContainer` also has `addAfterReceivePostProcessors()` and `removeAfterReceivePostProcessor()` methods added. -See the Javadoc of `RabbitTemplate` and `AbstractMessageListenerContainer` for more detail. - -[[request-reply]] -==== Request/Reply Messaging - -The `AmqpTemplate` also provides a variety of `sendAndReceive` methods that accept the same argument options that were described earlier for the one-way send operations (`exchange`, `routingKey`, and `Message`). -Those methods are quite useful for request-reply scenarios, since they handle the configuration of the necessary `reply-to` property before sending and can listen for the reply message on an exclusive queue that is created internally for that purpose. - -Similar request-reply methods are also available where the `MessageConverter` is applied to both the request and reply. -Those methods are named `convertSendAndReceive`. -See the https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/core/AmqpTemplate.html[Javadoc of `AmqpTemplate`] for more detail. - -Starting with version 1.5.0, each of the `sendAndReceive` method variants has an overloaded version that takes `CorrelationData`. -Together with a properly configured connection factory, this enables the receipt of publisher confirms for the send side of the operation. -See <> and the https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/rabbit/core/RabbitOperations.html[Javadoc for `RabbitOperations`] for more information. - -Starting with version 2.0, there are variants of these methods (`convertSendAndReceiveAsType`) that take an additional `ParameterizedTypeReference` argument to convert complex returned types. -The template must be configured with a `SmartMessageConverter`. -See <> for more information. - -Starting with version 2.1, you can configure the `RabbitTemplate` with the `noLocalReplyConsumer` option to control a `noLocal` flag for reply consumers. -This is `false` by default. - -[[reply-timeout]] -===== Reply Timeout - -By default, the send and receive methods timeout after five seconds and return null. -You can modify this behavior by setting the `replyTimeout` property. -Starting with version 1.5, if you set the `mandatory` property to `true` (or the `mandatory-expression` evaluates to `true` for a particular message), if the message cannot be delivered to a queue, an `AmqpMessageReturnedException` is thrown. -This exception has `returnedMessage`, `replyCode`, and `replyText` properties, as well as the `exchange` and `routingKey` used for the send. - -NOTE: This feature uses publisher returns. -You can enable it by setting `publisherReturns` to `true` on the `CachingConnectionFactory` (see <>). -Also, you must not have registered your own `ReturnCallback` with the `RabbitTemplate`. - -Starting with version 2.1.2, a `replyTimedOut` method has been added, letting subclasses be informed of the timeout so that they can clean up any retained state. - -Starting with versions 2.0.11 and 2.1.3, when you use the default `DirectReplyToMessageListenerContainer`, you can add an error handler by setting the template's `replyErrorHandler` property. -This error handler is invoked for any failed deliveries, such as late replies and messages received without a correlation header. -The exception passed in is a `ListenerExecutionFailedException`, which has a `failedMessage` property. - -[[direct-reply-to]] -===== RabbitMQ Direct reply-to - -IMPORTANT: Starting with version 3.4.0, the RabbitMQ server supports https://www.rabbitmq.com/direct-reply-to.html[direct reply-to]. -This eliminates the main reason for a fixed reply queue (to avoid the need to create a temporary queue for each request). -Starting with Spring AMQP version 1.4.1 direct reply-to is used by default (if supported by the server) instead of creating temporary reply queues. -When no `replyQueue` is provided (or it is set with a name of `amq.rabbitmq.reply-to`), the `RabbitTemplate` automatically detects whether direct reply-to is supported and either uses it or falls back to using a temporary reply queue. -When using direct reply-to, a `reply-listener` is not required and should not be configured. - -Reply listeners are still supported with named queues (other than `amq.rabbitmq.reply-to`), allowing control of reply concurrency and so on. - -Starting with version 1.6, if you wish to use a temporary, exclusive, auto-delete queue for each -reply, set the `useTemporaryReplyQueues` property to `true`. -This property is ignored if you set a `replyAddress`. - -You can change the criteria that dictate whether to use direct reply-to by subclassing `RabbitTemplate` and overriding `useDirectReplyTo()` to check different criteria. -The method is called once only, when the first request is sent. - -Prior to version 2.0, the `RabbitTemplate` created a new consumer for each request and canceled the consumer when the reply was received (or timed out). -Now the template uses a `DirectReplyToMessageListenerContainer` instead, letting the consumers be reused. -The template still takes care of correlating the replies, so there is no danger of a late reply going to a different sender. -If you want to revert to the previous behavior, set the `useDirectReplyToContainer` (`direct-reply-to-container` when using XML configuration) property to false. - -The `AsyncRabbitTemplate` has no such option. -It always used a `DirectReplyToContainer` for replies when direct reply-to is used. - -Starting with version 2.3.7, the template has a new property `useChannelForCorrelation`. -When this is `true`, the server does not have to copy the correlation id from the request message headers to the reply message. -Instead, the channel used to send the request is used to correlate the reply to the request. - -===== Message Correlation With A Reply Queue - -When using a fixed reply queue (other than `amq.rabbitmq.reply-to`), you must provide correlation data so that replies can be correlated to requests. -See https://www.rabbitmq.com/tutorials/tutorial-six-java.html[RabbitMQ Remote Procedure Call (RPC)]. -By default, the standard `correlationId` property is used to hold the correlation data. -However, if you wish to use a custom property to hold correlation data, you can set the `correlation-key` attribute on the . -Explicitly setting the attribute to `correlationId` is the same as omitting the attribute. -The client and server must use the same header for correlation data. - -NOTE: Spring AMQP version 1.1 used a custom property called `spring_reply_correlation` for this data. -If you wish to revert to this behavior with the current version (perhaps to maintain compatibility with another application using 1.1), you must set the attribute to `spring_reply_correlation`. - -By default, the template generates its own correlation ID (ignoring any user-supplied value). -If you wish to use your own correlation ID, set the `RabbitTemplate` instance's `userCorrelationId` property to `true`. - -IMPORTANT: The correlation ID must be unique to avoid the possibility of a wrong reply being returned for a request. - -[[reply-listener]] -===== Reply Listener Container - -When using RabbitMQ versions prior to 3.4.0, a new temporary queue is used for each reply. -However, a single reply queue can be configured on the template, which can be more efficient and also lets you set arguments on that queue. -In this case, however, you must also provide a sub element. -This element provides a listener container for the reply queue, with the template being the listener. -All of the <> attributes allowed on a are allowed on the element, except for `connection-factory` and `message-converter`, which are inherited from the template's configuration. - -IMPORTANT: If you run multiple instances of your application or use multiple `RabbitTemplate` instances, you *MUST* use a unique reply queue for each. -RabbitMQ has no ability to select messages from a queue, so, if they all use the same queue, each instance would compete for replies and not necessarily receive their own. - -The following example defines a rabbit template with a connection factory: - -==== -[source,xml] ----- - - - ----- -==== - -While the container and template share a connection factory, they do not share a channel. -Therefore, requests and replies are not performed within the same transaction (if transactional). - -NOTE: Prior to version 1.5.0, the `reply-address` attribute was not available. -Replies were always routed by using the default exchange and the `reply-queue` name as the routing key. -This is still the default, but you can now specify the new `reply-address` attribute. -The `reply-address` can contain an address with the form `/` and the reply is routed to the specified exchange and routed to a queue bound with the routing key. -The `reply-address` has precedence over `reply-queue`. -When only `reply-address` is in use, the `` must be configured as a separate `` component. -The `reply-address` and `reply-queue` (or `queues` attribute on the ``) must refer to the same queue logically. - -With this configuration, a `SimpleListenerContainer` is used to receive the replies, with the `RabbitTemplate` being the `MessageListener`. -When defining a template with the `` namespace element, as shown in the preceding example, the parser defines the container and wires in the template as the listener. - -NOTE: When the template does not use a fixed `replyQueue` (or is using direct reply-to -- see <>), a listener container is not needed. -Direct `reply-to` is the preferred mechanism when using RabbitMQ 3.4.0 or later. - -If you define your `RabbitTemplate` as a `` or use an `@Configuration` class to define it as an `@Bean` or when you create the template programmatically, you need to define and wire up the reply listener container yourself. -If you fail to do this, the template never receives the replies and eventually times out and returns null as the reply to a call to a `sendAndReceive` method. - -Starting with version 1.5, the `RabbitTemplate` detects if it has been -configured as a `MessageListener` to receive replies. -If not, attempts to send and receive messages with a reply address -fail with an `IllegalStateException` (because the replies are never received). - -Further, if a simple `replyAddress` (queue name) is used, the reply listener container verifies that it is listening -to a queue with the same name. -This check cannot be performed if the reply address is an exchange and routing key and a debug log message is written. - -IMPORTANT: When wiring the reply listener and template yourself, it is important to ensure that the template's `replyAddress` and the container's `queues` (or `queueNames`) properties refer to the same queue. -The template inserts the reply address into the outbound message `replyTo` property. - -The following listing shows examples of how to manually wire up the beans: - -==== -[source,xml] ----- - - - - - - - - - - - - - - - - ----- - -[source,java] ----- - @Bean - public RabbitTemplate amqpTemplate() { - RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory()); - rabbitTemplate.setMessageConverter(msgConv()); - rabbitTemplate.setReplyAddress(replyQueue().getName()); - rabbitTemplate.setReplyTimeout(60000); - rabbitTemplate.setUseDirectReplyToContainer(false); - return rabbitTemplate; - } - - @Bean - public SimpleMessageListenerContainer replyListenerContainer() { - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); - container.setConnectionFactory(connectionFactory()); - container.setQueues(replyQueue()); - container.setMessageListener(amqpTemplate()); - return container; - } - - @Bean - public Queue replyQueue() { - return new Queue("my.reply.queue"); - } ----- -==== - -A complete example of a `RabbitTemplate` wired with a fixed reply queue, together with a "`remote`" listener container that handles the request and returns the reply is shown in https://github.com/spring-projects/spring-amqp/tree/main/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/JavaConfigFixedReplyQueueTests.java[this test case]. - -IMPORTANT: When the reply times out (`replyTimeout`), the `sendAndReceive()` methods return null. - -Prior to version 1.3.6, late replies for timed out messages were only logged. -Now, if a late reply is received, it is rejected (the template throws an `AmqpRejectAndDontRequeueException`). -If the reply queue is configured to send rejected messages to a dead letter exchange, the reply can be retrieved for later analysis. -To do so, bind a queue to the configured dead letter exchange with a routing key equal to the reply queue's name. - -See the https://www.rabbitmq.com/dlx.html[RabbitMQ Dead Letter Documentation] for more information about configuring dead lettering. -You can also take a look at the `FixedReplyQueueDeadLetterTests` test case for an example. - -[[async-template]] -===== Async Rabbit Template - -Version 1.6 introduced the `AsyncRabbitTemplate`. -This has similar `sendAndReceive` (and `convertSendAndReceive`) methods to those on the <>. -However, instead of blocking, they return a `CompletableFuture`. - -The `sendAndReceive` methods return a `RabbitMessageFuture`. -The `convertSendAndReceive` methods return a `RabbitConverterFuture`. - -You can either synchronously retrieve the result later, by invoking `get()` on the future, or you can register a callback that is called asynchronously with the result. -The following listing shows both approaches: - -==== -[source, java] ----- -@Autowired -private AsyncRabbitTemplate template; - -... - -public void doSomeWorkAndGetResultLater() { - - ... - - CompletableFuture future = this.template.convertSendAndReceive("foo"); - - // do some more work - - String reply = null; - try { - reply = future.get(10, TimeUnit.SECONDS); - } - catch (ExecutionException e) { - ... - } - - ... - -} - -public void doSomeWorkAndGetResultAsync() { - - ... - - RabbitConverterFuture future = this.template.convertSendAndReceive("foo"); - future.whenComplete((result, ex) -> { - if (ex == null) { - // success - } - else { - // failure - } - }); - - ... - -} ----- -==== - -If `mandatory` is set and the message cannot be delivered, the future throws an `ExecutionException` with a cause of `AmqpMessageReturnedException`, which encapsulates the returned message and information about the return. - -If `enableConfirms` is set, the future has a property called `confirm`, which is itself a `CompletableFuture` with `true` indicating a successful publish. -If the confirm future is `false`, the `RabbitFuture` has a further property called `nackCause`, which contains the reason for the failure, if available. - -IMPORTANT: The publisher confirm is discarded if it is received after the reply, since the reply implies a successful publish. - -You can set the `receiveTimeout` property on the template to time out replies (it defaults to `30000` - 30 seconds). -If a timeout occurs, the future is completed with an `AmqpReplyTimeoutException`. - -The template implements `SmartLifecycle`. -Stopping the template while there are pending replies causes the pending `Future` instances to be canceled. - -Starting with version 2.0, the asynchronous template now supports https://www.rabbitmq.com/direct-reply-to.html[direct reply-to] instead of a configured reply queue. -To enable this feature, use one of the following constructors: - -==== -[source, java] ----- -public AsyncRabbitTemplate(ConnectionFactory connectionFactory, String exchange, String routingKey) - -public AsyncRabbitTemplate(RabbitTemplate template) ----- -==== - -See <> to use direct reply-to with the synchronous `RabbitTemplate`. - -Version 2.0 introduced variants of these methods (`convertSendAndReceiveAsType`) that take an additional `ParameterizedTypeReference` argument to convert complex returned types. -You must configure the underlying `RabbitTemplate` with a `SmartMessageConverter`. -See <> for more information. - -IMPORTANT: Starting with version 3.0, the `AsyncRabbitTemplate` methods now return `CompletableFuture` s instead of `ListenableFuture` s. - -[[remoting]] -===== Spring Remoting with AMQP - -Spring remoting is no longer supported because the functionality has been removed from Spring Framework. - -Use `sendAndReceive` operations using the `RabbitTemplate` (client side ) and `@RabbitListener` instead. - -[[broker-configuration]] -==== Configuring the Broker - -The AMQP specification describes how the protocol can be used to configure queues, exchanges, and bindings on the broker. -These operations (which are portable from the 0.8 specification and higher) are present in the `AmqpAdmin` interface in the `org.springframework.amqp.core` package. -The RabbitMQ implementation of that class is `RabbitAdmin` located in the `org.springframework.amqp.rabbit.core` package. - -The `AmqpAdmin` interface is based on using the Spring AMQP domain abstractions and is shown in the following listing: - -==== -[source,java] ----- -public interface AmqpAdmin { - - // Exchange Operations - - void declareExchange(Exchange exchange); - - void deleteExchange(String exchangeName); - - // Queue Operations - - Queue declareQueue(); - - String declareQueue(Queue queue); - - void deleteQueue(String queueName); - - void deleteQueue(String queueName, boolean unused, boolean empty); - - void purgeQueue(String queueName, boolean noWait); - - // Binding Operations - - void declareBinding(Binding binding); - - void removeBinding(Binding binding); - - Properties getQueueProperties(String queueName); - -} ----- -==== - -See also <>. - -The `getQueueProperties()` method returns some limited information about the queue (message count and consumer count). -The keys for the properties returned are available as constants in the `RabbitTemplate` (`QUEUE_NAME`, -`QUEUE_MESSAGE_COUNT`, and `QUEUE_CONSUMER_COUNT`). -The <> provides much more information in the `QueueInfo` object. - -The no-arg `declareQueue()` method defines a queue on the broker with a name that is automatically generated. -The additional properties of this auto-generated queue are `exclusive=true`, `autoDelete=true`, and `durable=false`. - -The `declareQueue(Queue queue)` method takes a `Queue` object and returns the name of the declared queue. -If the `name` property of the provided `Queue` is an empty `String`, the broker declares the queue with a generated name. -That name is returned to the caller. -That name is also added to the `actualName` property of the `Queue`. -You can use this functionality programmatically only by invoking the `RabbitAdmin` directly. -When using auto-declaration by the admin when defining a queue declaratively in the application context, you can set the name property to `""` (the empty string). -The broker then creates the name. -Starting with version 2.1, listener containers can use queues of this type. -See <> for more information. - -This is in contrast to an `AnonymousQueue` where the framework generates a unique (`UUID`) name and sets `durable` to -`false` and `exclusive`, `autoDelete` to `true`. -A `` with an empty (or missing) `name` attribute always creates an `AnonymousQueue`. - -See <> to understand why `AnonymousQueue` is preferred over broker-generated queue names as well as -how to control the format of the name. -Starting with version 2.1, anonymous queues are declared with argument `Queue.X_QUEUE_LEADER_LOCATOR` set to `client-local` by default. -This ensures that the queue is declared on the node to which the application is connected. -Declarative queues must have fixed names because they might be referenced elsewhere in the context -- such as in the -listener shown in the following example: - -==== -[source,xml] ----- - - - ----- -==== - -See <>. - -The RabbitMQ implementation of this interface is `RabbitAdmin`, which, when configured by using Spring XML, resembles the following example: - -==== -[source,xml] ----- - - - ----- -==== - -When the `CachingConnectionFactory` cache mode is `CHANNEL` (the default), the `RabbitAdmin` implementation does automatic lazy declaration of queues, exchanges, and bindings declared in the same `ApplicationContext`. -These components are declared as soon as a `Connection` is opened to the broker. -There are some namespace features that make this very convenient -- for example, -in the Stocks sample application, we have the following: - -==== -[source,xml] ----- - - - - - - - - - - - - - - - ----- -==== - -In the preceding example, we use anonymous queues (actually, internally, just queues with names generated by the framework, not by the broker) and refer to them by ID. -We can also declare queues with explicit names, which also serve as identifiers for their bean definitions in the context. -The following example configures a queue with an explicit name: - -==== -[source,xml] ----- - ----- -==== - -TIP: You can provide both `id` and `name` attributes. -This lets you refer to the queue (for example, in a binding) by an ID that is independent of the queue name. -It also allows standard Spring features (such as property placeholders and SpEL expressions for the queue name). -These features are not available when you use the name as the bean identifier. - -Queues can be configured with additional arguments -- for example, `x-message-ttl`. -When you use the namespace support, they are provided in the form of a `Map` of argument-name/argument-value pairs, which are defined by using the `` element. -The following example shows how to do so: - -==== -[source,xml] ----- - - - - - - ----- -==== - -By default, the arguments are assumed to be strings. -For arguments of other types, you must provide the type. -The following example shows how to specify the type: - -==== -[source,xml] ----- - - - - - ----- -==== - -When providing arguments of mixed types, you must provide the type for each entry element. -The following example shows how to do so: - -==== -[source,xml] ----- - - - - 100 - - - - - ----- -==== - -With Spring Framework 3.2 and later, this can be declared a little more succinctly, as follows: - -==== -[source,xml] ----- - - - - - - ----- -==== - -When you use Java configuration, the `Queue.X_QUEUE_LEADER_LOCATOR` argument is supported as a first class property through the `setLeaderLocator()` method on the `Queue` class. -Starting with version 2.1, anonymous queues are declared with this property set to `client-local` by default. -This ensures that the queue is declared on the node the application is connected to. - -IMPORTANT: The RabbitMQ broker does not allow declaration of a queue with mismatched arguments. -For example, if a `queue` already exists with no `time to live` argument, and you attempt to declare it with (for example) `key="x-message-ttl" value="100"`, an exception is thrown. - -By default, the `RabbitAdmin` immediately stops processing all declarations when any exception occurs. -This could cause downstream issues, such as a listener container failing to initialize because another queue (defined after the one in error) is not declared. - -This behavior can be modified by setting the `ignore-declaration-exceptions` attribute to `true` on the `RabbitAdmin` instance. -This option instructs the `RabbitAdmin` to log the exception and continue declaring other elements. -When configuring the `RabbitAdmin` using Java, this property is called `ignoreDeclarationExceptions`. -This is a global setting that applies to all elements. -Queues, exchanges, and bindings have a similar property that applies to just those elements. - -Prior to version 1.6, this property took effect only if an `IOException` occurred on the channel, such as when there is a mismatch between current and desired properties. -Now, this property takes effect on any exception, including `TimeoutException` and others. - -In addition, any declaration exceptions result in the publishing of a `DeclarationExceptionEvent`, which is an `ApplicationEvent` that can be consumed by any `ApplicationListener` in the context. -The event contains a reference to the admin, the element that was being declared, and the `Throwable`. - -[[headers-exchange]] -===== Headers Exchange - -Starting with version 1.3, you can configure the `HeadersExchange` to match on multiple headers. -You can also specify whether any or all headers must match. -The following example shows how to do so: - -==== -[source,xml] ----- - - - - - - - - - - - ----- -==== - -Starting with version 1.6, you can configure `Exchanges` with an `internal` flag (defaults to `false`) and such an -`Exchange` is properly configured on the Broker through a `RabbitAdmin` (if one is present in the application context). -If the `internal` flag is `true` for an exchange, RabbitMQ does not let clients use the exchange. -This is useful for a dead letter exchange or exchange-to-exchange binding, where you do not wish the exchange to be used -directly by publishers. - -To see how to use Java to configure the AMQP infrastructure, look at the Stock sample application, -where there is the `@Configuration` class `AbstractStockRabbitConfiguration`, which ,in turn has -`RabbitClientConfiguration` and `RabbitServerConfiguration` subclasses. -The following listing shows the code for `AbstractStockRabbitConfiguration`: - -==== -[source,java] ----- -@Configuration -public abstract class AbstractStockAppRabbitConfiguration { - - @Bean - public CachingConnectionFactory connectionFactory() { - CachingConnectionFactory connectionFactory = - new CachingConnectionFactory("localhost"); - connectionFactory.setUsername("guest"); - connectionFactory.setPassword("guest"); - return connectionFactory; - } - - @Bean - public RabbitTemplate rabbitTemplate() { - RabbitTemplate template = new RabbitTemplate(connectionFactory()); - template.setMessageConverter(jsonMessageConverter()); - configureRabbitTemplate(template); - return template; - } - - @Bean - public Jackson2JsonMessageConverter jsonMessageConverter() { - return new Jackson2JsonMessageConverter(); - } - - @Bean - public TopicExchange marketDataExchange() { - return new TopicExchange("app.stock.marketdata"); - } - - // additional code omitted for brevity - -} ----- -==== - -In the Stock application, the server is configured by using the following `@Configuration` class: - -==== -[source,java] ----- -@Configuration -public class RabbitServerConfiguration extends AbstractStockAppRabbitConfiguration { - - @Bean - public Queue stockRequestQueue() { - return new Queue("app.stock.request"); - } -} ----- -==== - -This is the end of the whole inheritance chain of `@Configuration` classes. -The end result is that `TopicExchange` and `Queue` are declared to the broker upon application startup. -There is no binding of `TopicExchange` to a queue in the server configuration, as that is done in the client application. -The stock request queue, however, is automatically bound to the AMQP default exchange. -This behavior is defined by the specification. - -The client `@Configuration` class is a little more interesting. -Its declaration follows: - -==== -[source,java] ----- -@Configuration -public class RabbitClientConfiguration extends AbstractStockAppRabbitConfiguration { - - @Value("${stocks.quote.pattern}") - private String marketDataRoutingKey; - - @Bean - public Queue marketDataQueue() { - return amqpAdmin().declareQueue(); - } - - /** - * Binds to the market data exchange. - * Interested in any stock quotes - * that match its routing key. - */ - @Bean - public Binding marketDataBinding() { - return BindingBuilder.bind( - marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey); - } - - // additional code omitted for brevity - -} ----- -==== - -The client declares another queue through the `declareQueue()` method on the `AmqpAdmin`. -It binds that queue to the market data exchange with a routing pattern that is externalized in a properties file. - - -[[builder-api]] -===== Builder API for Queues and Exchanges - -Version 1.6 introduces a convenient fluent API for configuring `Queue` and `Exchange` objects when using Java configuration. -The following example shows how to use it: - -==== -[source, java] ----- -@Bean -public Queue queue() { - return QueueBuilder.nonDurable("foo") - .autoDelete() - .exclusive() - .withArgument("foo", "bar") - .build(); -} - -@Bean -public Exchange exchange() { - return ExchangeBuilder.directExchange("foo") - .autoDelete() - .internal() - .withArgument("foo", "bar") - .build(); -} ----- -==== - -See the Javadoc for https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/core/QueueBuilder.html[`org.springframework.amqp.core.QueueBuilder`] and https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/core/ExchangeBuilder.html[`org.springframework.amqp.core.ExchangeBuilder`] for more information. - -Starting with version 2.0, the `ExchangeBuilder` now creates durable exchanges by default, to be consistent with the simple constructors on the individual `AbstractExchange` classes. -To make a non-durable exchange with the builder, use `.durable(false)` before invoking `.build()`. -The `durable()` method with no parameter is no longer provided. - -Version 2.2 introduced fluent APIs to add "well known" exchange and queue arguments... - -==== -[source, java] ----- -@Bean -public Queue allArgs1() { - return QueueBuilder.nonDurable("all.args.1") - .ttl(1000) - .expires(200_000) - .maxLength(42) - .maxLengthBytes(10_000) - .overflow(Overflow.rejectPublish) - .deadLetterExchange("dlx") - .deadLetterRoutingKey("dlrk") - .maxPriority(4) - .lazy() - .leaderLocator(LeaderLocator.minLeaders) - .singleActiveConsumer() - .build(); -} - -@Bean -public DirectExchange ex() { - return ExchangeBuilder.directExchange("ex.with.alternate") - .durable(true) - .alternate("alternate") - .build(); -} ----- -==== - -[[collection-declaration]] -===== Declaring Collections of Exchanges, Queues, and Bindings - -You can wrap collections of `Declarable` objects (`Queue`, `Exchange`, and `Binding`) in `Declarables` objects. -The `RabbitAdmin` detects such beans (as well as discrete `Declarable` beans) in the application context, and declares the contained objects on the broker whenever a connection is established (initially and after a connection failure). -The following example shows how to do so: - -==== -[source, java] ----- -@Configuration -public static class Config { - - @Bean - public CachingConnectionFactory cf() { - return new CachingConnectionFactory("localhost"); - } - - @Bean - public RabbitAdmin admin(ConnectionFactory cf) { - return new RabbitAdmin(cf); - } - - @Bean - public DirectExchange e1() { - return new DirectExchange("e1", false, true); - } - - @Bean - public Queue q1() { - return new Queue("q1", false, false, true); - } - - @Bean - public Binding b1() { - return BindingBuilder.bind(q1()).to(e1()).with("k1"); - } - - @Bean - public Declarables es() { - return new Declarables( - new DirectExchange("e2", false, true), - new DirectExchange("e3", false, true)); - } - - @Bean - public Declarables qs() { - return new Declarables( - new Queue("q2", false, false, true), - new Queue("q3", false, false, true)); - } - - @Bean - @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public Declarables prototypes() { - return new Declarables(new Queue(this.prototypeQueueName, false, false, true)); - } - - @Bean - public Declarables bs() { - return new Declarables( - new Binding("q2", DestinationType.QUEUE, "e2", "k2", null), - new Binding("q3", DestinationType.QUEUE, "e3", "k3", null)); - } - - @Bean - public Declarables ds() { - return new Declarables( - new DirectExchange("e4", false, true), - new Queue("q4", false, false, true), - new Binding("q4", DestinationType.QUEUE, "e4", "k4", null)); - } - -} ----- -==== - -IMPORTANT: In versions prior to 2.1, you could declare multiple `Declarable` instances by defining beans of type `Collection`. -This can cause undesirable side effects in some cases, because the admin has to iterate over all `Collection` beans. - -Version 2.2 added the `getDeclarablesByType` method to `Declarables`; this can be used as a convenience, for example, when declaring the listener container bean(s). - -==== -[source, java] ----- -public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, - Declarables mixedDeclarables, MessageListener listener) { - - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory); - container.setQueues(mixedDeclarables.getDeclarablesByType(Queue.class).toArray(new Queue[0])); - container.setMessageListener(listener); - return container; -} ----- -==== - -[[conditional-declaration]] -===== Conditional Declaration - -By default, all queues, exchanges, and bindings are declared by all `RabbitAdmin` instances (assuming they have `auto-startup="true"`) in the application context. - -Starting with version 2.1.9, the `RabbitAdmin` has a new property `explicitDeclarationsOnly` (which is `false` by default); when this is set to `true`, the admin will only declare beans that are explicitly configured to be declared by that admin. - -NOTE: Starting with the 1.2 release, you can conditionally declare these elements. -This is particularly useful when an application connects to multiple brokers and needs to specify with which brokers a particular element should be declared. - -The classes representing these elements implement `Declarable`, which has two methods: `shouldDeclare()` and `getDeclaringAdmins()`. -The `RabbitAdmin` uses these methods to determine whether a particular instance should actually process the declarations on its `Connection`. - -The properties are available as attributes in the namespace, as shown in the following examples: - -==== -[source,xml] ----- - - - - - - - - - - - - - - - - - - - ----- -==== - -NOTE: By default, the `auto-declare` attribute is `true` and, if the `declared-by` is not supplied (or is empty), then all `RabbitAdmin` instances declare the object (as long as the admin's `auto-startup` attribute is `true`, the default, and the admin's `explicit-declarations-only` attribute is false). - -Similarly, you can use Java-based `@Configuration` to achieve the same effect. -In the following example, the components are declared by `admin1` but not by `admin2`: - -==== -[source,java] ----- -@Bean -public RabbitAdmin admin1() { - return new RabbitAdmin(cf1()); -} - -@Bean -public RabbitAdmin admin2() { - return new RabbitAdmin(cf2()); -} - -@Bean -public Queue queue() { - Queue queue = new Queue("foo"); - queue.setAdminsThatShouldDeclare(admin1()); - return queue; -} - -@Bean -public Exchange exchange() { - DirectExchange exchange = new DirectExchange("bar"); - exchange.setAdminsThatShouldDeclare(admin1()); - return exchange; -} - -@Bean -public Binding binding() { - Binding binding = new Binding("foo", DestinationType.QUEUE, exchange().getName(), "foo", null); - binding.setAdminsThatShouldDeclare(admin1()); - return binding; -} ----- -==== - -[[note-id-name]] -===== A Note On the `id` and `name` Attributes - -The `name` attribute on `` and `` elements reflects the name of the entity in the broker. -For queues, if the `name` is omitted, an anonymous queue is created (see <>). - -In versions prior to 2.0, the `name` was also registered as a bean name alias (similar to `name` on `` elements). - -This caused two problems: - -* It prevented the declaration of a queue and exchange with the same name. -* The alias was not resolved if it contained a SpEL expression (`#{...}`). - -Starting with version 2.0, if you declare one of these elements with both an `id` _and_ a `name` attribute, the name is no longer declared as a bean name alias. -If you wish to declare a queue and exchange with the same `name`, you must provide an `id`. - -There is no change if the element has only a `name` attribute. -The bean can still be referenced by the `name` -- for example, in binding declarations. -However, you still cannot reference it if the name contains SpEL -- you must provide an `id` for reference purposes. - - -[[anonymous-queue]] -===== `AnonymousQueue` - -In general, when you need a uniquely-named, exclusive, auto-delete queue, we recommend that you use the `AnonymousQueue` -instead of broker-defined queue names (using `""` as a `Queue` name causes the broker to generate the queue -name). - -This is because: - -. The queues are actually declared when the connection to the broker is established. -This is long after the beans are created and wired together. -Beans that use the queue need to know its name. -In fact, the broker might not even be running when the application is started. -. If the connection to the broker is lost for some reason, the admin re-declares the `AnonymousQueue` with the same name. -If we used broker-declared queues, the queue name would change. - -You can control the format of the queue name used by `AnonymousQueue` instances. - -By default, the queue name is prefixed by `spring.gen-` followed by a base64 representation of the `UUID` -- for example: `spring.gen-MRBv9sqISkuCiPfOYfpo4g`. - -You can provide an `AnonymousQueue.NamingStrategy` implementation in a constructor argument. -The following example shows how to do so: - -==== -[source, java] ----- -@Bean -public Queue anon1() { - return new AnonymousQueue(); -} - -@Bean -public Queue anon2() { - return new AnonymousQueue(new AnonymousQueue.Base64UrlNamingStrategy("something-")); -} - -@Bean -public Queue anon3() { - return new AnonymousQueue(AnonymousQueue.UUIDNamingStrategy.DEFAULT); -} ----- -==== - -The first bean generates a queue name prefixed by `spring.gen-` followed by a base64 representation of the `UUID` -- for -example: `spring.gen-MRBv9sqISkuCiPfOYfpo4g`. -The second bean generates a queue name prefixed by `something-` followed by a base64 representation of the `UUID`. -The third bean generates a name by using only the UUID (no base64 conversion) -- for example, `f20c818a-006b-4416-bf91-643590fedb0e`. - -The base64 encoding uses the "`URL and Filename Safe Alphabet`" from RFC 4648. -Trailing padding characters (`=`) are removed. - -You can provide your own naming strategy, whereby you can include other information (such as the application name or client host) in the queue name. - -You can specify the naming strategy when you use XML configuration. -The `naming-strategy` attribute is present on the `` element -for a bean reference that implements `AnonymousQueue.NamingStrategy`. -The following examples show how to specify the naming strategy in various ways: - -==== -[source, xml] ----- - - - - - - - - - - - ----- -==== - -The first example creates names such as `spring.gen-MRBv9sqISkuCiPfOYfpo4g`. -The second example creates names with a String representation of a UUID. -The third example creates names such as `custom.gen-MRBv9sqISkuCiPfOYfpo4g`. - -You can also provide your own naming strategy bean. - -Starting with version 2.1, anonymous queues are declared with argument `Queue.X_QUEUE_LEADER_LOCATOR` set to `client-local` by default. -This ensures that the queue is declared on the node to which the application is connected. -You can revert to the previous behavior by calling `queue.setLeaderLocator(null)` after constructing the instance. - -[[declarable-recovery]] -===== Recovering Auto-Delete Declarations - -Normally, the `RabbitAdmin` (s) only recover queues/exchanges/bindings that are declared as beans in the application context; if any such declarations are auto-delete, they will be removed by the broker if the connection is lost. -When the connection is re-established, the admin will redeclare the entities. -Normally, entities created by calling `admin.declareQueue(...)`, `admin.declareExchange(...)` and `admin.declareBinding(...)` will not be recovered. - -Starting with version 2.4, the admin has a new property `redeclareManualDeclarations`; when `true`, the admin will recover these entities in addition to the beans in the application context. - -Recovery of individual declarations will not be performed if `deleteQueue(...)`, `deleteExchange(...)` or `removeBinding(...)` is called. -Associated bindings are removed from the recoverable entities when queues and exchanges are deleted. - -Finally, calling `resetAllManualDeclarations()` will prevent the recovery of any previously declared entities. - -[[broker-events]] -==== Broker Event Listener - -When the https://www.rabbitmq.com/event-exchange.html[Event Exchange Plugin] is enabled, if you add a bean of type `BrokerEventListener` to the application context, it publishes selected broker events as `BrokerEvent` instances, which can be consumed with a normal Spring `ApplicationListener` or `@EventListener` method. -Events are published by the broker to a topic exchange `amq.rabbitmq.event` with a different routing key for each event type. -The listener uses event keys, which are used to bind an `AnonymousQueue` to the exchange so the listener receives only selected events. -Since it is a topic exchange, wildcards can be used (as well as explicitly requesting specific events), as the following example shows: - -==== -[source, java] ----- -@Bean -public BrokerEventListener eventListener() { - return new BrokerEventListener(connectionFactory(), "user.deleted", "channel.#", "queue.#"); -} ----- -==== - -You can further narrow the received events in individual event listeners, by using normal Spring techniques, as the following example shows: - -==== -[source, java] ----- -@EventListener(condition = "event.eventType == 'queue.created'") -public void listener(BrokerEvent event) { - ... -} ----- -==== - -[[delayed-message-exchange]] -==== Delayed Message Exchange - -Version 1.6 introduces support for the -https://www.rabbitmq.com/blog/2015/04/16/scheduling-messages-with-rabbitmq/[Delayed Message Exchange Plugin] - -NOTE: The plugin is currently marked as experimental but has been available for over a year (at the time of writing). -If changes to the plugin make it necessary, we plan to add support for such changes as soon as practical. -For that reason, this support in Spring AMQP should be considered experimental, too. -This functionality was tested with RabbitMQ 3.6.0 and version 0.0.1 of the plugin. - -To use a `RabbitAdmin` to declare an exchange as delayed, you can set the `delayed` property on the exchange bean to -`true`. -The `RabbitAdmin` uses the exchange type (`Direct`, `Fanout`, and so on) to set the `x-delayed-type` argument and -declare the exchange with type `x-delayed-message`. - -The `delayed` property (default: `false`) is also available when configuring exchange beans using XML. -The following example shows how to use it: - -==== -[source, xml] ----- - ----- -==== - -To send a delayed message, you can set the `x-delay` header through `MessageProperties`, as the following examples show: - -==== -[source, java] ----- -MessageProperties properties = new MessageProperties(); -properties.setDelay(15000); -template.send(exchange, routingKey, - MessageBuilder.withBody("foo".getBytes()).andProperties(properties).build()); ----- - -[source, java] ----- -rabbitTemplate.convertAndSend(exchange, routingKey, "foo", new MessagePostProcessor() { - - @Override - public Message postProcessMessage(Message message) throws AmqpException { - message.getMessageProperties().setDelay(15000); - return message; - } - -}); ----- -==== - -To check if a message was delayed, use the `getReceivedDelay()` method on the `MessageProperties`. -It is a separate property to avoid unintended propagation to an output message generated from an input message. - - -[[management-rest-api]] -==== RabbitMQ REST API - -When the management plugin is enabled, the RabbitMQ server exposes a REST API to monitor and configure the broker. -A https://github.com/rabbitmq/hop[Java Binding for the API] is now provided. -The `com.rabbitmq.http.client.Client` is a standard, immediate, and, therefore, blocking API. -It is based on the https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#spring-web[Spring Web] module and its `RestTemplate` implementation. -On the other hand, the `com.rabbitmq.http.client.ReactorNettyClient` is a reactive, non-blocking implementation based on the https://projectreactor.io/docs/netty/release/reference/docs/index.html[Reactor Netty] project. - -The hop dependency (`com.rabbitmq:http-client`) is now also `optional`. - -See their Javadoc for more information. - -[[exception-handling]] -==== Exception Handling - -Many operations with the RabbitMQ Java client can throw checked exceptions. -For example, there are a lot of cases where `IOException` instances may be thrown. -The `RabbitTemplate`, `SimpleMessageListenerContainer`, and other Spring AMQP components catch those exceptions and convert them into one of the exceptions within `AmqpException` hierarchy. -Those are defined in the 'org.springframework.amqp' package, and `AmqpException` is the base of the hierarchy. - -When a listener throws an exception, it is wrapped in a `ListenerExecutionFailedException`. -Normally the message is rejected and requeued by the broker. -Setting `defaultRequeueRejected` to `false` causes messages to be discarded (or routed to a dead letter exchange). -As discussed in <>, the listener can throw an `AmqpRejectAndDontRequeueException` (or `ImmediateRequeueAmqpException`) to conditionally control this behavior. - -However, there is a class of errors where the listener cannot control the behavior. -When a message that cannot be converted is encountered (for example, an invalid `content_encoding` header), some exceptions are thrown before the message reaches user code. -With `defaultRequeueRejected` set to `true` (default) (or throwing an `ImmediateRequeueAmqpException`), such messages would be redelivered over and over. -Before version 1.3.2, users needed to write a custom `ErrorHandler`, as discussed in <>, to avoid this situation. - -Starting with version 1.3.2, the default `ErrorHandler` is now a `ConditionalRejectingErrorHandler` that rejects (and does not requeue) messages that fail with an irrecoverable error. -Specifically, it rejects messages that fail with the following errors: - -* `o.s.amqp...MessageConversionException`: Can be thrown when converting the incoming message payload using a `MessageConverter`. -* `o.s.messaging...MessageConversionException`: Can be thrown by the conversion service if additional conversion is required when mapping to a `@RabbitListener` method. -* `o.s.messaging...MethodArgumentNotValidException`: Can be thrown if validation (for example, `@Valid`) is used in the listener and the validation fails. -* `o.s.messaging...MethodArgumentTypeMismatchException`: Can be thrown if the inbound message was converted to a type that is not correct for the target method. -For example, the parameter is declared as `Message` but `Message` is received. -* `java.lang.NoSuchMethodException`: Added in version 1.6.3. -* `java.lang.ClassCastException`: Added in version 1.6.3. - -You can configure an instance of this error handler with a `FatalExceptionStrategy` so that users can provide their own rules for conditional message rejection -- for example, a delegate implementation to the `BinaryExceptionClassifier` from Spring Retry (<>). -In addition, the `ListenerExecutionFailedException` now has a `failedMessage` property that you can use in the decision. -If the `FatalExceptionStrategy.isFatal()` method returns `true`, the error handler throws an `AmqpRejectAndDontRequeueException`. -The default `FatalExceptionStrategy` logs a warning message when an exception is determined to be fatal. - -Since version 1.6.3, a convenient way to add user exceptions to the fatal list is to subclass `ConditionalRejectingErrorHandler.DefaultExceptionStrategy` and override the `isUserCauseFatal(Throwable cause)` method to return `true` for fatal exceptions. - -A common pattern for handling DLQ messages is to set a `time-to-live` on those messages as well as additional DLQ configuration such that these messages expire and are routed back to the main queue for retry. -The problem with this technique is that messages that cause fatal exceptions loop forever. -Starting with version 2.1, the `ConditionalRejectingErrorHandler` detects an `x-death` header on a message that causes a fatal exception to be thrown. -The message is logged and discarded. -You can revert to the previous behavior by setting the `discardFatalsWithXDeath` property on the `ConditionalRejectingErrorHandler` to `false`. - -IMPORTANT: Starting with version 2.1.9, messages with these fatal exceptions are rejected and NOT requeued by default, even if the container acknowledge mode is MANUAL. -These exceptions generally occur before the listener is invoked so the listener does not have a chance to ack or nack the message so it remained in the queue in an un-acked state. -To revert to the previous behavior, set the `rejectManual` property on the `ConditionalRejectingErrorHandler` to `false`. - -[[transactions]] -==== Transactions - -The Spring Rabbit framework has support for automatic transaction management in the synchronous and asynchronous use cases with a number of different semantics that can be selected declaratively, as is familiar to existing users of Spring transactions. -This makes many if not most common messaging patterns easy to implement. - -There are two ways to signal the desired transaction semantics to the framework. -In both the `RabbitTemplate` and `SimpleMessageListenerContainer`, there is a flag `channelTransacted` which, if `true`, tells the framework to use a transactional channel and to end all operations (send or receive) with a commit or rollback (depending on the outcome), with an exception signaling a rollback. -Another signal is to provide an external transaction with one of Spring's `PlatformTransactionManager` implementations as a context for the ongoing operation. -If there is already a transaction in progress when the framework is sending or receiving a message, and the `channelTransacted` flag is `true`, the commit or rollback of the messaging transaction is deferred until the end of the current transaction. -If the `channelTransacted` flag is `false`, no transaction semantics apply to the messaging operation (it is auto-acked). - -The `channelTransacted` flag is a configuration time setting. -It is declared and processed once when the AMQP components are created, usually at application startup. -The external transaction is more dynamic in principle because the system responds to the current thread state at runtime. -However, in practice, it is often also a configuration setting, when the transactions are layered onto an application declaratively. - -For synchronous use cases with `RabbitTemplate`, the external transaction is provided by the caller, either declaratively or imperatively according to taste (the usual Spring transaction model). -The following example shows a declarative approach (usually preferred because it is non-invasive), where the template has been configured with `channelTransacted=true`: - -==== -[source,java] ----- -@Transactional -public void doSomething() { - String incoming = rabbitTemplate.receiveAndConvert(); - // do some more database processing... - String outgoing = processInDatabaseAndExtractReply(incoming); - rabbitTemplate.convertAndSend(outgoing); -} ----- -==== - -In the preceding example, a `String` payload is received, converted, and sent as a message body inside a method marked as `@Transactional`. -If the database processing fails with an exception, the incoming message is returned to the broker, and the outgoing message is not sent. -This applies to any operations with the `RabbitTemplate` inside a chain of transactional methods (unless, for instance, the `Channel` is directly manipulated to commit the transaction early). - -For asynchronous use cases with `SimpleMessageListenerContainer`, if an external transaction is needed, it has to be requested by the container when it sets up the listener. -To signal that an external transaction is required, the user provides an implementation of `PlatformTransactionManager` to the container when it is configured. -The following example shows how to do so: - -==== -[source,java] ----- -@Configuration -public class ExampleExternalTransactionAmqpConfiguration { - - @Bean - public SimpleMessageListenerContainer messageListenerContainer() { - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); - container.setConnectionFactory(rabbitConnectionFactory()); - container.setTransactionManager(transactionManager()); - container.setChannelTransacted(true); - container.setQueueName("some.queue"); - container.setMessageListener(exampleListener()); - return container; - } - -} ----- -==== - -In the preceding example, the transaction manager is added as a dependency injected from another bean definition (not shown), and the `channelTransacted` flag is also set to `true`. -The effect is that if the listener fails with an exception, the transaction is rolled back, and the message is also returned to the broker. -Significantly, if the transaction fails to commit (for example, because of -a database constraint error or connectivity problem), the AMQP transaction is also rolled back, and the message is returned to the broker. -This is sometimes known as a "`Best Efforts 1 Phase Commit`", and is a very powerful pattern for reliable messaging. -If the `channelTransacted` flag was set to `false` (the default) in the preceding example, the external transaction would still be provided for the listener, but all messaging operations would be auto-acked, so the effect is to commit the messaging operations even on a rollback of the business operation. - -[[conditional-rollback]] -===== Conditional Rollback - -Prior to version 1.6.6, adding a rollback rule to a container's `transactionAttribute` when using an external transaction manager (such as JDBC) had no effect. -Exceptions always rolled back the transaction. - -Also, when using a https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/transaction.html#transaction-declarative[transaction advice] in the container's advice chain, conditional rollback was not very useful, because all listener exceptions are wrapped in a `ListenerExecutionFailedException`. - -The first problem has been corrected, and the rules are now applied properly. -Further, the `ListenerFailedRuleBasedTransactionAttribute` is now provided. -It is a subclass of `RuleBasedTransactionAttribute`, with the only difference being that it is aware of the `ListenerExecutionFailedException` and uses the cause of such exceptions for the rule. -This transaction attribute can be used directly in the container or through a transaction advice. - -The following example uses this rule: - -==== -[source, java] ----- -@Bean -public AbstractMessageListenerContainer container() { - ... - container.setTransactionManager(transactionManager); - RuleBasedTransactionAttribute transactionAttribute = - new ListenerFailedRuleBasedTransactionAttribute(); - transactionAttribute.setRollbackRules(Collections.singletonList( - new NoRollbackRuleAttribute(DontRollBackException.class))); - container.setTransactionAttribute(transactionAttribute); - ... -} ----- -==== - -[[transaction-rollback]] -===== A note on Rollback of Received Messages - -AMQP transactions apply only to messages and acks sent to the broker. -Consequently, when there is a rollback of a Spring transaction and a message has been received, Spring AMQP has to not only rollback the transaction but also manually reject the message (sort of a nack, but that is not what the specification calls it). -The action taken on message rejection is independent of transactions and depends on the `defaultRequeueRejected` property (default: `true`). -For more information about rejecting failed messages, see <>. - -For more information about RabbitMQ transactions and their limitations, see https://www.rabbitmq.com/semantics.html[RabbitMQ Broker Semantics]. - -NOTE: Prior to RabbitMQ 2.7.0, such messages (and any that are unacked when a channel is closed or aborts) went to the back of the queue on a Rabbit broker. -Since 2.7.0, rejected messages go to the front of the queue, in a similar manner to JMS rolled back messages. - -NOTE: Previously, message requeue on transaction rollback was inconsistent between local transactions and when a `TransactionManager` was provided. -In the former case, the normal requeue logic (`AmqpRejectAndDontRequeueException` or `defaultRequeueRejected=false`) applied (see <>). -With a transaction manager, the message was unconditionally requeued on rollback. -Starting with version 2.0, the behavior is consistent and the normal requeue logic is applied in both cases. -To revert to the previous behavior, you can set the container's `alwaysRequeueWithTxManagerRollback` property to `true`. -See <>. - -===== Using `RabbitTransactionManager` - -The https://docs.spring.io/spring-amqp/docs/latest_ga/api/org/springframework/amqp/rabbit/transaction/RabbitTransactionManager.html[RabbitTransactionManager] is an alternative to executing Rabbit operations within, and synchronized with, external transactions. -This transaction manager is an implementation of the https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/PlatformTransactionManager.html[`PlatformTransactionManager`] interface and should be used with a single Rabbit `ConnectionFactory`. - -IMPORTANT: This strategy is not able to provide XA transactions -- for example, in order to share transactions between messaging and database access. - -Application code is required to retrieve the transactional Rabbit resources through `ConnectionFactoryUtils.getTransactionalResourceHolder(ConnectionFactory, boolean)` instead of a standard `Connection.createChannel()` call with subsequent channel creation. -When using Spring AMQP's https://docs.spring.io/spring-amqp/docs/latest_ga/api/org/springframework/amqp/rabbit/core/RabbitTemplate.html[RabbitTemplate], it will autodetect a thread-bound Channel and automatically participate in its transaction. - -With Java Configuration, you can setup a new RabbitTransactionManager by using the following bean: - -==== -[source,java] ----- -@Bean -public RabbitTransactionManager rabbitTransactionManager() { - return new RabbitTransactionManager(connectionFactory); -} ----- -==== - -If you prefer XML configuration, you can declare the following bean in your XML Application Context file: - -==== -[source,xml] ----- - - - ----- -==== - -[[tx-sync]] -===== Transaction Synchronization - -Synchronizing a RabbitMQ transaction with some other (e.g. DBMS) transaction provides "Best Effort One Phase Commit" semantics. -It is possible that the RabbitMQ transaction fails to commit during the after completion phase of transaction synchronization. -This is logged by the `spring-tx` infrastructure as an error, but no exception is thrown to the calling code. -Starting with version 2.3.10, you can call `ConnectionUtils.checkAfterCompletion()` after the transaction has committed on the same thread that processed the transaction. -It will simply return if no exception occurred; otherwise it will throw an `AfterCompletionFailedException` which will have a property representing the synchronization status of the completion. - -Enable this feature by calling `ConnectionFactoryUtils.enableAfterCompletionFailureCapture(true)`; this is a global flag and applies to all threads. - -[[containerAttributes]] -==== Message Listener Container Configuration - -There are quite a few options for configuring a `SimpleMessageListenerContainer` (SMLC) and a `DirectMessageListenerContainer` (DMLC) related to transactions and quality of service, and some of them interact with each other. -Properties that apply to the SMLC, DMLC, or `StreamListenerContainer` (StLC) (see <>) are indicated by the check mark in the appropriate column. -See <> for information to help you decide which container is appropriate for your application. - -The following table shows the container property names and their equivalent attribute names (in parentheses) when using the namespace to configure a ``. -The `type` attribute on that element can be `simple` (default) or `direct` to specify an `SMLC` or `DMLC` respectively. -Some properties are not exposed by the namespace. -These are indicated by `N/A` for the attribute. - -.Configuration options for a message listener container -[cols="8,16,1,1,1", options="header"] -|=== -|Property -(Attribute) -|Description -|SMLC -|DMLC -|StLC - -|[[ackTimeout]]<> + -(N/A) - -|When `messagesPerAck` is set, this timeout is used as an alternative to send an ack. -When a new message arrives, the count of unacked messages is compared to `messagesPerAck`, and the time since the last ack is compared to this value. -If either condition is `true`, the message is acknowledged. -When no new messages arrive and there are unacked messages, this timeout is approximate since the condition is only checked each `monitorInterval`. -See also `messagesPerAck` and `monitorInterval` in this table. - -a| -a|image::images/tickmark.png[] -a| - -|[[acknowledgeMode]]<> + -(acknowledge) - -a| -* `NONE`: No acks are sent (incompatible with `channelTransacted=true`). -RabbitMQ calls this "`autoack`", because the broker assumes all messages are acked without any action from the consumer. -* `MANUAL`: The listener must acknowledge all messages by calling `Channel.basicAck()`. -* `AUTO`: The container acknowledges the message automatically, unless the `MessageListener` throws an exception. -Note that `acknowledgeMode` is complementary to `channelTransacted` -- if the channel is transacted, the broker requires a commit notification in addition to the ack. -This is the default mode. -See also `batchSize`. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[adviceChain]]<> + -(advice-chain) - -|An array of AOP Advice to apply to the listener execution. -This can be used to apply additional cross-cutting concerns, such as automatic retry in the event of broker death. -Note that simple re-connection after an AMQP error is handled by the `CachingConnectionFactory`, as long as the broker is still alive. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[afterReceivePostProcessors]]<> + -(N/A) - -|An array of `MessagePostProcessor` instances that are invoked before invoking the listener. -Post processors can implement `PriorityOrdered` or `Ordered`. -The array is sorted with un-ordered members invoked last. -If a post processor returns `null`, the message is discarded (and acknowledged, if appropriate). - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[alwaysRequeueWithTxManagerRollback]]<> + -(N/A) - -|Set to `true` to always requeue messages on rollback when a transaction manager is configured. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[autoDeclare]]<> + -(auto-declare) - -a|When set to `true` (default), the container uses a `RabbitAdmin` to redeclare all AMQP objects (queues, exchanges, bindings), if it detects that at least one of its queues is missing during startup, perhaps because it is an `auto-delete` or an expired queue, but the redeclaration proceeds if the queue is missing for any reason. -To disable this behavior, set this property to `false`. -Note that the container fails to start if all of its queues are missing. - -NOTE: Prior to version 1.6, if there was more than one admin in the context, the container would randomly select one. -If there were no admins, it would create one internally. -In either case, this could cause unexpected results. -Starting with version 1.6, for `autoDeclare` to work, there must be exactly one `RabbitAdmin` in the context, or a reference to a specific instance must be configured on the container using the `rabbitAdmin` property. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[autoStartup]]<> + -(auto-startup) - -|Flag to indicate that the container should start when the `ApplicationContext` does (as part of the `SmartLifecycle` callbacks, which happen after all beans are initialized). -Defaults to `true`, but you can set it to `false` if your broker might not be available on startup and call `start()` later manually when you know the broker is ready. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] - -|[[batchSize]]<> + -(transaction-size) -(batch-size) - -|When used with `acknowledgeMode` set to `AUTO`, the container tries to process up to this number of messages before sending an ack (waiting for each one up to the receive timeout setting). -This is also when a transactional channel is committed. -If the `prefetchCount` is less than the `batchSize`, it is increased to match the `batchSize`. - -a|image::images/tickmark.png[] -a| -a| - -|[[batchingStrategy]]<> + -(N/A) - -|The strategy used when debatchng messages. -Default `SimpleDebatchingStrategy`. -See <> and <>. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[channelTransacted]]<> + -(channel-transacted) - -|Boolean flag to signal that all messages should be acknowledged in a transaction (either manually or automatically). - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[concurrency]]<> + -(N/A) - -|`m-n` The range of concurrent consumers for each listener (min, max). -If only `n` is provided, `n` is a fixed number of consumers. -See <>. - -a|image::images/tickmark.png[] -a| -a| - -|[[concurrentConsumers]]<> + -(concurrency) - -|The number of concurrent consumers to initially start for each listener. -See <>. -For the `StLC`, concurrency is controlled via an overloaded `superStream` method; see <>. - -a|image::images/tickmark.png[] -a| -a|image::images/tickmark.png[] - -|[[connectionFactory]]<> + -(connection-factory) - -|A reference to the `ConnectionFactory`. -When configuring by using the XML namespace, the default referenced bean name is `rabbitConnectionFactory`. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[consecutiveActiveTrigger]]<> + -(min-consecutive-active) - -|The minimum number of consecutive messages received by a consumer, without a receive timeout occurring, when considering starting a new consumer. -Also impacted by 'batchSize'. -See <>. -Default: 10. - -a|image::images/tickmark.png[] -a| -a| - -|[[consecutiveIdleTrigger]]<> + -(min-consecutive-idle) - -|The minimum number of receive timeouts a consumer must experience before considering stopping a consumer. -Also impacted by 'batchSize'. -See <>. -Default: 10. - -a|image::images/tickmark.png[] -a| -a| - -|[[consumerBatchEnabled]]<> + -(batch-enabled) - -|If the `MessageListener` supports it, setting this to true enables batching of discrete messages, up to `batchSize`; a partial batch will be delivered if no new messages arrive in `receiveTimeout`. -When this is false, batching is only supported for batches created by a producer; see <>. - -a|image::images/tickmark.png[] -a| -a| - -|[[consumerCustomizer]]<> + -(N/A) - -|A `ConsumerCustomizer` bean used to modify stream consumers created by the container. - -a| -a| -a|image::images/tickmark.png[] - -|[[consumerStartTimeout]]<> + -(N/A) - -|The time in milliseconds to wait for a consumer thread to start. -If this time elapses, an error log is written. -An example of when this might happen is if a configured `taskExecutor` has insufficient threads to support the container `concurrentConsumers`. - -See <>. -Default: 60000 (one minute). - -a|image::images/tickmark.png[] -a| -a| - -|[[consumerTagStrategy]]<> + -(consumer-tag-strategy) - -|Set an implementation of <>, enabling the creation of a (unique) tag for each consumer. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[consumersPerQueue]]<> + -(consumers-per-queue) - -|The number of consumers to create for each configured queue. -See <>. - -a| -a|image::images/tickmark.png[] -a| - -|[[consumeDelay]]<> + -(N/A) - -|When using the https://github.com/rabbitmq/rabbitmq-sharding[RabbitMQ Sharding Plugin] with `concurrentConsumers > 1`, there is a race condition that can prevent even distribution of the consumers across the shards. -Use this property to add a small delay between consumer starts to avoid this race condition. -You should experiment with values to determine the suitable delay for your environment. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[debatchingEnabled]]<> + -(N/A) - -|When true, the listener container will debatch batched messages and invoke the listener with each message from the batch. -Starting with version 2.2.7, <> will be debatched as a `List` if the listener is a `BatchMessageListener` or `ChannelAwareBatchMessageListener`. -Otherwise messages from the batch are presented one-at-a-time. -Default true. -See <> and <>. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[declarationRetries]]<> + -(declaration-retries) - -|The number of retry attempts when passive queue declaration fails. -Passive queue declaration occurs when the consumer starts or, when consuming from multiple queues, when not all queues were available during initialization. -When none of the configured queues can be passively declared (for any reason) after the retries are exhausted, the container behavior is controlled by the 'missingQueuesFatal` property, described earlier. -Default: Three retries (for a total of four attempts). - -a|image::images/tickmark.png[] -a| -a| - -|[[defaultRequeueRejected]]<> + -(requeue-rejected) - -|Determines whether messages that are rejected because the listener threw an exception should be requeued or not. -Default: `true`. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[errorHandler]]<> + -(error-handler) - -|A reference to an `ErrorHandler` strategy for handling any uncaught exceptions that may occur during the execution of the MessageListener. -Default: `ConditionalRejectingErrorHandler` - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[exclusive]]<> + -(exclusive) - -|Determines whether the single consumer in this container has exclusive access to the queues. -The concurrency of the container must be 1 when this is `true`. -If another consumer has exclusive access, the container tries to recover the consumer, according to the -`recovery-interval` or `recovery-back-off`. -When using the namespace, this attribute appears on the `` element along with the queue names. -Default: `false`. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[exclusiveConsumerExceptionLogger]]<> + -(N/A) - -|An exception logger used when an exclusive consumer cannot gain access to a queue. -By default, this is logged at the `WARN` level. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[failedDeclarationRetryInterval]]<> + -(failed-declaration --retry-interval) - -|The interval between passive queue declaration retry attempts. -Passive queue declaration occurs when the consumer starts or, when consuming from multiple queues, when not all queues were available during initialization. -Default: 5000 (five seconds). - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[forceCloseChannel]]<> + -(N/A) - -|If the consumers do not respond to a shutdown within `shutdownTimeout`, if this is `true`, the channel will be closed, causing any unacked messages to be requeued. -Defaults to `true` since 2.0. -You can set it to `false` to revert to the previous behavior. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[forceStop]]<> + -(N/A) - -|Set to true to stop (when the container is stopped) after the current record is processed; causing all prefetched messages to be requeued. -By default, the container will cancel the consumer and process all prefetched messages before stopping. -Since versions 2.4.14, 3.0.6 -Defaults to `false`. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[globalQos]]<> + -(global-qos) - -|When true, the `prefetchCount` is applied globally to the channel rather than to each consumer on the channel. -See https://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.qos.global[`basicQos.global`] for more information. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|(group) - -|This is available only when using the namespace. -When specified, a bean of type `Collection` is registered with this name, and the -container for each `` element is added to the collection. -This allows, for example, starting and stopping the group of containers by iterating over the collection. -If multiple `` elements have the same group value, the containers in the collection form -an aggregate of all containers so designated. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[idleEventInterval]]<> + -(idle-event-interval) - -|See <>. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[javaLangErrorHandler]]<> + -(N/A) - -|An `AbstractMessageListenerContainer.JavaLangErrorHandler` implementation that is called when a container thread catches an `Error`. -The default implementation calls `System.exit(99)`; to revert to the previous behavior (do nothing), add a no-op handler. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[maxConcurrentConsumers]]<> + -(max-concurrency) - -|The maximum number of concurrent consumers to start, if needed, on demand. -Must be greater than or equal to 'concurrentConsumers'. -See <>. - -a|image::images/tickmark.png[] -a| -a| - -|[[messagesPerAck]]<> + -(N/A) - -|The number of messages to receive between acks. -Use this to reduce the number of acks sent to the broker (at the cost of increasing the possibility of redelivered messages). -Generally, you should set this property only on high-volume listener containers. -If this is set and a message is rejected (exception thrown), pending acks are acknowledged and the failed message is rejected. -Not allowed with transacted channels. -If the `prefetchCount` is less than the `messagesPerAck`, it is increased to match the `messagesPerAck`. -Default: ack every message. -See also `ackTimeout` in this table. - -a| -a|image::images/tickmark.png[] -a| - -|[[mismatchedQueuesFatal]]<> + -(mismatched-queues-fatal) - -a|When the container starts, if this property is `true` (default: `false`), the container checks that all queues declared in the context are compatible with queues already on the broker. -If mismatched properties (such as `auto-delete`) or arguments (skuch as `x-message-ttl`) exist, the container (and application context) fails to start with a fatal exception. - -If the problem is detected during recovery (for example, after a lost connection), the container is stopped. - -There must be a single `RabbitAdmin` in the application context (or one specifically configured on the container by using the `rabbitAdmin` property). -Otherwise, this property must be `false`. - -NOTE: If the broker is not available during initial startup, the container starts and the conditions are checked when the connection is established. - -IMPORTANT: The check is done against all queues in the context, not just the queues that a particular listener is configured to use. -If you wish to limit the checks to just those queues used by a container, you should configure a separate `RabbitAdmin` for the container, and provide a reference to it using the `rabbitAdmin` property. -See <> for more information. - -IMPORTANT: Mismatched queue argument detection is disabled while starting a container for a `@RabbitListener` in a bean that is marked `@Lazy`. -This is to avoid a potential deadlock which can delay the start of such containers for up to 60 seconds. -Applications using lazy listener beans should check the queue arguments before getting a reference to the lazy bean. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[missingQueuesFatal]]<> + -(missing-queues-fatal) - -a|When set to `true` (default), if none of the configured queues are available on the broker, it is considered fatal. -This causes the application context to fail to initialize during startup. -Also, when the queues are deleted while the container is running, by default, the consumers make three retries to connect to the queues (at five second intervals) and stop the container if these attempts fail. - -This was not configurable in previous versions. - -When set to `false`, after making the three retries, the container goes into recovery mode, as with other problems, such as the broker being down. -The container tries to recover according to the `recoveryInterval` property. -During each recovery attempt, each consumer again tries four times to passively declare the queues at five second intervals. -This process continues indefinitely. - -You can also use a properties bean to set the property globally for all containers, as follows: - -==== -[source,xml] ----- - - - false - - ----- -==== - -This global property is not applied to any containers that have an explicit `missingQueuesFatal` property set. - -The default retry properties (three retries at five-second intervals) can be overridden by setting the properties below. - -IMPORTANT: Missing queue detection is disabled while starting a container for a `@RabbitListener` in a bean that is marked `@Lazy`. -This is to avoid a potential deadlock which can delay the start of such containers for up to 60 seconds. -Applications using lazy listener beans should check the queue(s) before getting a reference to the lazy bean. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[monitorInterval]]<> + -(monitor-interval) - -|With the DMLC, a task is scheduled to run at this interval to monitor the state of the consumers and recover any that have failed. - -a| -a|image::images/tickmark.png[] -a| - -|[[noLocal]]<> + -(N/A) - -|Set to `true` to disable delivery from the server to consumers messages published on the same channel's connection. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[phase]]<> + -(phase) - -|When `autoStartup` is `true`, the lifecycle phase within which this container should start and stop. -The lower the value, the earlier this container starts and the later it stops. -The default is `Integer.MAX_VALUE`, meaning the container starts as late as possible and stops as soon as possible. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[possibleAuthenticationFailureFatal]]<> + -(possible-authentication-failure-fatal) - -a|When set to `true` (default for SMLC), if a `PossibleAuthenticationFailureException` is thrown during connection, it is considered fatal. -This causes the application context to fail to initialize during startup (if the container is configured with auto startup). - -Since _version 2.0_. - -**DirectMessageListenerContainer** - -When set to `false` (default), each consumer will attempt to reconnect according to the `monitorInterval`. - -**SimpleMessageListenerContainer** - -When set to `false`, after making the 3 retries, the container will go into recovery mode, as with other problems, such as the broker being down. -The container will attempt to recover according to the `recoveryInterval` property. -During each recovery attempt, each consumer will again try 4 times to start. -This process will continue indefinitely. - -You can also use a properties bean to set the property globally for all containers, as follows: - -[source,xml] ----- - - - false - - ----- - -This global property will not be applied to any containers that have an explicit `missingQueuesFatal` property set. - -The default retry properties (3 retries at 5 second intervals) can be overridden using the properties after this one. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[prefetchCount]]<> + -(prefetch) - -a|The number of unacknowledged messages that can be outstanding at each consumer. -The higher this value is, the faster the messages can be delivered, but the higher the risk of non-sequential processing. -Ignored if the `acknowledgeMode` is `NONE`. -This is increased, if necessary, to match the `batchSize` or `messagePerAck`. -Defaults to 250 since 2.0. -You can set it to 1 to revert to the previous behavior. - -IMPORTANT: There are scenarios where the prefetch value should -be low -- for example, with large messages, especially if the processing is slow (messages could add up -to a large amount of memory in the client process), and if strict message ordering is necessary -(the prefetch value should be set back to 1 in this case). -Also, with low-volume messaging and multiple consumers (including concurrency within a single listener container instance), you may wish to reduce the prefetch to get a more even distribution of messages across consumers. - -Also see `globalQos`. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[rabbitAdmin]]<> + -(admin) - -|When a listener container listens to at least one auto-delete queue and it is found to be missing during startup, the container uses a `RabbitAdmin` to declare the queue and any related bindings and exchanges. -If such elements are configured to use conditional declaration (see <>), the container must use the admin that was configured to declare those elements. -Specify that admin here. -It is required only when using auto-delete queues with conditional declaration. -If you do not wish the auto-delete queues to be declared until the container is started, set `auto-startup` to `false` on the admin. -Defaults to a `RabbitAdmin` that declares all non-conditional elements. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[receiveTimeout]]<> + -(receive-timeout) - -|The maximum time to wait for each message. -If `acknowledgeMode=NONE`, this has very little effect -- the container spins round and asks for another message. -It has the biggest effect for a transactional `Channel` with `batchSize > 1`, since it can cause messages already consumed not to be acknowledged until the timeout expires. -When `consumerBatchEnabled` is true, a partial batch will be delivered if this timeout occurs before a batch is complete. - -a|image::images/tickmark.png[] -a| -a| - -|[[recoveryBackOff]]<> + -(recovery-back-off) - -|Specifies the `BackOff` for intervals between attempts to start a consumer if it fails to start for non-fatal reasons. -Default is `FixedBackOff` with unlimited retries every five seconds. -Mutually exclusive with `recoveryInterval`. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[recoveryInterval]]<> + -(recovery-interval) - -|Determines the time in milliseconds between attempts to start a consumer if it fails to start for non-fatal reasons. -Default: 5000. -Mutually exclusive with `recoveryBackOff`. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[retryDeclarationInterval]]<> + -(missing-queue- -retry-interval) - -|If a subset of the configured queues are available during consumer initialization, the consumer starts consuming from those queues. -The consumer tries to passively declare the missing queues by using this interval. -When this interval elapses, the 'declarationRetries' and 'failedDeclarationRetryInterval' is used again. -If there are still missing queues, the consumer again waits for this interval before trying again. -This process continues indefinitely until all queues are available. -Default: 60000 (one minute). - -a|image::images/tickmark.png[] -a| -a| - -|[[shutdownTimeout]]<> + -(N/A) - -|When a container shuts down (for example, -if its enclosing `ApplicationContext` is closed), it waits for in-flight messages to be processed up to this limit. -Defaults to five seconds. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[startConsumerMinInterval]]<> + -(min-start-interval) - -|The time in milliseconds that must elapse before each new consumer is started on demand. -See <>. -Default: 10000 (10 seconds). - -a|image::images/tickmark.png[] -a| -a| - -|[[statefulRetryFatal]]<> + -WithNullMessageId -(N/A) - -|When using a stateful retry advice, if a message with a missing `messageId` property is received, it is considered -fatal for the consumer (it is stopped) by default. -Set this to `false` to discard (or route to a dead-letter queue) such messages. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[stopConsumerMinInterval]]<> + -(min-stop-interval) - -|The time in milliseconds that must elapse before a consumer is stopped since the last consumer was stopped when an idle consumer is detected. -See <>. -Default: 60000 (one minute). - -a|image::images/tickmark.png[] -a| -a| - -|[[streamConverter]]<> + -(N/A) - -|A `StreamMessageConverter` to convert a native Stream message to a Spring AMQP message. - -a| -a| -a|image::images/tickmark.png[] - -|[[taskExecutor]]<> + -(task-executor) - -|A reference to a Spring `TaskExecutor` (or standard JDK 1.5+ `Executor`) for executing listener invokers. -Default is a `SimpleAsyncTaskExecutor`, using internally managed threads. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| - -|[[taskScheduler]]<> + -(task-scheduler) - -|With the DMLC, the scheduler used to run the monitor task at the 'monitorInterval'. - -a| -a|image::images/tickmark.png[] -a| - -|[[transactionManager]]<> + -(transaction-manager) - -|External transaction manager for the operation of the listener. -Also complementary to `channelTransacted` -- if the `Channel` is transacted, its transaction is synchronized with the external transaction. - -a|image::images/tickmark.png[] -a|image::images/tickmark.png[] -a| -|=== - -[[listener-concurrency]] -==== Listener Concurrency - -===== SimpleMessageListenerContainer - -By default, the listener container starts a single consumer that receives messages from the queues. - -When examining the table in the previous section, you can see a number of properties and attributes that control concurrency. -The simplest is `concurrentConsumers`, which creates that (fixed) number of consumers that concurrently process messages. - -Prior to version 1.3.0, this was the only setting available and the container had to be stopped and started again to change the setting. - -Since version 1.3.0, you can now dynamically adjust the `concurrentConsumers` property. -If it is changed while the container is running, consumers are added or removed as necessary to adjust to the new setting. - -In addition, a new property called `maxConcurrentConsumers` has been added and the container dynamically adjusts the concurrency based on workload. -This works in conjunction with four additional properties: `consecutiveActiveTrigger`, `startConsumerMinInterval`, `consecutiveIdleTrigger`, and `stopConsumerMinInterval`. -With the default settings, the algorithm to increase consumers works as follows: - -If the `maxConcurrentConsumers` has not been reached and an existing consumer is active for ten consecutive cycles AND at least 10 seconds has elapsed since the last consumer was started, a new consumer is started. -A consumer is considered active if it received at least one message in `batchSize` * `receiveTimeout` milliseconds. - -With the default settings, the algorithm to decrease consumers works as follows: - -If there are more than `concurrentConsumers` running and a consumer detects ten consecutive timeouts (idle) AND the last consumer was stopped at least 60 seconds ago, a consumer is stopped. -The timeout depends on the `receiveTimeout` and the `batchSize` properties. -A consumer is considered idle if it receives no messages in `batchSize` * `receiveTimeout` milliseconds. -So, with the default timeout (one second) and a `batchSize` of four, stopping a consumer is considered after 40 seconds of idle time (four timeouts correspond to one idle detection). - -NOTE: Practically, consumers can be stopped only if the whole container is idle for some time. -This is because the broker shares its work across all the active consumers. - -Each consumer uses a single channel, regardless of the number of configured queues. - -Starting with version 2.0, the `concurrentConsumers` and `maxConcurrentConsumers` properties can be set with the `concurrency` property -- for example, `2-4`. - -===== Using `DirectMessageListenerContainer` - -With this container, concurrency is based on the configured queues and `consumersPerQueue`. -Each consumer for each queue uses a separate channel, and the concurrency is controlled by the rabbit client library. -By default, at the time of writing, it uses a pool of `DEFAULT_NUM_THREADS = Runtime.getRuntime().availableProcessors() * 2` threads. - -You can configure a `taskExecutor` to provide the required maximum concurrency. - -[[exclusive-consumer]] -==== Exclusive Consumer - -Starting with version 1.3, you can configure the listener container with a single exclusive consumer. -This prevents other containers from consuming from the queues until the current consumer is cancelled. -The concurrency of such a container must be `1`. - -When using exclusive consumers, other containers try to consume from the queues according to the `recoveryInterval` property and log a `WARN` message if the attempt fails. - -[[listener-queues]] -==== Listener Container Queues - -Version 1.3 introduced a number of improvements for handling multiple queues in a listener container. - -Container can be initially configured to listen on zero queues. -Queues can be added and removed at runtime. -The `SimpleMessageListenerContainer` recycles (cancels and re-creates) all consumers when any pre-fetched messages have been processed. -The `DirectMessageListenerContainer` creates/cancels individual consumer(s) for each queue without affecting consumers on other queues. -See the https://docs.spring.io/spring-amqp/docs/latest-ga/api/org/springframework/amqp/rabbit/listener/AbstractMessageListenerContainer.html[Javadoc] for the `addQueues`, `addQueueNames`, `removeQueues` and `removeQueueNames` methods. - -If not all queues are available, the container tries to passively declare (and consume from) the missing queues every 60 seconds. - -Also, if a consumer receives a cancel from the broker (for example, if a queue is deleted) the consumer tries to recover, and the recovered consumer continues to process messages from any other configured queues. -Previously, a cancel on one queue cancelled the entire consumer and, eventually, the container would stop due to the missing queue. - -If you wish to permanently remove a queue, you should update the container before or after deleting to queue, to avoid future attempts trying to consume from it. - -==== Resilience: Recovering from Errors and Broker Failures - -Some of the key (and most popular) high-level features that Spring AMQP provides are to do with recovery and automatic re-connection in the event of a protocol error or broker failure. -We have seen all the relevant components already in this guide, but it should help to bring them all together here and call out the features and recovery scenarios individually. - -The primary reconnection features are enabled by the `CachingConnectionFactory` itself. -It is also often beneficial to use the `RabbitAdmin` auto-declaration features. -In addition, if you care about guaranteed delivery, you probably also need to use the `channelTransacted` flag in `RabbitTemplate` and `SimpleMessageListenerContainer` and the `AcknowledgeMode.AUTO` (or manual if you do the acks yourself) in the `SimpleMessageListenerContainer`. - -[[automatic-declaration]] -===== Automatic Declaration of Exchanges, Queues, and Bindings - -The `RabbitAdmin` component can declare exchanges, queues, and bindings on startup. -It does this lazily, through a `ConnectionListener`. -Consequently, if the broker is not present on startup, it does not matter. -The first time a `Connection` is used (for example, -by sending a message) the listener fires and the admin features is applied. -A further benefit of doing the auto declarations in a listener is that, if the connection is dropped for any reason (for example, -broker death, network glitch, and others), they are applied again when the connection is re-established. - -NOTE: Queues declared this way must have fixed names -- either explicitly declared or generated by the framework for `AnonymousQueue` instances. -Anonymous queues are non-durable, exclusive, and auto-deleting. - -IMPORTANT: Automatic declaration is performed only when the `CachingConnectionFactory` cache mode is `CHANNEL` (the default). -This limitation exists because exclusive and auto-delete queues are bound to the connection. - -Starting with version 2.2.2, the `RabbitAdmin` will detect beans of type `DeclarableCustomizer` and apply the function before actually processing the declaration. -This is useful, for example, to set a new argument (property) before it has first class support within the framework. - -==== -[source, java] ----- -@Bean -public DeclarableCustomizer customizer() { - return dec -> { - if (dec instanceof Queue && ((Queue) dec).getName().equals("my.queue")) { - dec.addArgument("some.new.queue.argument", true); - } - return dec; - }; -} ----- -==== - -It is also useful in projects that don't provide direct access to the `Declarable` bean definitions. - -See also <>. - -[[retry]] -===== Failures in Synchronous Operations and Options for Retry - -If you lose your connection to the broker in a synchronous sequence when using `RabbitTemplate` (for instance), Spring AMQP throws an `AmqpException` (usually, but not always, `AmqpIOException`). -We do not try to hide the fact that there was a problem, so you have to be able to catch and respond to the exception. -The easiest thing to do if you suspect that the connection was lost (and it was not your fault) is to try the operation again. -You can do this manually, or you could look at using Spring Retry to handle the retry (imperatively or declaratively). - -Spring Retry provides a couple of AOP interceptors and a great deal of flexibility to specify the parameters of the retry (number of attempts, exception types, backoff algorithm, and others). -Spring AMQP also provides some convenience factory beans for creating Spring Retry interceptors in a convenient form for AMQP use cases, with strongly typed callback interfaces that you can use to implement custom recovery logic. -See the Javadoc and properties of `StatefulRetryOperationsInterceptor` and `StatelessRetryOperationsInterceptor` for more detail. -Stateless retry is appropriate if there is no transaction or if a transaction is started inside the retry callback. -Note that stateless retry is simpler to configure and analyze than stateful retry, but it is not usually appropriate if there is an ongoing transaction that must be rolled back or definitely is going to roll back. -A dropped connection in the middle of a transaction should have the same effect as a rollback. -Consequently, for reconnections where the transaction is started higher up the stack, stateful retry is usually the best choice. -Stateful retry needs a mechanism to uniquely identify a message. -The simplest approach is to have the sender put a unique value in the `MessageId` message property. -The provided message converters provide an option to do this: you can set `createMessageIds` to `true`. -Otherwise, you can inject a `MessageKeyGenerator` implementation into the interceptor. -The key generator must return a unique key for each message. -In versions prior to version 2.0, a `MissingMessageIdAdvice` was provided. -It enabled messages without a `messageId` property to be retried exactly once (ignoring the retry settings). -This advice is no longer provided, since, along with `spring-retry` version 1.2, its functionality is built into the interceptor and message listener containers. - -NOTE: For backwards compatibility, a message with a null message ID is considered fatal for the consumer (consumer is stopped) by default (after one retry). -To replicate the functionality provided by the `MissingMessageIdAdvice`, you can set the `statefulRetryFatalWithNullMessageId` property to `false` on the listener container. -With that setting, the consumer continues to run and the message is rejected (after one retry). -It is discarded or routed to the dead letter queue (if one is configured). - -Starting with version 1.3, a builder API is provided to aid in assembling these interceptors by using Java (in `@Configuration` classes). -The following example shows how to do so: - -==== -[source,java] ----- -@Bean -public StatefulRetryOperationsInterceptor interceptor() { - return RetryInterceptorBuilder.stateful() - .maxAttempts(5) - .backOffOptions(1000, 2.0, 10000) // initialInterval, multiplier, maxInterval - .build(); -} ----- -==== - -Only a subset of retry capabilities can be configured this way. -More advanced features would need the configuration of a `RetryTemplate` as a Spring bean. -See the https://docs.spring.io/spring-retry/docs/api/current/[Spring Retry Javadoc] for complete information about available policies and their configuration. - -[[batch-retry]] -===== Retry with Batch Listeners - -It is not recommended to configure retry with a batch listener, unless the batch was created by the producer, in a single record. -See <> for information about consumer and producer-created batches. -With a consumer-created batch, the framework has no knowledge about which message in the batch caused the failure so recovery after the retries are exhausted is not possible. -With producer-created batches, since there is only one message that actually failed, the whole message can be recovered. -Applications may want to inform a custom recoverer where in the batch the failure occurred, perhaps by setting an index property of the thrown exception. - -A retry recoverer for a batch listener must implement `MessageBatchRecoverer`. - -[[async-listeners]] -===== Message Listeners and the Asynchronous Case - -If a `MessageListener` fails because of a business exception, the exception is handled by the message listener container, which then goes back to listening for another message. -If the failure is caused by a dropped connection (not a business exception), the consumer that is collecting messages for the listener has to be cancelled and restarted. -The `SimpleMessageListenerContainer` handles this seamlessly, and it leaves a log to say that the listener is being restarted. -In fact, it loops endlessly, trying to restart the consumer. -Only if the consumer is very badly behaved indeed will it give up. -One side effect is that if the broker is down when the container starts, it keeps trying until a connection can be established. - -Business exception handling, as opposed to protocol errors and dropped connections, might need more thought and some custom configuration, especially if transactions or container acks are in use. -Prior to 2.8.x, RabbitMQ had no definition of dead letter behavior. -Consequently, by default, a message that is rejected or rolled back because of a business exception can be redelivered endlessly. -To put a limit on the client on the number of re-deliveries, one choice is a `StatefulRetryOperationsInterceptor` in the advice chain of the listener. -The interceptor can have a recovery callback that implements a custom dead letter action -- whatever is appropriate for your particular environment. - -Another alternative is to set the container's `defaultRequeueRejected` property to `false`. -This causes all failed messages to be discarded. -When using RabbitMQ 2.8.x or higher, this also facilitates delivering the message to a dead letter exchange. - -Alternatively, you can throw a `AmqpRejectAndDontRequeueException`. -Doing so prevents message requeuing, regardless of the setting of the `defaultRequeueRejected` property. - -Starting with version 2.1, an `ImmediateRequeueAmqpException` is introduced to perform exactly the opposite logic: the message will be requeued, regardless of the setting of the `defaultRequeueRejected` property. - -Often, a combination of both techniques is used. -You can use a `StatefulRetryOperationsInterceptor` in the advice chain with a `MessageRecoverer` that throws an `AmqpRejectAndDontRequeueException`. -The `MessageRecover` is called when all retries have been exhausted. -The `RejectAndDontRequeueRecoverer` does exactly that. -The default `MessageRecoverer` consumes the errant message and emits a `WARN` message. - -Starting with version 1.3, a new `RepublishMessageRecoverer` is provided, to allow publishing of failed messages after retries are exhausted. - -When a recoverer consumes the final exception, the message is ack'd and is not sent to the dead letter exchange by the broker, if configured. - -NOTE: When `RepublishMessageRecoverer` is used on the consumer side, the received message has `deliveryMode` in the `receivedDeliveryMode` message property. -In this case the `deliveryMode` is `null`. -That means a `NON_PERSISTENT` delivery mode on the broker. -Starting with version 2.0, you can configure the `RepublishMessageRecoverer` for the `deliveryMode` to set into the message to republish if it is `null`. -By default, it uses `MessageProperties` default value - `MessageDeliveryMode.PERSISTENT`. - -The following example shows how to set a `RepublishMessageRecoverer` as the recoverer: - -==== -[source,java] ----- -@Bean -RetryOperationsInterceptor interceptor() { - return RetryInterceptorBuilder.stateless() - .maxAttempts(5) - .recoverer(new RepublishMessageRecoverer(amqpTemplate(), "something", "somethingelse")) - .build(); -} ----- -==== - -The `RepublishMessageRecoverer` publishes the message with additional information in message headers, such as the exception message, stack trace, original exchange, and routing key. -Additional headers can be added by creating a subclass and overriding `additionalHeaders()`. -The `deliveryMode` (or any other properties) can also be changed in the `additionalHeaders()`, as the following example shows: - -==== -[source,java] ----- -RepublishMessageRecoverer recoverer = new RepublishMessageRecoverer(amqpTemplate, "error") { - - protected Map additionalHeaders(Message message, Throwable cause) { - message.getMessageProperties() - .setDeliveryMode(message.getMessageProperties().getReceivedDeliveryMode()); - return null; - } - -}; ----- -==== - -Starting with version 2.0.5, the stack trace may be truncated if it is too large; this is because all headers have to fit in a single frame. -By default, if the stack trace would cause less than 20,000 bytes ('headroom') to be available for other headers, it will be truncated. -This can be adjusted by setting the recoverer's `frameMaxHeadroom` property, if you need more or less space for other headers. -Starting with versions 2.1.13, 2.2.3, the exception message is included in this calculation, and the amount of stack trace will be maximized using the following algorithm: - -* if the stack trace alone would exceed the limit, the exception message header will be truncated to 97 bytes plus `...` and the stack trace is truncated too. -* if the stack trace is small, the message will be truncated (plus `...`) to fit in the available bytes (but the message within the stack trace itself is truncated to 97 bytes plus `...`). - -Whenever a truncation of any kind occurs, the original exception will be logged to retain the complete information. -The evaluation is performed after the headers are enhanced so information such as the exception type can be used in the expressions. - -Starting with version 2.4.8, the error exchange and routing key can be provided as SpEL expressions, with the `Message` being the root object for the evaluation. - -Starting with version 2.3.3, a new subclass `RepublishMessageRecovererWithConfirms` is provided; this supports both styles of publisher confirms and will wait for the confirmation before returning (or throw an exception if not confirmed or the message is returned). - -If the confirm type is `CORRELATED`, the subclass will also detect if a message is returned and throw an `AmqpMessageReturnedException`; if the publication is negatively acknowledged, it will throw an `AmqpNackReceivedException`. - -If the confirm type is `SIMPLE`, the subclass will invoke the `waitForConfirmsOrDie` method on the channel. - -See <> for more information about confirms and returns. - -Starting with version 2.1, an `ImmediateRequeueMessageRecoverer` is added to throw an `ImmediateRequeueAmqpException`, which notifies a listener container to requeue the current failed message. - -===== Exception Classification for Spring Retry - -Spring Retry has a great deal of flexibility for determining which exceptions can invoke retry. -The default configuration retries for all exceptions. -Given that user exceptions are wrapped in a `ListenerExecutionFailedException`, we need to ensure that the classification examines the exception causes. -The default classifier looks only at the top level exception. - -Since Spring Retry 1.0.3, the `BinaryExceptionClassifier` has a property called `traverseCauses` (default: `false`). -When `true`, it travers exception causes until it finds a match or there is no cause. - -To use this classifier for retry, you can use a `SimpleRetryPolicy` created with the constructor that takes the max attempts, the `Map` of `Exception` instances, and the boolean (`traverseCauses`) and inject this policy into the `RetryTemplate`. - -[[multi-rabbit]] -==== Multiple Broker (or Cluster) Support - -Version 2.3 added more convenience when communicating between a single application and multiple brokers or broker clusters. -The main benefit, on the consumer side, is that the infrastructure can automatically associate auto-declared queues with the appropriate broker. - -This is best illustrated with an example: - -==== -[source, java] ----- -@SpringBootApplication(exclude = RabbitAutoConfiguration.class) -public class Application { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - @Bean - CachingConnectionFactory cf1() { - return new CachingConnectionFactory("localhost"); - } - - @Bean - CachingConnectionFactory cf2() { - return new CachingConnectionFactory("otherHost"); - } - - @Bean - CachingConnectionFactory cf3() { - return new CachingConnectionFactory("thirdHost"); - } - - @Bean - SimpleRoutingConnectionFactory rcf(CachingConnectionFactory cf1, - CachingConnectionFactory cf2, CachingConnectionFactory cf3) { - - SimpleRoutingConnectionFactory rcf = new SimpleRoutingConnectionFactory(); - rcf.setDefaultTargetConnectionFactory(cf1); - rcf.setTargetConnectionFactories(Map.of("one", cf1, "two", cf2, "three", cf3)); - return rcf; - } - - @Bean("factory1-admin") - RabbitAdmin admin1(CachingConnectionFactory cf1) { - return new RabbitAdmin(cf1); - } - - @Bean("factory2-admin") - RabbitAdmin admin2(CachingConnectionFactory cf2) { - return new RabbitAdmin(cf2); - } - - @Bean("factory3-admin") - RabbitAdmin admin3(CachingConnectionFactory cf3) { - return new RabbitAdmin(cf3); - } - - @Bean - public RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry() { - return new RabbitListenerEndpointRegistry(); - } - - @Bean - public RabbitListenerAnnotationBeanPostProcessor postProcessor(RabbitListenerEndpointRegistry registry) { - MultiRabbitListenerAnnotationBeanPostProcessor postProcessor - = new MultiRabbitListenerAnnotationBeanPostProcessor(); - postProcessor.setEndpointRegistry(registry); - postProcessor.setContainerFactoryBeanName("defaultContainerFactory"); - return postProcessor; - } - - @Bean - public SimpleRabbitListenerContainerFactory factory1(CachingConnectionFactory cf1) { - SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); - factory.setConnectionFactory(cf1); - return factory; - } - - @Bean - public SimpleRabbitListenerContainerFactory factory2(CachingConnectionFactory cf2) { - SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); - factory.setConnectionFactory(cf2); - return factory; - } - - @Bean - public SimpleRabbitListenerContainerFactory factory3(CachingConnectionFactory cf3) { - SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); - factory.setConnectionFactory(cf3); - return factory; - } - - @Bean - RabbitTemplate template(SimpleRoutingConnectionFactory rcf) { - return new RabbitTemplate(rcf); - } - - @Bean - ConnectionFactoryContextWrapper wrapper(SimpleRoutingConnectionFactory rcf) { - return new ConnectionFactoryContextWrapper(rcf); - } - -} - -@Component -class Listeners { - - @RabbitListener(queuesToDeclare = @Queue("q1"), containerFactory = "factory1") - public void listen1(String in) { - - } - - @RabbitListener(queuesToDeclare = @Queue("q2"), containerFactory = "factory2") - public void listen2(String in) { - - } - - @RabbitListener(queuesToDeclare = @Queue("q3"), containerFactory = "factory3") - public void listen3(String in) { - - } - -} ----- -==== - -As you can see, we have declared 3 sets of infrastructure (connection factories, admins, container factories). -As discussed earlier, `@RabbitListener` can define which container factory to use; in this case, they also use `queuesToDeclare` which causes the queue(s) to be declared on the broker, if it doesn't exist. -By naming the `RabbitAdmin` beans with the convention `-admin`, the infrastructure is able to determine which admin should declare the queue. -This will also work with `bindings = @QueueBinding(...)` whereby the exchange and binding will also be declared. -It will NOT work with `queues`, since that expects the queue(s) to already exist. - -On the producer side, a convenient `ConnectionFactoryContextWrapper` class is provided, to make using the `RoutingConnectionFactory` (see <>) simpler. - -As you can see above, a `SimpleRoutingConnectionFactory` bean has been added with routing keys `one`, `two` and `three`. -There is also a `RabbitTemplate` that uses that factory. -Here is an example of using that template with the wrapper to route to one of the broker clusters. - -==== -[source, java] ----- -@Bean -public ApplicationRunner runner(RabbitTemplate template, ConnectionFactoryContextWrapper wrapper) { - return args -> { - wrapper.run("one", () -> template.convertAndSend("q1", "toCluster1")); - wrapper.run("two", () -> template.convertAndSend("q2", "toCluster2")); - wrapper.run("three", () -> template.convertAndSend("q3", "toCluster3")); - }; -} ----- -==== - -==== Debugging - -Spring AMQP provides extensive logging, especially at the `DEBUG` level. - -If you wish to monitor the AMQP protocol between the application and broker, you can use a tool such as WireShark, which has a plugin to decode the protocol. -Alternatively, the RabbitMQ Java client comes with a very useful class called `Tracer`. -When run as a `main`, by default, it listens on port 5673 and connects to port 5672 on localhost. -You can run it and change your connection factory configuration to connect to port 5673 on localhost. -It displays the decoded protocol on the console. -Refer to the `Tracer` Javadoc for more information. diff --git a/src/reference/asciidoc/appendix.adoc b/src/reference/asciidoc/appendix.adoc deleted file mode 100644 index 60bee0b6..00000000 --- a/src/reference/asciidoc/appendix.adoc +++ /dev/null @@ -1,1329 +0,0 @@ -[appendix] -[[observation-gen]] -== Micrometer Observation Documentation - -include::../docs/generated/metrics.adoc[] - -include::../docs/generated/spans.adoc[] - -include::../docs/generated/conventions.adoc[] - -[appendix] -[[native-images]] -== Native Images - -https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aot[Spring AOT] native hints are provided to assist in developing native images for Spring applications that use Spring AMQP. - -Some examples can be seen in the https://github.com/spring-projects/spring-aot-smoke-tests/tree/main/integration[`spring-aot-smoke-tests` GitHub repository]. - -[appendix] -[[change-history]] -== Change History - -This section describes changes that have been made as versions have changed. - -=== Current Release - -See <>. - -[[previous-whats-new]] -=== Previous Releases - -==== Changes in 3.0 Since 2.4 - -===== Java 17, Spring Framework 6.0 - -This version requires Spring Framework 6.0 and Java 17 - -===== Remoting - -The remoting feature (using RMI) is no longer supported. - -===== Observation - -Enabling observation for timers and tracing using Micrometer is now supported. -See <> for more information. - -[[x30-Native]] -===== Native Images - -Support for creating native images is provided. -See <> for more information. - -===== AsyncRabbitTemplate - -IMPORTANT: The `AsyncRabbitTemplate` now returns `CompletableFuture` s instead of `ListenableFuture` s. -See <> for more information. - -===== Stream Support Changes - -IMPORTANT: `RabbitStreamOperations` and `RabbitStreamTemplate` methods now return `CompletableFuture` instead of `ListenableFuture`. - -Super streams and single active consumers thereon are now supported. - -See <> for more information. - -===== `@RabbitListener` Changes - -Batch listeners can now consume `Collection` as well as `List`. -The batch messaging adapter now ensures that the method is suitable for consuming batches. -When setting the container factory `consumerBatchEnabled` to `true`, the `batchListener` property is also set to `true`. -See <> for more information. - -`MessageConverter` s can now return `Optional.empty()` for a null value; this is currently implemented by the `Jackson2JsonMessageConverter`. -See <> for more information - -You can now configure a `ReplyPostProcessor` via the container factory rather than via a property on `@RabbitListener`. -See <> for more information. - -The `@RabbitListener` (and `@RabbitHandler`) methods can now be declared as Kotlin `suspend` functions. -See <> for more information. - -Starting with version 3.0.5, listeners with async return types (including Kotlin suspend functions) invoke the `RabbitListenerErrorHandler` (if configured) after a failure. -Previously, the error handler was only invoked with synchronous invocations. - -===== Connection Factory Changes - -The default `addressShuffleMode` in `AbstractConnectionFactory` is now `RANDOM`. -This results in connecting to a random host when multiple addresses are provided. -See <> for more information. - -The `LocalizedQueueConnectionFactory` no longer uses the RabbitMQ `http-client` library to determine which node is the leader for a queue. -See <> for more information. - -==== Changes in 2.4 Since 2.3 - -This section describes the changes between version 2.3 and version 2.4. -See <> for changes in previous versions. - -===== `@RabbitListener` Changes - -`MessageProperties` is now available for argument matching. -See <> for more information. - -===== `RabbitAdmin` Changes - -A new property `recoverManualDeclarations` allows recovery of manually declared queues/exchanges/bindings. -See <> for more information. - -===== Remoting Support - -Support remoting using Spring Framework’s RMI support is deprecated and will be removed in 3.0. See Spring Remoting with AMQP for more information. - -==== Message Converter Changes - -The `Jackson2JsonMessageConverter` can now determine the charset from the `contentEncoding` header. -See <> for more information. - -==== Message Converter Changes - -The `Jackson2JsonMessageConverter` can now determine the charset from the `contentEncoding` header. -See <> for more information. - -==== Stream Support Changes - -`RabbitStreamOperations` and `RabbitStreamTemplate` have been deprecated in favor of `RabbitStreamOperations2` and `RabbitStreamTemplate2` respectively; they return `CompletableFuture` instead of `ListenableFuture`. -See <> for more information. - -==== Changes in 2.3 Since 2.2 - -This section describes the changes between version 2.2 and version 2.3. -See <> for changes in previous versions. - -===== Connection Factory Changes - -Two additional connection factories are now provided. -See <> for more information. - -===== `@RabbitListener` Changes - -You can now specify a reply content type. -See <> for more information. - -===== Message Converter Changes - -The `Jackson2JMessageConverter` s can now deserialize abstract classes (including interfaces) if the `ObjectMapper` is configured with a custom deserializer. -See <> for more information. - -===== Testing Changes - -A new annotation `@SpringRabbitTest` is provided to automatically configure some infrastructure beans for when you are not using `SpringBootTest`. -See <> for more information. - -===== RabbitTemplate Changes - -The template's `ReturnCallback` has been refactored as `ReturnsCallback` for simpler use in lambda expressions. -See <> for more information. - -When using returns and correlated confirms, the `CorrelationData` now requires a unique `id` property. -See <> for more information. - -When using direct reply-to, you can now configure the template such that the server does not need to return correlation data with the reply. -See <> for more information. - -===== Listener Container Changes - -A new listener container property `consumeDelay` is now available; it is helpful when using the https://github.com/rabbitmq/rabbitmq-sharding[RabbitMQ Sharding Plugin]. - -The default `JavaLangErrorHandler` now calls `System.exit(99)`. -To revert to the previous behavior (do nothing), add a no-op handler. - -The containers now support the `globalQos` property to apply the `prefetchCount` globally for the channel rather than for each consumer on the channel. - -See <> for more information. - -===== MessagePostProcessor Changes - -The compressing `MessagePostProcessor` s now use a comma to separate multiple content encodings instead of a colon. -The decompressors can handle both formats but, if you produce messages with this version that are consumed by versions earlier than 2.2.12, you should configure the compressor to use the old delimiter. -See the IMPORTANT note in <> for more information. - -===== Multiple Broker Support Improvements - -See <> for more information. - -===== RepublishMessageRecoverer Changes - -A new subclass of this recoverer is not provided that supports publisher confirms. -See <> for more information. - -==== Changes in 2.2 Since 2.1 - -This section describes the changes between version 2.1 and version 2.2. - -===== Package Changes - -The following classes/interfaces have been moved from `org.springframework.amqp.rabbit.core.support` to `org.springframework.amqp.rabbit.batch`: - -* `BatchingStrategy` -* `MessageBatch` -* `SimpleBatchingStrategy` - -In addition, `ListenerExecutionFailedException` has been moved from `org.springframework.amqp.rabbit.listener.exception` to `org.springframework.amqp.rabbit.support`. - -===== Dependency Changes - -JUnit (4) is now an optional dependency and will no longer appear as a transitive dependency. - -The `spring-rabbit-junit` module is now a *compile* dependency in the `spring-rabbit-test` module for a better target application development experience when with only a single `spring-rabbit-test` we get the full stack of testing utilities for AMQP components. - -===== "Breaking" API Changes - -the JUnit (5) `RabbitAvailableCondition.getBrokerRunning()` now returns a `BrokerRunningSupport` instance instead of a `BrokerRunning`, which depends on JUnit 4. -It has the same API so it's just a matter of changing the class name of any references. -See <> for more information. - -===== ListenerContainer Changes - -Messages with fatal exceptions are now rejected and NOT requeued, by default, even if the acknowledge mode is manual. -See <> for more information. - -Listener performance can now be monitored using Micrometer `Timer` s. -See <> for more information. - -===== @RabbitListener Changes - -You can now configure an `executor` on each listener, overriding the factory configuration, to more easily identify threads associated with the listener. -You can now override the container factory's `acknowledgeMode` property with the annotation's `ackMode` property. -See <> for more information. - -When using <>, `@RabbitListener` methods can now receive a complete batch of messages in one call instead of getting them one-at-a-time. - -When receiving batched messages one-at-a-time, the last message has the `isLastInBatch` message property set to true. - -In addition, received batched messages now contain the `amqp_batchSize` header. - -Listeners can also consume batches created in the `SimpleMessageListenerContainer`, even if the batch is not created by the producer. -See <> for more information. - -Spring Data Projection interfaces are now supported by the `Jackson2JsonMessageConverter`. -See <> for more information. - -The `Jackson2JsonMessageConverter` now assumes the content is JSON if there is no `contentType` property, or it is the default (`application/octet-string`). -See <> for more information. - -Similarly. the `Jackson2XmlMessageConverter` now assumes the content is XML if there is no `contentType` property, or it is the default (`application/octet-string`). -See <> for more information. - -When a `@RabbitListener` method returns a result, the bean and `Method` are now available in the reply message properties. -This allows configuration of a `beforeSendReplyMessagePostProcessor` to, for example, set a header in the reply to indicate which method was invoked on the server. -See <> for more information. - -You can now configure a `ReplyPostProcessor` to make modifications to a reply message before it is sent. -See <> for more information. - -===== AMQP Logging Appenders Changes - -The Log4J and Logback `AmqpAppender` s now support a `verifyHostname` SSL option. - -Also these appenders now can be configured to not add MDC entries as headers. -The `addMdcAsHeaders` boolean option has been introduces to configure such a behavior. - -The appenders now support the `SaslConfig` property. - -See <> for more information. - -===== MessageListenerAdapter Changes - -The `MessageListenerAdapter` provides now a new `buildListenerArguments(Object, Channel, Message)` method to build an array of arguments to be passed into target listener and an old one is deprecated. -See <> for more information. - -===== Exchange/Queue Declaration Changes - -The `ExchangeBuilder` and `QueueBuilder` fluent APIs used to create `Exchange` and `Queue` objects for declaration by `RabbitAdmin` now support "well known" arguments. -See <> for more information. - -The `RabbitAdmin` has a new property `explicitDeclarationsOnly`. -See <> for more information. - -===== Connection Factory Changes - -The `CachingConnectionFactory` has a new property `shuffleAddresses`. -When providing a list of broker node addresses, the list will be shuffled before creating a connection so that the order in which the connections are attempted is random. -See <> for more information. - -When using Publisher confirms and returns, the callbacks are now invoked on the connection factory's `executor`. -This avoids a possible deadlock in the `amqp-clients` library if you perform rabbit operations from within the callback. -See <> for more information. - -Also, the publisher confirm type is now specified with the `ConfirmType` enum instead of the two mutually exclusive setter methods. - -The `RabbitConnectionFactoryBean` now uses TLS 1.2 by default when SSL is enabled. -See <> for more information. - -===== New MessagePostProcessor Classes - -Classes `DeflaterPostProcessor` and `InflaterPostProcessor` were added to support compression and decompression, respectively, when the message content-encoding is set to `deflate`. - -===== Other Changes - -The `Declarables` object (for declaring multiple queues, exchanges, bindings) now has a filtered getter for each type. -See <> for more information. - -You can now customize each `Declarable` bean before the `RabbitAdmin` processes the declaration thereof. -See <> for more information. - -`singleActiveConsumer()` has been added to the `QueueBuilder` to set the `x-single-active-consumer` queue argument. -See <> for more information. - -Outbound headers with values of type `Class` are now mapped using `getName()` instead of `toString()`. -See <> for more information. - -Recovery of failed producer-created batches is now supported. -See <> for more information. - -==== Changes in 2.1 Since 2.0 - -===== AMQP Client library - -Spring AMQP now uses the 5.4.x version of the `amqp-client` library provided by the RabbitMQ team. -This client has auto-recovery configured by default. -See <>. - -NOTE: As of version 4.0, the client enables automatic recovery by default. -While compatible with this feature, Spring AMQP has its own recovery mechanisms and the client recovery feature generally is not needed. -We recommend disabling `amqp-client` automatic recovery, to avoid getting `AutoRecoverConnectionNotCurrentlyOpenException` instances when the broker is available but the connection has not yet recovered. -Starting with version 1.7.1, Spring AMQP disables it unless you explicitly create your own RabbitMQ connection factory and provide it to the `CachingConnectionFactory`. -RabbitMQ `ConnectionFactory` instances created by the `RabbitConnectionFactoryBean` also have the option disabled by default. - - -===== Package Changes - -Certain classes have moved to different packages. -Most are internal classes and do not affect user applications. -Two exceptions are `ChannelAwareMessageListener` and `RabbitListenerErrorHandler`. -These interfaces are now in `org.springframework.amqp.rabbit.listener.api`. - -===== Publisher Confirms Changes - -Channels enabled for publisher confirmations are not returned to the cache while there are outstanding confirmations. -See <> for more information. - -===== Listener Container Factory Improvements - -You can now use the listener container factories to create any listener container, not only those for use with `@RabbitListener` annotations or the `@RabbitListenerEndpointRegistry`. -See <> for more information. - -`ChannelAwareMessageListener` now inherits from `MessageListener`. - -===== Broker Event Listener - -A `BrokerEventListener` is introduced to publish selected broker events as `ApplicationEvent` instances. -See <> for more information. - -===== RabbitAdmin Changes - -The `RabbitAdmin` discovers beans of type `Declarables` (which is a container for `Declarable` - `Queue`, `Exchange`, and `Binding` objects) and declare the contained objects on the broker. -Users are discouraged from using the old mechanism of declaring `>` (and others) and should use `Declarables` beans instead. -By default, the old mechanism is disabled. -See <> for more information. - -`AnonymousQueue` instances are now declared with `x-queue-master-locator` set to `client-local` by default, to ensure the queues are created on the node the application is connected to. -See <> for more information. - -===== RabbitTemplate Changes - -You can now configure the `RabbitTemplate` with the `noLocalReplyConsumer` option to control a `noLocal` flag for reply consumers in the `sendAndReceive()` operations. -See <> for more information. - -`CorrelationData` for publisher confirmations now has a `ListenableFuture`, which you can use to get the acknowledgment instead of using a callback. -When returns and confirmations are enabled, the correlation data, if provided, is populated with the returned message. -See <> for more information. - -A method called `replyTimedOut` is now provided to notify subclasses that a reply has timed out, allowing for any state cleanup. -See <> for more information. - -You can now specify an `ErrorHandler` to be invoked when using request/reply with a `DirectReplyToMessageListenerContainer` (the default) when exceptions occur when replies are delivered (for example, late replies). -See `setReplyErrorHandler` on the `RabbitTemplate`. -(Also since 2.0.11). - -===== Message Conversion - -We introduced a new `Jackson2XmlMessageConverter` to support converting messages from and to XML format. -See <> for more information. - -===== Management REST API - -The `RabbitManagementTemplate` is now deprecated in favor of the direct `com.rabbitmq.http.client.Client` (or `com.rabbitmq.http.client.ReactorNettyClient`) usage. -See <> for more information. - -===== `@RabbitListener` Changes - -The listener container factory can now be configured with a `RetryTemplate` and, optionally, a `RecoveryCallback` used when sending replies. -See <> for more information. - -===== Async `@RabbitListener` Return - -`@RabbitListener` methods can now return `ListenableFuture` or `Mono`. -See <> for more information. - -===== Connection Factory Bean Changes - -By default, the `RabbitConnectionFactoryBean` now calls `enableHostnameVerification()`. -To revert to the previous behavior, set the `enableHostnameVerification` property to `false`. - -===== Connection Factory Changes - -The `CachingConnectionFactory` now unconditionally disables auto-recovery in the underlying RabbitMQ `ConnectionFactory`, even if a pre-configured instance is provided in a constructor. -While steps have been taken to make Spring AMQP compatible with auto recovery, certain corner cases have arisen where issues remain. -Spring AMQP has had its own recovery mechanism since 1.0.0 and does not need to use the recovery provided by the client. -While it is still possible to enable the feature (using `cachingConnectionFactory.getRabbitConnectionFactory()` `.setAutomaticRecoveryEnabled()`) after the `CachingConnectionFactory` is constructed, **we strongly recommend that you not do so**. -We recommend that you use a separate RabbitMQ `ConnectionFactory` if you need auto recovery connections when using the client factory directly (rather than using Spring AMQP components). - -===== Listener Container Changes - -The default `ConditionalRejectingErrorHandler` now completely discards messages that cause fatal errors if an `x-death` header is present. -See <> for more information. - -===== Immediate requeue - -A new `ImmediateRequeueAmqpException` is introduced to notify a listener container that the message has to be re-queued. -To use this feature, a new `ImmediateRequeueMessageRecoverer` implementation is added. - -See <> for more information. - - -==== Changes in 2.0 Since 1.7 - -===== Using `CachingConnectionFactory` - -Starting with version 2.0.2, you can configure the `RabbitTemplate` to use a different connection to that used by listener containers. -This change avoids deadlocked consumers when producers are blocked for any reason. -See <> for more information. - -===== AMQP Client library - -Spring AMQP now uses the new 5.0.x version of the `amqp-client` library provided by the RabbitMQ team. -This client has auto recovery configured by default. -See <>. - -NOTE: As of version 4.0, the client enables automatic recovery by default. -While compatible with this feature, Spring AMQP has its own recovery mechanisms, and the client recovery feature generally is not needed. -We recommend that you disable `amqp-client` automatic recovery, to avoid getting `AutoRecoverConnectionNotCurrentlyOpenException` instances when the broker is available but the connection has not yet recovered. -Starting with version 1.7.1, Spring AMQP disables it unless you explicitly create your own RabbitMQ connection factory and provide it to the `CachingConnectionFactory`. -RabbitMQ `ConnectionFactory` instances created by the `RabbitConnectionFactoryBean` also have the option disabled by default. - -===== General Changes - -The `ExchangeBuilder` now builds durable exchanges by default. -The `@Exchange` annotation used within a `@QeueueBinding` also declares durable exchanges by default. -The `@Queue` annotation used within a `@RabbitListener` by default declares durable queues if named and non-durable if anonymous. -See <> and <> for more information. - -===== Deleted Classes - -`UniquelyNameQueue` is no longer provided. -It is unusual to create a durable non-auto-delete queue with a unique name. -This class has been deleted. -If you require its functionality, use `new Queue(UUID.randomUUID().toString())`. - -===== New Listener Container - -The `DirectMessageListenerContainer` has been added alongside the existing `SimpleMessageListenerContainer`. -See <> and <> for information about choosing which container to use as well as how to configure them. - - -===== Log4j Appender - -This appender is no longer available due to the end-of-life of log4j. -See <> for information about the available log appenders. - - -===== `RabbitTemplate` Changes - -IMPORTANT: Previously, a non-transactional `RabbitTemplate` participated in an existing transaction if it ran on a transactional listener container thread. -This was a serious bug. -However, users might have relied on this behavior. -Starting with version 1.6.2, you must set the `channelTransacted` boolean on the template for it to participate in the container transaction. - -The `RabbitTemplate` now uses a `DirectReplyToMessageListenerContainer` (by default) instead of creating a new consumer for each request. -See <> for more information. - -The `AsyncRabbitTemplate` now supports direct reply-to. -See <> for more information. - -The `RabbitTemplate` and `AsyncRabbitTemplate` now have `receiveAndConvert` and `convertSendAndReceiveAsType` methods that take a `ParameterizedTypeReference` argument, letting the caller specify the type to which to convert the result. -This is particularly useful for complex types or when type information is not conveyed in message headers. -It requires a `SmartMessageConverter` such as the `Jackson2JsonMessageConverter`. -See <>, <>, <>, and <> for more information. - -You can now use a `RabbitTemplate` to perform multiple operations on a dedicated channel. -See <> for more information. - -===== Listener Adapter - -A convenient `FunctionalInterface` is available for using lambdas with the `MessageListenerAdapter`. -See <> for more information. - -===== Listener Container Changes - -====== Prefetch Default Value - -The prefetch default value used to be 1, which could lead to under-utilization of efficient consumers. -The default prefetch value is now 250, which should keep consumers busy in most common scenarios and, -thus, improve throughput. - -IMPORTANT: There are scenarios where the prefetch value should -be low -- for example, with large messages, especially if the processing is slow (messages could add up -to a large amount of memory in the client process), and if strict message ordering is necessary -(the prefetch value should be set back to 1 in this case). -Also, with low-volume messaging and multiple consumers (including concurrency within a single listener container instance), you may wish to reduce the prefetch to get a more even distribution of messages across consumers. - -For more background about prefetch, see this post about https://www.rabbitmq.com/blog/2014/04/14/finding-bottlenecks-with-rabbitmq-3-3/[consumer utilization in RabbitMQ] -and this post about https://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/[queuing theory]. - -====== Message Count - -Previously, `MessageProperties.getMessageCount()` returned `0` for messages emitted by the container. -This property applies only when you use `basicGet` (for example, from `RabbitTemplate.receive()` methods) and is now initialized to `null` for container messages. - -====== Transaction Rollback Behavior - -Message re-queue on transaction rollback is now consistent, regardless of whether or not a transaction manager is configured. -See <> for more information. - -====== Shutdown Behavior - -If the container threads do not respond to a shutdown within `shutdownTimeout`, the channels are forced closed by default. -See <> for more information. - -====== After Receive Message Post Processors - -If a `MessagePostProcessor` in the `afterReceiveMessagePostProcessors` property returns `null`, the message is discarded (and acknowledged if appropriate). - -===== Connection Factory Changes - -The connection and channel listener interfaces now provide a mechanism to obtain information about exceptions. -See <> and <> for more information. - -A new `ConnectionNameStrategy` is now provided to populate the application-specific identification of the target RabbitMQ connection from the `AbstractConnectionFactory`. -See <> for more information. - -===== Retry Changes - -The `MissingMessageIdAdvice` is no longer provided. -Its functionality is now built-in. -See <> for more information. - -===== Anonymous Queue Naming - -By default, `AnonymousQueues` are now named with the default `Base64UrlNamingStrategy` instead of a simple `UUID` string. -See <> for more information. - -===== `@RabbitListener` Changes - -You can now provide simple queue declarations (bound only to the default exchange) in `@RabbitListener` annotations. -See <> for more information. - -You can now configure `@RabbitListener` annotations so that any exceptions are returned to the sender. -You can also configure a `RabbitListenerErrorHandler` to handle exceptions. -See <> for more information. - -You can now bind a queue with multiple routing keys when you use the `@QueueBinding` annotation. -Also `@QueueBinding.exchange()` now supports custom exchange types and declares durable exchanges by default. - -You can now set the `concurrency` of the listener container at the annotation level rather than having to configure a different container factory for different concurrency settings. - -You can now set the `autoStartup` property of the listener container at the annotation level, overriding the default setting in the container factory. - -You can now set after receive and before send (reply) `MessagePostProcessor` instances in the `RabbitListener` container factories. - -See <> for more information. - -Starting with version 2.0.3, one of the `@RabbitHandler` annotations on a class-level `@RabbitListener` can be designated as the default. -See <> for more information. - -===== Container Conditional Rollback - -When using an external transaction manager (such as JDBC), rule-based rollback is now supported when you provide the container with a transaction attribute. -It is also now more flexible when you use a transaction advice. -See <> for more information. - -===== Remove Jackson 1.x support - -Deprecated in previous versions, Jackson `1.x` converters and related components have now been deleted. -You can use similar components based on Jackson 2.x. -See <> for more information. - -===== JSON Message Converter - -When the `__TypeId__` is set to `Hashtable` for an inbound JSON message, the default conversion type is now `LinkedHashMap`. -Previously, it was `Hashtable`. -To revert to a `Hashtable`, you can use `setDefaultMapType` on the `DefaultClassMapper`. - -===== XML Parsers - -When parsing `Queue` and `Exchange` XML components, the parsers no longer register the `name` attribute value as a bean alias if an `id` attribute is present. -See <> for more information. - -===== Blocked Connection -You can now inject the `com.rabbitmq.client.BlockedListener` into the `org.springframework.amqp.rabbit.connection.Connection` object. -Also, the `ConnectionBlockedEvent` and `ConnectionUnblockedEvent` events are emitted by the `ConnectionFactory` when the connection is blocked or unblocked by the Broker. - -See <> for more information. - -==== Changes in 1.7 Since 1.6 - -===== AMQP Client library - -Spring AMQP now uses the new 4.0.x version of the `amqp-client` library provided by the RabbitMQ team. -This client has auto-recovery configured by default. -See <>. - -NOTE: The 4.0.x client enables automatic recovery by default. -While compatible with this feature, Spring AMQP has its own recovery mechanisms, and the client recovery feature generally is not needed. -We recommend disabling `amqp-client` automatic recovery, to avoid getting `AutoRecoverConnectionNotCurrentlyOpenException` instances when the broker is available but the connection has not yet recovered. -Starting with version 1.7.1, Spring AMQP disables it unless you explicitly create your own RabbitMQ connection factory and provide it to the `CachingConnectionFactory`. -RabbitMQ `ConnectionFactory` instances created by the `RabbitConnectionFactoryBean` also have the option disabled by default. - - -===== Log4j 2 upgrade -The minimum Log4j 2 version (for the `AmqpAppender`) is now `2.7`. -The framework is no longer compatible with previous versions. -See <> for more information. - -===== Logback Appender - -This appender no longer captures caller data (method, line number) by default. -You can re-enable it by setting the `includeCallerData` configuration option. -See <> for information about the available log appenders. - -===== Spring Retry Upgrade - -The minimum Spring Retry version is now `1.2`. -The framework is no longer compatible with previous versions. - -====== Shutdown Behavior - -You can now set `forceCloseChannel` to `true` so that, if the container threads do not respond to a shutdown within `shutdownTimeout`, the channels are forced closed, -causing any unacked messages to be re-queued. -See <> for more information. - -===== FasterXML Jackson upgrade - -The minimum Jackson version is now `2.8`. -The framework is no longer compatible with previous versions. - -===== JUnit `@Rules` - -Rules that have previously been used internally by the framework have now been made available in a separate jar called `spring-rabbit-junit`. -See <> for more information. - -===== Container Conditional Rollback - -When you use an external transaction manager (such as JDBC), rule-based rollback is now supported when you provide the container with a transaction attribute. -It is also now more flexible when you use a transaction advice. - -===== Connection Naming Strategy - -A new `ConnectionNameStrategy` is now provided to populate the application-specific identification of the target RabbitMQ connection from the `AbstractConnectionFactory`. -See <> for more information. - -===== Listener Container Changes - -====== Transaction Rollback Behavior - -You can now configure message re-queue on transaction rollback to be consistent, regardless of whether or not a transaction manager is configured. -See <> for more information. - -==== Earlier Releases - -See <> for changes in previous versions. - -==== Changes in 1.6 Since 1.5 - -===== Testing Support - -A new testing support library is now provided. -See <> for more information. - -===== Builder - -Builders that provide a fluent API for configuring `Queue` and `Exchange` objects are now available. -See <> for more information. - -===== Namespace Changes - -====== Connection Factory - -You can now add a `thread-factory` to a connection factory bean declaration -- for example, to name the threads -created by the `amqp-client` library. -See <> for more information. - -When you use `CacheMode.CONNECTION`, you can now limit the total number of connections allowed. -See <> for more information. - -====== Queue Definitions - -You can now provide a naming strategy for anonymous queues. -See <> for more information. - -===== Listener Container Changes - -====== Idle Message Listener Detection - -You can now configure listener containers to publish `ApplicationEvent` instances when idle. -See <> for more information. - -====== Mismatched Queue Detection - -By default, when a listener container starts, if queues with mismatched properties or arguments are detected, -the container logs the exception but continues to listen. -The container now has a property called `mismatchedQueuesFatal`, which prevents the container (and context) from -starting if the problem is detected during startup. -It also stops the container if the problem is detected later, such as after recovering from a connection failure. -See <> for more information. - -====== Listener Container Logging - -Now, listener container provides its `beanName` to the internal `SimpleAsyncTaskExecutor` as a `threadNamePrefix`. -It is useful for logs analysis. - -====== Default Error Handler - -The default error handler (`ConditionalRejectingErrorHandler`) now considers irrecoverable `@RabbitListener` -exceptions as fatal. -See <> for more information. - - -===== `AutoDeclare` and `RabbitAdmin` Instances - -See <> (`autoDeclare`) for some changes to the semantics of that option with respect to the use -of `RabbitAdmin` instances in the application context. - -===== `AmqpTemplate`: Receive with Timeout - -A number of new `receive()` methods with `timeout` have been introduced for the `AmqpTemplate` -and its `RabbitTemplate` implementation. -See <> for more information. - -===== Using `AsyncRabbitTemplate` - -A new `AsyncRabbitTemplate` has been introduced. -This template provides a number of send and receive methods, where the return value is a `ListenableFuture`, which can -be used later to obtain the result either synchronously or asynchronously. -See <> for more information. - -===== `RabbitTemplate` Changes - -1.4.1 introduced the ability to use https://www.rabbitmq.com/direct-reply-to.html[direct reply-to] when the broker supports it. -It is more efficient than using a temporary queue for each reply. -This version lets you override this default behavior and use a temporary queue by setting the `useTemporaryReplyQueues` property to `true`. -See <> for more information. - -The `RabbitTemplate` now supports a `user-id-expression` (`userIdExpression` when using Java configuration). -See https://www.rabbitmq.com/validated-user-id.html[Validated User-ID RabbitMQ documentation] and <> for more information. - -===== Message Properties - -====== Using `CorrelationId` - -The `correlationId` message property can now be a `String`. -See <> for more information. - -====== Long String Headers - -Previously, the `DefaultMessagePropertiesConverter` "`converted`" headers longer than the long string limit (default 1024) -to a `DataInputStream` (actually, it referenced the `LongString` instance's `DataInputStream`). -On output, this header was not converted (except to a String -- for example, `java.io.DataInputStream@1d057a39` by calling -`toString()` on the stream). - -With this release, long `LongString` instances are now left as `LongString` instances by default. -You can access the contents by using the `getBytes[]`, `toString()`, or `getStream()` methods. -A large incoming `LongString` is now correctly "`converted`" on output too. - -See <> for more information. - -====== Inbound Delivery Mode - -The `deliveryMode` property is no longer mapped to the `MessageProperties.deliveryMode`. -This change avoids unintended propagation if the the same `MessageProperties` object is used to send an outbound message. -Instead, the inbound `deliveryMode` header is mapped to `MessageProperties.receivedDeliveryMode`. - -See <> for more information. - -When using annotated endpoints, the header is provided in the header named `AmqpHeaders.RECEIVED_DELIVERY_MODE`. - -See <> for more information. - -====== Inbound User ID - -The `user_id` property is no longer mapped to the `MessageProperties.userId`. -This change avoids unintended propagation if the the same `MessageProperties` object is used to send an outbound message. -Instead, the inbound `userId` header is mapped to `MessageProperties.receivedUserId`. - -See <> for more information. - -When you use annotated endpoints, the header is provided in the header named `AmqpHeaders.RECEIVED_USER_ID`. - -See <> for more information. - -===== `RabbitAdmin` Changes - -====== Declaration Failures - -Previously, the `ignoreDeclarationFailures` flag took effect only for `IOException` on the channel (such as mis-matched -arguments). -It now takes effect for any exception (such as `TimeoutException`). -In addition, a `DeclarationExceptionEvent` is now published whenever a declaration fails. -The `RabbitAdmin` last declaration event is also available as a property `lastDeclarationExceptionEvent`. -See <> for more information. - -===== `@RabbitListener` Changes - -====== Multiple Containers for Each Bean - -When you use Java 8 or later, you can now add multiple `@RabbitListener` annotations to `@Bean` classes or -their methods. -When using Java 7 or earlier, you can use the `@RabbitListeners` container annotation to provide the same -functionality. -See <> for more information. - -====== `@SendTo` SpEL Expressions - -`@SendTo` for routing replies with no `replyTo` property can now be SpEL expressions evaluated against the -request/reply. -See <> for more information. - -====== `@QueueBinding` Improvements - -You can now specify arguments for queues, exchanges, and bindings in `@QueueBinding` annotations. -Header exchanges are now supported by `@QueueBinding`. -See <> for more information. - -===== Delayed Message Exchange - -Spring AMQP now has first class support for the RabbitMQ Delayed Message Exchange plugin. -See <> for more information. - -===== Exchange Internal Flag - -Any `Exchange` definitions can now be marked as `internal`, and `RabbitAdmin` passes the value to the broker when -declaring the exchange. -See <> for more information. - -===== `CachingConnectionFactory` Changes - -====== `CachingConnectionFactory` Cache Statistics - -The `CachingConnectionFactory` now provides cache properties at runtime and over JMX. -See <> for more information. - -====== Accessing the Underlying RabbitMQ Connection Factory - -A new getter has been added to provide access to the underlying factory. -You can use this getter, for example, to add custom connection properties. -See <> for more information. - -====== Channel Cache - -The default channel cache size has been increased from 1 to 25. -See <> for more information. - -In addition, the `SimpleMessageListenerContainer` no longer adjusts the cache size to be at least as large as the number -of `concurrentConsumers` -- this was superfluous, since the container consumer channels are never cached. - -===== Using `RabbitConnectionFactoryBean` - -The factory bean now exposes a property to add client connection properties to connections made by the resulting -factory. - -===== Java Deserialization - -You can now configure a "`allowed list`" of allowable classes when you use Java deserialization. -You should consider creating an allowed list if you accept messages with serialized java objects from -untrusted sources. -See <> for more information. - -===== JSON `MessageConverter` - -Improvements to the JSON message converter now allow the consumption of messages that do not have type information -in message headers. -See <> and <> for more information. - -===== Logging Appenders - -====== Log4j 2 - -A log4j 2 appender has been added, and the appenders can now be configured with an `addresses` property to connect -to a broker cluster. - -====== Client Connection Properties - -You can now add custom client connection properties to RabbitMQ connections. - -See <> for more information. - -==== Changes in 1.5 Since 1.4 - -===== `spring-erlang` Is No Longer Supported - -The `spring-erlang` jar is no longer included in the distribution. -Use <> instead. - -===== `CachingConnectionFactory` Changes - -====== Empty Addresses Property in `CachingConnectionFactory` - -Previously, if the connection factory was configured with a host and port but an empty String was also supplied for -`addresses`, the host and port were ignored. -Now, an empty `addresses` String is treated the same as a `null`, and the host and port are used. - -====== URI Constructor - -The `CachingConnectionFactory` has an additional constructor, with a `URI` parameter, to configure the broker connection. - -====== Connection Reset - -A new method called `resetConnection()` has been added to let users reset the connection (or connections). -You might use this, for example, to reconnect to the primary broker after failing over to the secondary broker. -This *does* impact in-process operations. -The existing `destroy()` method does exactly the same, but the new method has a less daunting name. - -===== Properties to Control Container Queue Declaration Behavior - -When the listener container consumers start, they attempt to passively declare the queues to ensure they are available -on the broker. -Previously, if these declarations failed (for example, because the queues didn't exist) or when an HA queue was being -moved, the retry logic was fixed at three retry attempts at five-second intervals. -If the queues still do not exist, the behavior is controlled by the `missingQueuesFatal` property (default: `true`). -Also, for containers configured to listen from multiple queues, if only a subset of queues are available, the consumer -retried the missing queues on a fixed interval of 60 seconds. - -The `declarationRetries`, `failedDeclarationRetryInterval`, and `retryDeclarationInterval` properties are now configurable. -See <> for more information. - -===== Class Package Change - -The `RabbitGatewaySupport` class has been moved from `o.s.amqp.rabbit.core.support` to `o.s.amqp.rabbit.core`. - -===== `DefaultMessagePropertiesConverter` Changes - -You can now configure the `DefaultMessagePropertiesConverter` to -determine the maximum length of a `LongString` that is converted -to a `String` rather than to a `DataInputStream`. -The converter has an alternative constructor that takes the value as a limit. -Previously, this limit was hard-coded at `1024` bytes. -(Also available in 1.4.4). - -===== `@RabbitListener` Improvements - -====== `@QueueBinding` for `@RabbitListener` - -The `bindings` attribute has been added to the `@RabbitListener` annotation as mutually exclusive with the `queues` -attribute to allow the specification of the `queue`, its `exchange`, and `binding` for declaration by a `RabbitAdmin` on -the Broker. - -====== SpEL in `@SendTo` - -The default reply address (`@SendTo`) for a `@RabbitListener` can now be a SpEL expression. - -====== Multiple Queue Names through Properties - -You can now use a combination of SpEL and property placeholders to specify multiple queues for a listener. - -See <> for more information. - -===== Automatic Exchange, Queue, and Binding Declaration - -You can now declare beans that define a collection of these entities, and the `RabbitAdmin` adds the -contents to the list of entities that it declares when a connection is established. -See <> for more information. - -===== `RabbitTemplate` Changes - -====== `reply-address` Added - -The `reply-address` attribute has been added to the `` component as an alternative `reply-queue`. -See <> for more information. -(Also available in 1.4.4 as a setter on the `RabbitTemplate`). - -====== Blocking `receive` Methods - -The `RabbitTemplate` now supports blocking in `receive` and `convertAndReceive` methods. -See <> for more information. - -====== Mandatory with `sendAndReceive` Methods - -When the `mandatory` flag is set when using the `sendAndReceive` and `convertSendAndReceive` methods, the calling thread -throws an `AmqpMessageReturnedException` if the request message cannot be delivered. -See <> for more information. - -====== Improper Reply Listener Configuration - -The framework tries to verify proper configuration of a reply listener container when using a named reply queue. - -See <> for more information. - -===== `RabbitManagementTemplate` Added - -The `RabbitManagementTemplate` has been introduced to monitor and configure the RabbitMQ Broker by using the REST API provided by its https://www.rabbitmq.com/management.html[management plugin]. -See <> for more information. - -===== Listener Container Bean Names (XML) - -[IMPORTANT] -==== -The `id` attribute on the `` element has been removed. -Starting with this release, the `id` on the `` child element is used alone to name the listener container bean created for each listener element. - -Normal Spring bean name overrides are applied. -If a later `` is parsed with the same `id` as an existing bean, the new definition overrides the existing one. -Previously, bean names were composed from the `id` attributes of the `` and `` elements. - -When migrating to this release, if you have `id` attributes on your `` elements, remove them and set the `id` on the child `` element instead. -==== - -However, to support starting and stopping containers as a group, a new `group` attribute has been added. -When this attribute is defined, the containers created by this element are added to a bean with this name, of type `Collection`. -You can iterate over this group to start and stop containers. - -===== Class-Level `@RabbitListener` - -The `@RabbitListener` annotation can now be applied at the class level. -Together with the new `@RabbitHandler` method annotation, this lets you select the handler method based on payload type. -See <> for more information. - -===== `SimpleMessageListenerContainer`: BackOff Support - -The `SimpleMessageListenerContainer` can now be supplied with a `BackOff` instance for `consumer` startup recovery. -See <> for more information. - -===== Channel Close Logging - -A mechanism to control the log levels of channel closure has been introduced. -See <>. - -===== Application Events - -The `SimpleMessageListenerContainer` now emits application events when consumers fail. -See <> for more information. - -===== Consumer Tag Configuration - -Previously, the consumer tags for asynchronous consumers were generated by the broker. -With this release, it is now possible to supply a naming strategy to the listener container. -See <>. - -===== Using `MessageListenerAdapter` - -The `MessageListenerAdapter` now supports a map of queue names (or consumer tags) to method names, to determine -which delegate method to call based on the queue from which the message was received. - -===== `LocalizedQueueConnectionFactory` Added - -`LocalizedQueueConnectionFactory` is a new connection factory that connects to the node in a cluster where a mirrored queue actually resides. - -See <>. - -===== Anonymous Queue Naming - -Starting with version 1.5.3, you can now control how `AnonymousQueue` names are generated. -See <> for more information. - - -==== Changes in 1.4 Since 1.3 - -===== `@RabbitListener` Annotation - -POJO listeners can be annotated with `@RabbitListener`, enabled by `@EnableRabbit` or ``. -Spring Framework 4.1 is required for this feature. -See <> for more information. - -===== `RabbitMessagingTemplate` Added - -A new `RabbitMessagingTemplate` lets you interact with RabbitMQ by using `spring-messaging` `Message` instances. -Internally, it uses the `RabbitTemplate`, which you can configure as normal. -Spring Framework 4.1 is required for this feature. -See <> for more information. - -===== Listener Container `missingQueuesFatal` Attribute - -1.3.5 introduced the `missingQueuesFatal` property on the `SimpleMessageListenerContainer`. -This is now available on the listener container namespace element. -See <>. - -===== RabbitTemplate `ConfirmCallback` Interface - -The `confirm` method on this interface has an additional parameter called `cause`. -When available, this parameter contains the reason for a negative acknowledgement (nack). -See <>. - -===== `RabbitConnectionFactoryBean` Added - -`RabbitConnectionFactoryBean` creates the underlying RabbitMQ `ConnectionFactory` used by the `CachingConnectionFactory`. -This enables configuration of SSL options using Spring's dependency injection. -See <>. - -===== Using `CachingConnectionFactory` - -The `CachingConnectionFactory` now lets the `connectionTimeout` be set as a property or as an attribute in the namespace. -It sets the property on the underlying RabbitMQ `ConnectionFactory`. -See <>. - -===== Log Appender - -The Logback `org.springframework.amqp.rabbit.logback.AmqpAppender` has been introduced. -It provides options similar to `org.springframework.amqp.rabbit.log4j.AmqpAppender`. -For more information, see the JavaDoc of these classes. - -The Log4j `AmqpAppender` now supports the `deliveryMode` property (`PERSISTENT` or `NON_PERSISTENT`, default: `PERSISTENT`). -Previously, all log4j messages were `PERSISTENT`. - -The appender also supports modification of the `Message` before sending -- allowing, for example, the addition of custom headers. -Subclasses should override the `postProcessMessageBeforeSend()`. - -===== Listener Queues - -The listener container now, by default, redeclares any missing queues during startup. -A new `auto-declare` attribute has been added to the `` to prevent these re-declarations. -See <>. - -===== `RabbitTemplate`: `mandatory` and `connectionFactorySelector` Expressions - -The `mandatoryExpression`, `sendConnectionFactorySelectorExpression`, and `receiveConnectionFactorySelectorExpression` SpEL Expression`s properties have been added to `RabbitTemplate`. -The `mandatoryExpression` is used to evaluate a `mandatory` boolean value against each request message when a `ReturnCallback` is in use. -See <>. -The `sendConnectionFactorySelectorExpression` and `receiveConnectionFactorySelectorExpression` are used when an `AbstractRoutingConnectionFactory` is provided, to determine the `lookupKey` for the target `ConnectionFactory` at runtime on each AMQP protocol interaction operation. -See <>. - -===== Listeners and the Routing Connection Factory - -You can configure a `SimpleMessageListenerContainer` with a routing connection factory to enable connection selection based on the queue names. -See <>. - -===== `RabbitTemplate`: `RecoveryCallback` Option - -The `recoveryCallback` property has been added for use in the `retryTemplate.execute()`. -See <>. - -===== `MessageConversionException` Change - -This exception is now a subclass of `AmqpException`. -Consider the following code: - -==== -[source,java] ----- -try { - template.convertAndSend("thing1", "thing2", "cat"); -} -catch (AmqpException e) { - ... -} -catch (MessageConversionException e) { - ... -} ----- -==== - -The second catch block is no longer reachable and needs to be moved above the catch-all `AmqpException` catch block. - -===== RabbitMQ 3.4 Compatibility - -Spring AMQP is now compatible with the RabbitMQ 3.4, including direct reply-to. -See <> and <> for more information. - -===== `ContentTypeDelegatingMessageConverter` Added - -The `ContentTypeDelegatingMessageConverter` has been introduced to select the `MessageConverter` to use, based on the `contentType` property in the `MessageProperties`. -See <> for more information. - -==== Changes in 1.3 Since 1.2 - -===== Listener Concurrency - -The listener container now supports dynamic scaling of the number of consumers based on workload, or you can programmatically change the concurrency without stopping the container. -See <>. - -===== Listener Queues - -The listener container now permits the queues on which it listens to be modified at runtime. -Also, the container now starts if at least one of its configured queues is available for use. -See <> - -This listener container now redeclares any auto-delete queues during startup. -See <>. - -===== Consumer Priority - -The listener container now supports consumer arguments, letting the `x-priority` argument be set. -See <>. - -===== Exclusive Consumer - -You can now configure `SimpleMessageListenerContainer` with a single `exclusive` consumer, preventing other consumers from listening to the queue. -See <>. - -===== Rabbit Admin - -You can now have the broker generate the queue name, regardless of `durable`, `autoDelete`, and `exclusive` settings. -See <>. - -===== Direct Exchange Binding - -Previously, omitting the `key` attribute from a `binding` element of a `direct-exchange` configuration caused the queue or exchange to be bound with an empty string as the routing key. -Now it is bound with the the name of the provided `Queue` or `Exchange`. -If you wish to bind with an empty string routing key, you need to specify `key=""`. - -===== `AmqpTemplate` Changes - -The `AmqpTemplate` now provides several synchronous `receiveAndReply` methods. -These are implemented by the `RabbitTemplate`. -For more information see <>. - -The `RabbitTemplate` now supports configuring a `RetryTemplate` to attempt retries (with optional back-off policy) for when the broker is not available. -For more information see <>. - -===== Caching Connection Factory - -You can now configure the caching connection factory to cache `Connection` instances and their `Channel` instances instead of using a single connection and caching only `Channel` instances. -See <>. - -===== Binding Arguments - -The `` of the `` now supports parsing of the `` sub-element. -You can now configure the `` of the `` with a `key/value` attribute pair (to match on a single header) or with a `` sub-element (allowing matching on multiple headers). -These options are mutually exclusive. -See <>. - -===== Routing Connection Factory - -A new `SimpleRoutingConnectionFactory` has been introduced. -It allows configuration of `ConnectionFactories` mapping, to determine the target `ConnectionFactory` to use at runtime. -See <>. - -===== `MessageBuilder` and `MessagePropertiesBuilder` - -"`Fluent APIs`" for building messages or message properties are now provided. -See <>. - -===== `RetryInterceptorBuilder` Change - -A "`Fluent API`" for building listener container retry interceptors is now provided. -See <>. - -===== `RepublishMessageRecoverer` Added - -This new `MessageRecoverer` is provided to allow publishing a failed message to another queue (including stack trace information in the header) when retries are exhausted. -See <>. - -===== Default Error Handler (Since 1.3.2) - -A default `ConditionalRejectingErrorHandler` has been added to the listener container. -This error handler detects fatal message conversion problems and instructs the container to reject the message to prevent the broker from continually redelivering the unconvertible message. -See <>. - -===== Listener Container 'missingQueuesFatal` Property (Since 1.3.5) - -The `SimpleMessageListenerContainer` now has a property called `missingQueuesFatal` (default: `true`). -Previously, missing queues were always fatal. -See <>. - -==== Changes to 1.2 Since 1.1 - -===== RabbitMQ Version - -Spring AMQP now uses RabbitMQ 3.1.x by default (but retains compatibility with earlier versions). -Certain deprecations have been added for features no longer supported by RabbitMQ 3.1.x -- federated exchanges and the `immediate` property on the `RabbitTemplate`. - -===== Rabbit Admin - -`RabbitAdmin` now provides an option to let exchange, queue, and binding declarations continue when a declaration fails. -Previously, all declarations stopped on a failure. -By setting `ignore-declaration-exceptions`, such exceptions are logged (at the `WARN` level), but further declarations continue. -An example where this might be useful is when a queue declaration fails because of a slightly different `ttl` setting that would normally stop other declarations from proceeding. - -`RabbitAdmin` now provides an additional method called `getQueueProperties()`. -You can use this determine if a queue exists on the broker (returns `null` for a non-existent queue). -In addition, it returns the current number of messages in the queue as well as the current number of consumers. - -===== Rabbit Template - -Previously, when the `...sendAndReceive()` methods were used with a fixed reply queue, two custom headers were used for correlation data and to retain and restore reply queue information. -With this release, the standard message property (`correlationId`) is used by default, although you can specify a custom property to use instead. -In addition, nested `replyTo` information is now retained internally in the template, instead of using a custom header. - -The `immediate` property is deprecated. -You must not set this property when using RabbitMQ 3.0.x or greater. - -===== JSON Message Converters - -A Jackson 2.x `MessageConverter` is now provided, along with the existing converter that uses Jackson 1.x. - -===== Automatic Declaration of Queues and Other Items - -Previously, when declaring queues, exchanges and bindings, you could not define which connection factory was used for the declarations. -Each `RabbitAdmin` declared all components by using its connection. - -Starting with this release, you can now limit declarations to specific `RabbitAdmin` instances. -See <>. - -===== AMQP Remoting - -Facilities are now provided for using Spring remoting techniques, using AMQP as the transport for the RPC calls. -For more information see <> - -===== Requested Heart Beats - -Several users have asked for the underlying client connection factory's `requestedHeartBeats` property to be exposed on the Spring AMQP `CachingConnectionFactory`. -This is now available. -Previously, it was necessary to configure the AMQP client factory as a separate bean and provide a reference to it in the `CachingConnectionFactory`. - -==== Changes to 1.1 Since 1.0 - -===== General - -Spring-AMQP is now built with Gradle. - -Adds support for publisher confirms and returns. - -Adds support for HA queues and broker failover. - -Adds support for dead letter exchanges and dead letter queues. - -===== AMQP Log4j Appender - -Adds an option to support adding a message ID to logged messages. - -Adds an option to allow the specification of a `Charset` name to be used when converting `String` to `byte[]`. diff --git a/src/reference/asciidoc/docinfo.html b/src/reference/asciidoc/docinfo.html deleted file mode 100644 index 19e2462b..00000000 --- a/src/reference/asciidoc/docinfo.html +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/src/reference/asciidoc/index.adoc b/src/reference/asciidoc/index.adoc deleted file mode 100644 index 71ff75d9..00000000 --- a/src/reference/asciidoc/index.adoc +++ /dev/null @@ -1,70 +0,0 @@ -[[spring-amqp-reference]] -= Spring AMQP -ifdef::backend-html5[] -:revnumber: '' -endif::[] -:toc: left -:toclevels: 4 -:numbered: -:icons: font -:hide-uri-scheme: -Mark Pollack; Mark Fisher; Oleg Zhurakousky; Dave Syer; Gary Russell; Gunnar Hillert; Artem Bilan; Stéphane Nicoll; Arnaud Cogoluègnes; Jay Bryant - -ifdef::backend-html5[] -*{project-version}* - -NOTE: This documentation is also available as https://docs.spring.io/spring-amqp/docs/current/reference/pdf/spring-amqp-reference.pdf[PDF]. -endif::[] - -ifdef::backend-pdf[] -NOTE: This documentation is also available as https://docs.spring.io/spring-amqp/docs/current/reference/html/index.html[HTML]. -endif::[] - -(C) 2010 - 2021 by VMware, Inc. - -Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. - - -== Preface - -include::preface.adoc[] - -include::whats-new.adoc[] - -== Introduction - -This first part of the reference documentation is a high-level overview of Spring AMQP and the underlying concepts. -It includes some code snippets to get you up and running as quickly as possible. - -include::quick-tour.adoc[] - -== Reference - -This part of the reference documentation details the various components that comprise Spring AMQP. -The <> covers the core classes to develop an AMQP application. -This part also includes a chapter about the <>. - -include::amqp.adoc[] - -include::stream.adoc[] - -include::logging.adoc[] - -include::sample-apps.adoc[] - -include::testing.adoc[] - -== Spring Integration - Reference - -This part of the reference documentation provides a quick introduction to the AMQP support within the Spring Integration project. - -include::si-amqp.adoc[] - -[[resources]] -== Other Resources - -In addition to this reference documentation, there exist a number of other resources that may help you learn about AMQP. - -include::further-reading.adoc[] - -include::appendix.adoc[]