2
votes

I have two services which should communicate with each other via ActiveMQ. Everything works nicely when I have the receiver and sender in one service but when I split them, I got a strange activemq exception.

This is configuration of service A:

@EnableScheduling
@SpringBootApplication
@EnableJms
public class App extends SpringBootServletInitializer {

  private static final Logger log = LoggerFactory.getLogger(App.class);

  @Autowired
  private static JmsTemplate jms;

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

}

Sending messages:

  @Autowired
  private JmsTemplate jms;

  public void sendTicket(Reservation reservation) {
    log.debug("---------------sending message----------------");
    // Send a message
    jms.send("mailbox-destination", new MessageCreator() {
      public ObjectMessage createMessage(Session session) throws JMSException {
        ObjectMessage message = session.createObjectMessage();
        message.setObject(reservation);
        return message;
      }
    });
  }

The JMS is configured as in-memory queue:

spring.activemq.in-memory=true
spring.activemq.pooled=false

Service B is similar but id does not define the JmsContainerFactory. It only has the receiver:

@Component
public class Receiver {

  private static final Logger log = LoggerFactory.getLogger(Receiver.class);

  /**
   * Receive a message with reservation and print it out as a e-ticket.
   * 
   * @param reservation
   */
  @JmsListener(destination = "mailbox-destination")
  public void receiveMessage(Reservation reservation) {
    log.info("Received <" + reservation.getId() + ">");
  }
}

Service A have JMS and ActiveMQ broker as maven dependencies:

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-broker</artifactId>
        </dependency>

Service B has only the JMS dependency.

Could you give me a hint how to share the bean between both services and send messages between them? I am completely new to this topic.

I am getting the following exception when receiving a message

org.springframework.jms.UncategorizedJmsException: Uncategorized exception occured during JMS processing; nested exception is javax.jms.JMSException: Could not create Transport. Reason: javax.management.InstanceAlreadyExistsException: org.apache.activemq:type=Broker,brokerName=localhost

EDIT: If I remove the broker dependency from one of the services, the Tomcat does not even start:

java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost]]
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:917)
    at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:262)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.StandardService.startInternal(StandardService.java:439)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:769)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.startup.Catalina.start(Catalina.java:625)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:351)
    at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:485)
Caused by: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost]]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:154)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1409)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1399)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.apache.catalina.LifecycleException: A child container failed during start
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:925)
    at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:871)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    ... 6 common frames omitted
1
You don't share the bean... Also why do you need a custom one, Spring Boot already provides one you can configure with properties. I would say remove the bean altogether as well as the containerFactory property. Also in your sender you don't even need the container (you are only sending not listening).M. Deinum
Thanks for the answer. I removed the factory but I am getting a different exception. See my edited question.Smajl
Only 1 should create the broker, now both are creating it.M. Deinum
If I remove the broker dependency from one of the services, I get the following null pointer exception: java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost]]Smajl

1 Answers

2
votes

JMS server were historically standalone applications deployed separately and consumer as well as listener of the message were communicating with this server. So there are typically three separate applications in JMS equation.

Now Spring Boot allows embed Active MQ JMS broker (JMS server) into your Spring Boot application. But if you configure your Spring Boot service each one with it's own embedded JMS broker, these two JMS brokers are totally separate, don't know about each other and are not connected in any way.

If you want to use JMS in production, it would be much wiser to avoid using Spring Boot embedded JMS brokers and host it separately. So 3 node setup would be preferred for PROD.

Edit:

I suspect I was wrong when with my previous way to share embedded JMS broker. Looking at Spring Docs it doesn't seem to be possible with ActiveMQ:

spring.activemq.broker-url= # URL of the ActiveMQ broker. Auto-generated by default. For instance tcp://localhost:61616

spring.activemq.in-memory=true # Specify if the default broker URL should be in memory. Ignored if an explicit broker has been specified.

I was trying it with HornetQ when I was working on these Spring Enterprise examples, but I can't find such config now. So I bet I had problems and ended up using separate third node.

If you want to further try it, I would

  1. Switch to HornetQ or Artemis
  2. Remove broker dependency from service B and leave there only spring-jms dependency (so that there would be only JMS client dependencies available)
  3. Try to poke with these HornetQ properties to try to have embedded instance that is exposed via port:

    spring.hornetq.mode=embedded
    spring.hornetq.port=5445
    

But As I mentioned I didn't make this working before and not sure if it is possible. Didn't find in Spring Boot docs explicit message it doesn't work in this combination.

I suspect the idea behind embedded Spring Boot JMS broker is to allow local in memory integration testing only, not to expose embedded JMS brokers to the outside world.