JMS实例程序

Git 项目地址:https://github.com/anguoyoula/JMS

一、JMS简介

1、什么是JMS

JMS即ava消息服务(Java Message Service),通常是一个具体的消息中间件来实现具体消息服务。JMS允许两个应用程序或系统之间解耦合的进行消息传递,JMS是消息传递机制的一个具体的规范。一搜一大堆。

二、JMS的Linux测试环境

1、Java开发环境

JDK:1.8
依赖:activemq-all 5.10.0

2、服务器环境

JDK:1.8
操作系统:Centos7
消息中间件:apache-activemq-5.15.6

3、搭建注意事项

  1. 没有Linux环境就在Windows环境搭建,也许还简单一些。
  2. Linux环境搭建需要注意防火墙的设置,推荐一篇关于防火墙命令的博文:https://www.cnblogs.com/moxiaoan/p/5683743.html。
  3. 依赖添加那个全家桶依赖就够了,ActiveMQ依赖里面有JMS接口。主要是方便、不出错,学习的时候,不出错最好。
  4. 如果中间件配置了认证的话,默认的用户和密码都是:admin。

三、JMS整体API架构

1、JMS整体API架构图

JMS的主要接口及其交互实例图

2、JMS开发基本流程

结合上面的交互实例图,JMS的开发流程代码如下:

  • 声明连接工厂,用于获取与消息服务器的连接:ConnectionFactory
  • 通过连接工厂创建连接:Connection
  • 通过连接创建会话:Session

    消息发送者:
  • 通过会话创建消息发送者:MessageProducer
  • 通过会话创建消息目的地:Destination
  • 通过会话创建需要发送的消息:Message
  • 通过MessageProducer send Message to Destination

    消息接收者
  • 通过会话创建消息接收者:MessageConsumer
  • 通过会话创建消息来源地:Destination
  • 设置MessageConsumer的监听器
  • 开启连接的消息接收

以上的流程仅仅是基本的流程,还有很多需要注意的细节,例如:

  • 连接的认证信息
  • 会话的事物控制、会话的单线程访问权限控制(JMS规定一个Session只能够被一个线程访问,但是一个连接可以有多个Session)
  • 消息的类型(文本?字节?对象?…)
  • 消息的投递模式
  • 消息分组

四、实例程序

这个实例开发以一个简易的聊天室来学习JMS的基本开发流程,使用订阅发布来进行实例开发。

1、简易说明消息传送模型

详细模型讨论见我的另外一篇博文:https://blog.csdn.net/qq_37121463/article/details/83212732

①:点对点模型

每一条发送到消息中间件某个目的地(消息队列)的消息只会被一个监听该队列的消息消费者消费,哪怕有多个消费者监听这个消息队列。每条消息只被一个消费者消费。

②:订阅发布模型

每一条发送到消息中间件某个目的地(主题)的消息会被已经订阅该主题的消息消费者获取,每一个消费者都会获取到这个消息的备份。每条消息会被已经订阅的消费者们消费。

2、程序逻辑简述

  1. 程序启动,获取JMS服务的基本条件:获取消息中间件的连接、创建会话。
  2. 订阅聊天主题,以便获取聊天室的聊天消息。
  3. 监听下线提醒主题以收取其他的用户下线消息提醒。
  4. 监听上线提醒主题以收取其他的用户上线消息提醒。
  5. 向上线提醒主题发送当前用户上线消息,其他在线用户将监听到这个用户上线的消息。
  6. 开启连接,以便获取主题的消息。
  7. 监听输入,发送到聊天主题。监听退出。
  8. 向下线主题发送当前用户下线提醒。

3、程序具体代码如下

package jms.step1;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;
import java.util.Scanner;

public class ChatRoom {

