Search code examples
javaspring-bootactivemq-artemis

Spring Boot Embedded ActiveMQ Artemis broker connection


I have a very simple Spring Boot 2.7.6 ActiveMQ Artemis app which listens for messages.

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
import javax.jms.ConnectionFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;

@SpringBootApplication
@EnableJms
public class Application {

    @Bean
    public JmsListenerContainerFactory<?> myFactory(ConnectionFactory connectionFactory,
                            DefaultJmsListenerContainerFactoryConfigurer configurer) {
      DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
      // This provides all auto-configured defaults to this factory, including the message converter
      configurer.configure(factory, connectionFactory);
      // You could still override some settings if necessary.
      return factory;
    }

    public static void main(String[] args) {
       SpringApplication.run(Application.class, args);
    }

    @JmsListener(destination = "my-queue-1")
    public void listen(String in) {
       System.out.println(in);
    }
}

Here's the code that configures the embedded broker. I was just guessing by adding multiple acceptors. Different posts refer to addConnectorConfiguration, but none of them seem to work so far.

package hello;

import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory;
import org.springframework.boot.autoconfigure.jms.artemis.ArtemisConfigurationCustomizer;
import org.springframework.context.annotation.Configuration;


@Configuration
public class ArtemisConfig implements ArtemisConfigurationCustomizer {
    @Override
    public void customize(org.apache.activemq.artemis.core.config.Configuration configuration) {
      configuration.addAcceptorConfiguration("remote", "tcp://0.0.0.0:61616");
    }
}

With this simple application.properties:

spring.artemis.mode=embedded
spring.artemis.embedded.server-id=54321
spring.artemis.embedded.queues=my-queue-1
spring.artemis.embedded.enabled=true

Then I have another Spring Boot app which produces messages and sends them to the broker address.

package broker.producer;

import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.destination.JndiDestinationResolver;
import org.springframework.stereotype.Service;

@Service
public class JmsProducer {

  @Value("${spring.jms.template.default-destination}")
  private String defaultDestination;

  Logger log = LoggerFactory.getLogger(JmsProducer.class);

  @Bean
  public ActiveMQConnectionFactory activeMQConnectionFactory() {
      ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");

      return activeMQConnectionFactory;
  }

  @Bean
  public JndiDestinationResolver jndiDestinationResolver() {
    return new JndiDestinationResolver();
  }

  @Bean
  public JmsTemplate jmsTemplate() {
    JmsTemplate template = new JmsTemplate();
    template.setConnectionFactory(activeMQConnectionFactory());
    template.setPubSubDomain(false); // false for a Queue, true for a Topic
    template.setDefaultDestinationName(defaultDestination);

    return template;
  }


  public void send(String message) {
    JmsTemplate jmsTemplate = jmsTemplate();

    log.info("Sending message='{}'", message);
    jmsTemplate.convertAndSend(message);
    log.info("Sent message='{}'", message);
  }
}

Then I start each of the apps and try calling send method and I am unable to connect to the broker from the producer app due to this error:

2024-01-16 10:25:00.596 ERROR 30486 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.jms.UncategorizedJmsException: Uncategorized exception occurred during JMS processing; nested exception is javax.jms.JMSException: Failed to create session factory; nested exception is ActiveMQNotConnectedException[errorType=NOT_CONNECTED message=AMQ219007: Cannot connect to server(s). Tried with all available servers.]] with root cause

The producer app is able to connect to a Docker instance of ActiveMQ Artemis just fine.

For now both apps are running on the same machine, but in prod I'm hoping to have each app running in a separate pod.


Solution

  • I put together a very simple project as a proof-of-concept to ensure what you were doing was possible and everything worked fine for me. The embedded broker was started and accepted connections from remote clients on port 61616.

    Here's Application.java:

    package hello;
    
    import javax.jms.ConnectionFactory;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
    import org.springframework.context.annotation.Bean;
    import org.springframework.jms.annotation.EnableJms;
    import org.springframework.jms.annotation.JmsListener;
    import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
    import org.springframework.jms.config.JmsListenerContainerFactory;
    
    @SpringBootApplication
    @EnableJms
    public class Application {
    
        @Bean
        public JmsListenerContainerFactory<?> myFactory(ConnectionFactory connectionFactory,
                                                                        DefaultJmsListenerContainerFactoryConfigurer configurer) {
            DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
            // This provides all auto-configured defaults to this factory, including the message converter
            configurer.configure(factory, connectionFactory);
            // You could still override some settings if necessary.
            return factory;
        }
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    
        @JmsListener(destination = "my-queue-1")
        public void listen(String in) {
            System.out.println(in);
        }
    }
    

    Here's ArtemisConfig.java:

    package hello;
    
    import org.springframework.boot.autoconfigure.jms.artemis.ArtemisConfigurationCustomizer;
    import org.springframework.context.annotation.Configuration;
    
    
    @Configuration
    public class ArtemisConfig implements ArtemisConfigurationCustomizer {
        @Override
        public void customize(org.apache.activemq.artemis.core.config.Configuration configuration) {
           try {
              configuration.addAcceptorConfiguration("remote", "tcp://0.0.0.0:61616");
           } catch (Exception e) {
              e.printStackTrace();
           }
        }
    }
    

    Here's my application.properties:

    spring.artemis.mode=embedded
    spring.artemis.embedded.server-id=54321
    spring.artemis.embedded.queues=my-queue-1
    spring.artemis.embedded.enabled=true
    

    Finally, here's my pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
       <modelVersion>4.0.0</modelVersion>
       <parent>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-parent</artifactId>
          <version>2.7.6</version>
          <relativePath/>
       </parent>
       <groupId>com.example</groupId>
       <artifactId>spring-boot-customized-activemq-artemis</artifactId>
       <version>0.0.1-SNAPSHOT</version>
       <properties>
          <java.version>17</java.version>
          <spring.version>2.7.6</spring.version>
          <activemq.artemis.version>2.19.1</activemq.artemis.version>
       </properties>
    
       <dependencies>
          <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-artemis</artifactId>
          </dependency>
          <dependency>
             <groupId>org.apache.activemq</groupId>
             <artifactId>artemis-jms-server</artifactId>
         <version>${activemq.artemis.version}</version>
          </dependency>
       </dependencies>
    
       <build>
          <plugins>
             <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            <version>${spring.version}</version>
             </plugin>
          </plugins>
       </build>
    </project>
    

    I start the application like so:

    mvn spring-boot:run
    

    I see logging like this:

    ...
    ... AMQ221020: Started EPOLL Acceptor at 0.0.0.0:61616 for protocols [CORE]
    ... AMQ221007: Server is now live
    ...
    

    I can send a message to the broker from another application and the JmsListener receives it.

    I uploaded the project to GitHub.