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
This commit is contained in:
Stéphane Nicoll
2024-03-19 14:17:11 -05:00
parent 1f7e7738e8
commit 3651ff87cd
12 changed files with 249 additions and 7 deletions

View File

@@ -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"

View File

@@ -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"

View File

@@ -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 {
/**

View File

@@ -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");
});
}

View File

@@ -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();

View File

@@ -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]

View File

@@ -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"))
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}