    public static void main(String[] args) throws JMSException{

        /*
        程序关闭命令
         */
        final String exit = "exit";

        /*
        一个用于监听其他人员登录的主题,某个加入这个聊天室的人员都会收到一个比他后加入该聊天室的人员登录提醒信息
         */
        final String loginTopicName = "loginTopicName";
        /*
        一个用于监听其他人员登出的主题,正在这个聊天室的人员都会收到一个比他先登出该聊天室的人员登出提醒信息
         */
        final String logoutTopicName = "logoutTopicName";
        /*
        加入同一个聊天室的人员会向同一个聊天主题发送消息,同时也会订阅该主题来获取其他人员发送的消息
         */
        final String chatMessageTopicName = "chatMessageTopicName";

        /*
        消息中间件服务器的连接url
         */
        final String url = "tcp://192.168.161.151:61616";
        /*
        中间件服务器的认证信息
         */
        final String userName = "admin";
        final String password = "admin";

        /*
        当前用户的名称,用于标识消息来源
         */
        String name;

        Scanner scanner = new Scanner(System.in);

        /*
        获取当前人员的标识
         */
        System.out.println("请输入你的名字:");
        name = scanner.nextLine();

        /////1.程序启动,获取JMS服务的基本条件:获取消息中间件的连接、创建会话。

        /*
        获取具体的连接工厂,这里是用ActiveMQ,所以使用ActiveMQConnectionFactory连接工厂。
        参数1、2:连接的认证信息,可以没有,如果有但是这里没有配置的话,需要在创建连接的时候传入认证信息
        参数3:中间件服务器的连接url,没有就是默认的本机61616端口
         */
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(userName,password,url);

        /*
        创建连接,上面连接工厂传入了认证信息,所以没有再传入,如果连接工厂没有传入认证信息,但是消息服务中间件又需要
        认证信息,这里不传入的话,将会报错。
         */
        Connection connection = connectionFactory.createConnection();

        /*
        创建会话,会话对象只能够被一个线程使用。
        参数1:是否开启事物控制,详细见我博客:https://blog.csdn.net/qq_37121463/article/details/83212759
        参数2:消息的签收模式,详细见我博客:https://blog.csdn.net/qq_37121463/article/details/83212752
         */
        Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);


        /////2.订阅聊天主题,以便获取聊天室的聊天消息。

        /*
        创建一个目的地,这个目的地是一个主题,当向这个主题发送消息时,如果中间件没有这个主题,将创建这个主题。
         */
        Destination chatMessageTopic = session.createTopic(chatMessageTopicName);

        /*
        创建一个消息消费者来消费这个主题的消息,即获取聊天室里面的信息。
        第一个参数:目的地
        第二个参数:消息选择器,选取消息的规则表达式,详细见我的博客:https://blog.csdn.net/qq_37121463/article/details/83212752
        第三个参数:指明当前消费者是否可以接受"本地"消息.noLocal参数只对Topic类型的"目的地"有效。
            如果消息消费者和生产者有一个Connection创建(即它们具有同一个ClientID,或者说底层是一个TCP链接),
            即使它们是在不同的session中,它们均被认为是“本地”。
        在这里我们要看见自己发的消息,所以第三个参数为false,表示接受本地发出去的消息。同时我们监听所有消息,没有消息选择。
         */
        MessageConsumer chatMessageConsumer = session.createConsumer(chatMessageTopic,null,false);

        /*
        为这个主题设置监听器,当有消息被发到这个主题时,这个监听器将被调用,以获取聊天室的信息,但是在connection没有start之前
        ,消息无法被监听。
         */
        chatMessageConsumer.setMessageListener(message -> {

            if(message instanceof TextMessage){

                TextMessage textMessage = (TextMessage)message;

                /*
                显示聊天的信息,这些信息是多个应用组件之间的协商结果,简单的说就是主要是这种消息,你就必须有什么,必须怎么发 ...
                 */
                try{
                    /*
                    每条聊天消息都必须将谁发的这条信息给放在属性里面
                     */
                    String author = textMessage.getStringProperty("author");
                    /*
                    获取消息里面的消息,也就是放在里面的聊天信息
                     */
                    String content = textMessage.getText();

                    System.out.println(author + " : " + content);
                }catch(JMSException e){
                    e.printStackTrace();
                }
            }
        });


