# Spring集成Artemis实现JSM的异步消息传递

***

JMS是一套Java定义的标准，用于在程序之间进行消息的流通。

## 一、概念逻辑

JMS的逻辑如下：

发布信息的称为JMS生产者，生产者将信息发送给JMS服务器，同时说明发送到的目的地（Destination），JMS服务器将消息保存在对应目的地的一个队列（queue）中，等待JMS消费者领取。

JMS消费者有两种领取信息的方式，一种是拉（pull）模式，即发出收取消息的请求，等待直到队列中有消息到达为止；一种是推（push）模式，即由容器监听目标队列，消息到达时通知对应的消费者进行处理。

这样的模式意味着信息的传送可以不一定是一对一的。对发送到队列的信息，消费者提取后即pop掉。

JMS服务器（MQ，消息队列，在Artemis里称broker）常用apeche的ActiveMQ，以及其新版本Artemis。在本地或远程部署Artemis实例后即可使用。

Spring提供了一套JMS实现，即JMSTemplate，可用它写出消费者和生产者，与Artemis交互。

## 二、引入依赖及配置

要在Spring项目中使用artemis，可在maven用springboot starter引入相应的框架：

```xml
<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-artemis</artifactId>
</dependency>
```

在application.yml中，可以对Artemis做一些配置。如果只是使用本地的Artemis broker实例，可以不做任何配置。

配置示例如下：

```yaml
spring:
  artemis:
    broker-url: tcp://api.mcyou.cc
    user: admin
    password: passwd
```

这里配置了broker的地址、用户名及密码。注意它是基于tcp的。

接下来要下载并创建artemis实例。首先在Apeche网站下载artemis，然后进入其lib，使用命令`artemis create 目标目录` 来在目标位置创建一个实例。创建时会要求输入想要的用户名及密码。

完成后来到实例的目录，进入/bin/，执行`artemis run`即可运行该实例。

## 三、创建生产者

```java
import org.apache.activemq.artemis.jms.client.ActiveMQQueue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;

import javax.jms.Destination;

@Service
public class JmsMessagingService {
    private JmsTemplate jms;
    private Destination messageQueue = new ActiveMQQueue("cc.mcyou.queue");;

    @Autowired
    public JmsMessagingService(JmsTemplate jms){
        this.jms = jms;
    }

    public void sendUser(User user){
        jms.send(messageQueue, session -> session.createObjectMessage(user));
    }

    public void sendUser2(User user){
        jms.convertAndSend("cc.mcyou.queue", user);
    }
}
```

导入一个JmsTemplate，然后使用jms实例即可完成发送操作。

这里sendUser方法和sendUser2方法展示了两种发送对象的方式。sendUser方法使用jms.send方法，需要使用MessageCreator来构造Message；sendUser2方法直接使用convertAndSend方法，发送对象更方便一些。

对于每个发送，都要指定一个`Destination`。这里既可以构造一个Destination对象，也可以直接用字符串表明destination的名字，传递给artemis处理。

## 四、创建拉模式的消费者

```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;

@Component
public class JmsMessageReceiver {

    private JmsTemplate jms;

    @Autowired
    public JmsMessageReceiver(JmsTemplate jms){
        this.jms = jms;
    }

    public User receiveUser(){
        return (User)jms.receiveAndConvert("cc.mcyou.queue");
    }
}
```

还是通过Spring容器注入一个JmsTemplate的实例。在接收信息时，对应发送信息时的方法名，这里同样可以用`receive`方法或`receiveAndConvert`方法，且同样要指定一个接收的目的地。接收对象时直接用`receiveAndConvert`比较简单。如果用`receive`方法，需要手动注入一个`MessageConverter`对其进行转换。

注意对于拉模式的这种`receive`等方法，在调用时进程会被阻塞，等待信息到达。

## 五、创建推模式的消费者

```java
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

@Component
public class UserListener {

    @JmsListener(destination = "cc.mcyou.queue")
    public void receiveUser(User user){
        System.out.println("收到用户："+user.name);
    }
}
```

推模式是一个Listener的模式。使用注解`@JmsListener`注册监听器，同时指定destination。这样这个方法就交给Spring挂起，等待消息到达时由Spring调用这个方法，进行后续的处理。

推模式的最大好处在于不会阻塞进程，比较适合需要保证可用性的场景。

使用Spring提供的模板实现JMS，然后与Artemis通信，这样就使得限定于Java的JMS得以利用跨语言的Artemis进行通信。当然基于JMS的信息仍然必须由基于JMS的消费者接受才能利用。
