From 3651ff87cd665ff61326d1a36ef1ec401732a5c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 19 Mar 2024 14:17:11 -0500 Subject: [PATCH] Reinstate auto-configuration support for embedded ActiveMQ This commit restores auto-configuration for using an Embedded broker with ActiveMQ classic. Contrary to its 2.7.x version, "spring-boot-starter-activemq" no longer adds the broker for consistency with Artemis, and to keep the existing 3.x behavior. Rather than "inMemory", a "s.a.embedded.enabled" property has been reintroduced that matches the name used by Artemis. The documentation has been updated to mention that the broker dependency must be added to use it. Closes gh-38404 --- .../build.gradle | 1 + .../spring-boot-autoconfigure/build.gradle | 1 + .../jms/activemq/ActiveMQProperties.java | 33 ++++++++++++- .../ActiveMQAutoConfigurationTests.java | 6 +-- .../jms/activemq/ActiveMQPropertiesTests.java | 19 +++++++- .../reference/pages/messaging/jms.adoc | 19 +++++++- .../build.gradle | 14 ++++++ .../smoketest/activemq/embedded/Consumer.java | 30 ++++++++++++ .../smoketest/activemq/embedded/Producer.java | 45 +++++++++++++++++ .../embedded/SampleActiveMQApplication.java | 40 ++++++++++++++++ .../src/main/resources/application.properties | 0 .../SampleActiveMQApplicationTests.java | 48 +++++++++++++++++++ 12 files changed, 249 insertions(+), 7 deletions(-) create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/build.gradle create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/java/smoketest/activemq/embedded/Consumer.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/java/smoketest/activemq/embedded/Producer.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/java/smoketest/activemq/embedded/SampleActiveMQApplication.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/resources/application.properties create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/test/java/smoketest/activemq/embedded/SampleActiveMQApplicationTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle index b7a6e154bf..6ae4cf4528 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle @@ -77,6 +77,7 @@ dependencies { optional("jakarta.persistence:jakarta.persistence-api") optional("jakarta.servlet:jakarta.servlet-api") optional("javax.cache:cache-api") + optional("org.apache.activemq:activemq-broker") optional("org.apache.activemq:activemq-client") optional("org.apache.commons:commons-dbcp2") { exclude group: "commons-logging", module: "commons-logging" diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index 509d39ee01..094b81160d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -77,6 +77,7 @@ dependencies { optional("jakarta.ws.rs:jakarta.ws.rs-api") optional("javax.cache:cache-api") optional("javax.money:money-api") + optional("org.apache.activemq:activemq-broker") optional("org.apache.activemq:activemq-client") optional("org.apache.activemq:artemis-jakarta-client") { exclude group: "commons-logging", module: "commons-logging" diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java index 2877479a08..9c43a6b299 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,8 @@ import org.springframework.boot.context.properties.NestedConfigurationProperty; @ConfigurationProperties(prefix = "spring.activemq") public class ActiveMQProperties { + private static final String DEFAULT_EMBEDDED_BROKER_URL = "vm://localhost?broker.persistent=false"; + private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616"; /** @@ -54,6 +56,8 @@ public class ActiveMQProperties { */ private String password; + private final Embedded embedded = new Embedded(); + /** * Time to wait before considering a close complete. */ @@ -99,6 +103,10 @@ public class ActiveMQProperties { this.password = password; } + public Embedded getEmbedded() { + return this.embedded; + } + public Duration getCloseTimeout() { return this.closeTimeout; } @@ -135,9 +143,32 @@ public class ActiveMQProperties { if (this.brokerUrl != null) { return this.brokerUrl; } + if (this.embedded.isEnabled()) { + return DEFAULT_EMBEDDED_BROKER_URL; + } return DEFAULT_NETWORK_BROKER_URL; } + /** + * Configuration for an embedded ActiveMQ broker. + */ + public static class Embedded { + + /** + * Whether to enable embedded mode if the ActiveMQ Broker is available. + */ + private boolean enabled = true; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + } + public static class Packages { /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java index 2815e3e2ff..b6f31fbf77 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,14 +48,14 @@ class ActiveMQAutoConfigurationTests { .withConfiguration(AutoConfigurations.of(ActiveMQAutoConfiguration.class, JmsAutoConfiguration.class)); @Test - void brokerIsLocalhostByDefault() { + void brokerIsEmbeddedByDefault() { this.contextRunner.withUserConfiguration(EmptyConfiguration.class).run((context) -> { assertThat(context).hasSingleBean(CachingConnectionFactory.class).hasBean("jmsConnectionFactory"); CachingConnectionFactory connectionFactory = context.getBean(CachingConnectionFactory.class); assertThat(context.getBean("jmsConnectionFactory")).isSameAs(connectionFactory); assertThat(connectionFactory.getTargetConnectionFactory()).isInstanceOf(ActiveMQConnectionFactory.class); assertThat(((ActiveMQConnectionFactory) connectionFactory.getTargetConnectionFactory()).getBrokerURL()) - .isEqualTo("tcp://localhost:61616"); + .isEqualTo("vm://localhost?broker.persistent=false"); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java index 3c4ee27987..78c45042ef 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java @@ -31,13 +31,15 @@ import static org.assertj.core.api.Assertions.assertThat; */ class ActiveMQPropertiesTests { + private static final String DEFAULT_EMBEDDED_BROKER_URL = "vm://localhost?broker.persistent=false"; + private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616"; private final ActiveMQProperties properties = new ActiveMQProperties(); @Test - void getBrokerUrlIsLocalhostByDefault() { - assertThat(this.properties.determineBrokerUrl()).isEqualTo(DEFAULT_NETWORK_BROKER_URL); + void getBrokerUrlIsEmbeddedByDefault() { + assertThat(this.properties.determineBrokerUrl()).isEqualTo(DEFAULT_EMBEDDED_BROKER_URL); } @Test @@ -46,6 +48,19 @@ class ActiveMQPropertiesTests { assertThat(this.properties.determineBrokerUrl()).isEqualTo("tcp://activemq.example.com:71717"); } + @Test + void getBrokerUrlWithEmbeddedSetToFalse() { + this.properties.getEmbedded().setEnabled(false); + assertThat(this.properties.determineBrokerUrl()).isEqualTo(DEFAULT_NETWORK_BROKER_URL); + } + + @Test + void getExplicitBrokerUrlAlwaysWins() { + this.properties.setBrokerUrl("tcp://activemq.example.com:71717"); + this.properties.getEmbedded().setEnabled(false); + assertThat(this.properties.determineBrokerUrl()).isEqualTo("tcp://activemq.example.com:71717"); + } + @Test void setTrustAllPackages() { ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(); diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/jms.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/jms.adoc index f66a18d012..0b7910318b 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/jms.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/jms.adoc @@ -12,11 +12,26 @@ Spring Boot also auto-configures the necessary infrastructure to send and receiv == ActiveMQ "Classic" Support When https://activemq.apache.org/components/classic[ActiveMQ "Classic"] is available on the classpath, Spring Boot can configure a `ConnectionFactory`. +If the broker is present, an embedded broker is automatically started and configured (provided no broker URL is specified through configuration and the embedded broker is not disabled in the configuration). NOTE: If you use `spring-boot-starter-activemq`, the necessary dependencies to connect to an ActiveMQ "Classic" instance are provided, as is the Spring infrastructure to integrate with JMS. +Adding `org.apache.activemq:activemq-broker` to your application lets you use the embedded broker. ActiveMQ "Classic" configuration is controlled by external configuration properties in `+spring.activemq.*+`. -By default, ActiveMQ "Classic" is auto-configured to use the https://activemq.apache.org/tcp-transport-reference[TCP transport], connecting by default to `tcp://localhost:61616`. The following example shows how to change the default broker URL: + +If `activemq-broker` is on the classpath, ActiveMQ "Classic" is auto-configured to use the https://activemq.apache.org/vm-transport-reference.html[VM transport], which starts a broker embedded in the same JVM instance. + +You can disable the embedded broker by configuring the configprop:spring.activemq.embedded.enabled[] property, as shown in the following example: + +[configprops,yaml] +---- +spring: + activemq: + embedded: + enabled: false +---- + +The embedded broker will also be disabled if you configure the broker URL, as shown in the following example: [configprops,yaml] ---- @@ -27,6 +42,8 @@ spring: password: "secret" ---- +If you want to take full control over the embedded broker, see https://activemq.apache.org/how-do-i-embed-a-broker-inside-a-connection.html[the ActiveMQ "Classic" documentation] for further information. + By default, a `CachingConnectionFactory` wraps the native `ConnectionFactory` with sensible settings that you can control by external configuration properties in `+spring.jms.*+`: [configprops,yaml] diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/build.gradle new file mode 100644 index 0000000000..c6dd37d2d5 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/build.gradle @@ -0,0 +1,14 @@ +plugins { + id "java" + id "org.springframework.boot.conventions" +} + +description = "Spring Boot Actuator ActiveMQ Embedded smoke test" + +dependencies { + implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-activemq")) + + runtimeOnly("org.apache.activemq:activemq-broker") + + testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test")) +} \ No newline at end of file diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/java/smoketest/activemq/embedded/Consumer.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/java/smoketest/activemq/embedded/Consumer.java new file mode 100644 index 0000000000..66cc31e048 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/java/smoketest/activemq/embedded/Consumer.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.activemq.embedded; + +import org.springframework.jms.annotation.JmsListener; +import org.springframework.stereotype.Component; + +@Component +public class Consumer { + + @JmsListener(destination = "sample.queue") + public void receiveQueue(String text) { + System.out.println(text); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/java/smoketest/activemq/embedded/Producer.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/java/smoketest/activemq/embedded/Producer.java new file mode 100644 index 0000000000..ddeacda378 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/java/smoketest/activemq/embedded/Producer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.activemq.embedded; + +import jakarta.jms.Queue; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.jms.core.JmsMessagingTemplate; +import org.springframework.stereotype.Component; + +@Component +public class Producer implements CommandLineRunner { + + @Autowired + private JmsMessagingTemplate jmsMessagingTemplate; + + @Autowired + private Queue queue; + + @Override + public void run(String... args) throws Exception { + send("Sample message"); + System.out.println("Message was sent to the Queue"); + } + + public void send(String msg) { + this.jmsMessagingTemplate.convertAndSend(this.queue, msg); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/java/smoketest/activemq/embedded/SampleActiveMQApplication.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/java/smoketest/activemq/embedded/SampleActiveMQApplication.java new file mode 100644 index 0000000000..6890cce28f --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/java/smoketest/activemq/embedded/SampleActiveMQApplication.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.activemq.embedded; + +import jakarta.jms.Queue; +import org.apache.activemq.command.ActiveMQQueue; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.jms.annotation.EnableJms; + +@SpringBootApplication +@EnableJms +public class SampleActiveMQApplication { + + @Bean + public Queue queue() { + return new ActiveMQQueue("sample.queue"); + } + + public static void main(String[] args) { + SpringApplication.run(SampleActiveMQApplication.class, args); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/resources/application.properties b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/main/resources/application.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/test/java/smoketest/activemq/embedded/SampleActiveMQApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/test/java/smoketest/activemq/embedded/SampleActiveMQApplicationTests.java new file mode 100644 index 0000000000..76f0e6f561 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq-embedded/src/test/java/smoketest/activemq/embedded/SampleActiveMQApplicationTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.activemq.embedded; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for ActiveMQ smoke test with embedded broker. + * + * @author Stephane Nicoll + */ +@SpringBootTest +@ExtendWith(OutputCaptureExtension.class) +class SampleActiveMQApplicationTests { + + @Autowired + private Producer producer; + + @Test + void sendSimpleMessage(CapturedOutput output) throws InterruptedException { + this.producer.send("Test message"); + Thread.sleep(1000L); + assertThat(output).contains("Test message"); + } + +}