        /////3.监听下线提醒主题以收取其他的用户下线消息提醒

        Destination logoutTopic = session.createTopic(logoutTopicName);

        MessageConsumer logoutMessageConsumer = session.createConsumer(logoutTopic,null,false);

        logoutMessageConsumer.setMessageListener(message -> {

            if(message instanceof TextMessage){

                TextMessage textMessage = (TextMessage)message;

                try{
                    String author = textMessage.getText();

                    System.out.println(author + "\t下线了");
                }catch(JMSException e){
                    e.printStackTrace();
                }
            }
        });


        /////4.监听上线提醒主题以收取其他的用户上线消息提醒。
        Destination loginTopic = session.createTopic(loginTopicName);

        MessageConsumer loginMessageConsumer = session.createConsumer(loginTopic,null,false);

        loginMessageConsumer.setMessageListener(message -> {

            if(message instanceof TextMessage){

                TextMessage textMessage = (TextMessage)message;

                try{
                    String author = textMessage.getText();

                    System.out.println(author + "\t上线了");
                }catch(JMSException e){
                    e.printStackTrace();
                }
            }
        });


        /////5.向上线提醒主题发送当前用户上线消息,其他在线用户将监听到这个用户上线的消息。
        /*
        创建一个消息提供者,用于发送当前用户的上线提醒
         */
        MessageProducer loginMessageProducer = session.createProducer(loginTopic);

        /*
        创建一个文本类型的消息,可以直接传参数创建一个等待发送的文本消息,也可以创建一个空参文本消息,再进行set设置
         */
        TextMessage userLoginMessage = session.createTextMessage(name);

        /*
        发送这个文本消息。
        void send(Message message,int deliveryMode,int priority,long timeToLive);
        发送消息,并为此消息指定"传输模式"和"消息权重".如果某条消息需要特定声明这些属性,那么你可以使用此方法.
        void send(Destinantion des,Message message): 向指定的目的地,发送消息;
        这个方法并不常用,通常用在"请求-应答"模式中:即消息消费者接收到消息之后,需要临时创建一个Producer并将"应答消息"发送到指定的"replyTo"地址.
        传输模式见我博客:https://blog.csdn.net/qq_37121463/article/details/83212752
        API解释见:https://www.cnblogs.com/whsa/p/4223728.html
         */
        loginMessageProducer.send(userLoginMessage);

        /////6.开启连接,以便获取主题的消息。
        /*
        开启连接接收消息,使用connection.stop();关闭消息接收,关闭后可以再次调用start开启消息接收。注意:close是关闭连接。
         */
        connection.start();


        /////7.监听输入,发送到聊天主题。监听退出
        /*
            创建一个消息提供者来进行聊天消息发送
             */
        MessageProducer chatMessageProducer = session.createProducer(chatMessageTopic);

        String content = null;

        while(true){

            content = scanner.nextLine();

            if(content == null || content.length() < 1){
                continue;
            }else {

                if(content.equals(exit)){

                    /*
                    发送离线通知
                     */
                    MessageProducer logoutMessageProducer = session.createProducer(logoutTopic);

                    TextMessage logoutTextMessage = session.createTextMessage(name);

                    logoutMessageProducer.send(logoutTextMessage);

                    /*
                    退出系统
                     */
                    break;
                }
                /*
                发送消息
                 */
                TextMessage chatMessage = session.createTextMessage(content);
                /*
                设置消息属性为当前用户
                 */
                chatMessage.setStringProperty("author",name);
                /*
                发送聊天消息
                 */
                chatMessageProducer.send(chatMessage);
            }
        }

        System.out.println("再见:" + name);

        System.exit(-1);
    }
}

五、总结

  • 开发流程基本上是固定的,特别的一点,Message不能自己new。
  • 记住JMS的API/接口交互示例图就成功大半。
  • 访问中间件管理页面链接:http://[ip]:8161/admin/
    在这里插入图片描述

路漫漫其修远兮,吾将上下而求索。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章