项目实战 — 消息队列(7){虚拟主机设计(1)}

news/2025/1/3 6:19:39/

目录

一、什么是虚拟主机

二、编写虚拟主机代码

🍅 1、准备工作

🍅 2、实现exchange相关操作

🎄实现创建交换机exchangeDeclare

🎄 实现 删除交换机exchangeDelete

🍅  3、实现queue相关操作

🎄实现创建队列queueDeclare

🎄实现删除队列queueDelete

🍅 4、实现binding相关操作

🎄 实现交换机的转发规则

🎄 创建绑定queueBind

🎄 删除绑定queueUnbind

🍅 5、实现basicPublish

🎄实现basicPublish类

🎄 完善router类中的代码

三、测试routeTopic

🍅 1、准备工作和收尾工作

 🍅 2、编写测试方法


一、什么是虚拟主机

虚拟主机,就类似于MySQL的database,把交换机,队列,绑定,消息等进行逻辑上的隔离。

这里只实现单个虚拟主机,不仅要管理数据,还要提供一些核心API,供上层代码进行调用。

这里的核心API,主要就是要把之前写的内存中的数据管理和硬盘的数据管理穿起来。

   核心API:

        (1)创建交换机 exhcangeDeclare

        (2)删除交换机 exchangeDelete

        (3)创建队列 queueDeckare

        (4)删除队列 queueDelete

        (5)创建绑定 queueBind

        (6)删除绑定 queueUnbind 

        (7)发送消息 basicPublish

        (8)订阅消息 basicCosume

        (9)确认消息 basicAck

二、编写虚拟主机代码

🍅 1、准备工作

创建一个VirtualHost表示虚拟主机,其中每一个虚拟主机都管理着自己的交换机、队列、绑定、消息和数据,并且提供了一些api供上层使用。

/*
* 表示虚拟主机
* 每个虚拟主机都相当于一个消息队列,管理者自己的交换机、队列、绑定....
* 提供了api供上层调用
* */
@Data
public class VirtualHost {private String vitualHostName;private MemoryDataCenter memoryDataCenter = new MemoryDataCenter();private DiskDataCenter diskDataCenter = new DiskDataCenter();/** 创建构造方法* */public VirtualHost(String name){this.vitualHostName = name;
//        MemoryDataCenter只需要new对象
//        DiskDataCenter需要进行初始化操作,建库建表和初始数据的设定diskDataCenter.init();//        还需要针对硬盘的数据进行恢复到内存中try{memoryDataCenter.recovery(diskDataCenter);}catch (IOException | MqException | ClassNotFoundException e ){e.printStackTrace();System.out.println("[VirtualHost]恢复内存数据失败");}}
}

 注意:有关创建、删除等操作,无法避免的在多线程环境下面进行,所以后续为了保证线程安全,对一些操作还需要加锁。

这里创建一个统一的锁对象,在上面的代码中还新增几条成员变量:

//作为交换机的锁对象private final Object exchangeLocker = new Object();//针对队列的锁对象private final Object queueLocker = new Object();


🍅 2、实现exchange相关操作

表示交换机和虚拟主机之间的关系:使用虚拟主机的名字 + 交换机的真实名字

🎄实现创建交换机exchangeDeclare

public boolean exchangeDeclare(String exchangeName, ExchangeType exchangeType, boolean durable){
//        1、把交换机的名字,加上虚拟主机作为前缀exchangeName = virtualHostName + exchangeName;try{synchronized (exchangeLocker){
//                1.判定交换机是否存在,直接通过内存查询Exchange existsExchange = memoryDataCenter.getExchange(exchangeName);if (existsExchange != null){
//                该交换机已经存在System.out.println("[VirtualHost]交换机已经存在!exchangeName = "+ exchangeName);return true;}//           2、创建交换机,先构造Exchange对象Exchange exchange = new Exchange();exchange.setName(exchangeName);exchange.setType(exchangeType);exchange.setDurable(durable);//            3、把交换机对象写入硬盘if (durable){diskDataCenter.insertExchange(exchange);}//            4、把交换机对象写入内存memoryDataCenter.insertExchange(exchange);System.out.println("[VirtualHost] 交换机创建完成!exchangeName = " + exchangeName);}return true;}catch (Exception e) {System.out.println("[VirtualHost]交换机创建失败!exchangName = " + exchangeName );e.printStackTrace();return false;}}

🎄 实现 删除交换机exchangeDelete

public boolean exchangeDelete(String exchangeName){exchangeName = virtualHostName + exchangeName;try{synchronized (exchangeLocker){//             1.先找到对应的交换机Exchange toDelete = memoryDataCenter.getExchange(exchangeName);if (toDelete == null){throw new MqException("[VirtualHost]交换机不存在无法删除");}
//            2、删除硬盘上的数据if (toDelete.isDurable()){diskDataCenter.deleteExchange(exchangeName);}
//              3、删除内存中的交换数据memoryDataCenter.deleteExchange(exchangeName);System.out.println("[VirtualHost] 交换机删除成功!exchangeName = " + exchangeName);}return true;} catch (Exception e){System.out.println("[VirtualHost] 交换机删除失败!exchangeName = " + exchangeName);e.printStackTrace();return false;}}

🍅  3、实现queue相关操作

表示队列和虚拟主机之间的关系:使用虚拟主机的名字 + 队列的真实名字

🎄实现创建队列queueDeclare

//    创建队列public boolean queueDeclare(String queueName,boolean durable){
//        把队列的名字,拼接上虚拟主机的名字queueName = virtualHostName + queueName;try {synchronized (queueLocker){//1、判定队列是否存在MSGQueue exixtsQueue = memoryDataCenter.getQueue(queueName);if (exixtsQueue != null){System.out.println("[VirtualHost]队列已经存在!queueName = " + queueName);return true;}
//            2、创建队列对象MSGQueue queue = new MSGQueue();queue.setName(queueName);queue.setDurable(durable);//          3、写硬盘if(durable){diskDataCenter.insertQueue(queue);}
//            4、写内容memoryDataCenter.insertQueue(queue);System.out.println("[VirtualHost]队列创建成功!queueName = " + queueName);}return true;} catch (IOException e) {System.out.println("[VirtualHost]队列创建失败!queueName = " + queueName);e.printStackTrace();return false;}}

🎄实现删除队列queueDelete

public Boolean queueDelete(String queueName){queueName = virtualHostName + queueName;try{synchronized (queueLocker){
//            1、根据队列名字,查询当前队列对象MSGQueue queue = memoryDataCenter.getQueue(queueName);if (queue == null){throw new MqException("[VirtualHost]队列不存在!无法删除,queueName = " + queueName);}
//            2、删除硬盘数据if (queue.isDurable()){diskDataCenter.deleteQueue(queueName);}
//            3、删除内存数据memoryDataCenter.deleteQueue(queueName);System.out.println("[VirtualHost]删除队列成功!queueName = " + queueName);}return  true;} catch (Exception e) {System.out.println("[VirtualHost]删除队列失败!queueName = " + queueName);e.printStackTrace();return false;}}


🍅 4、实现binding相关操作

🎄 实现交换机的转发规则

创建 一个Router类,验证bindingKey是否合法,合法返回true没不合法返回false。

public class Router {public  boolean checkBindingKey(String bindingKey){
//        这里暂时不会写具体的步骤,等后面需要了再添加return true;}
}

 然后再VirtualHost里面新增一条成员变量:

private Router router = new Router();

🎄 创建绑定queueBind

 public boolean queueBind(String queueName,String exchangeName,String bindingKey){queueName = virtualHostName + queueName;exchangeName = virtualHostName + exchangeName;try {synchronized (exchangeLocker){synchronized (queueLocker){
//                    1、判定当前的绑定是否已经存在Binding existsBinding = memoryDataCenter.getBinding(exchangeName,queueName);if (existsBinding != null){throw new MqException("[VirtualHost]binding已经存在!queueName = " + queueName + ",exchangeName = " + exchangeName);}//            2、验证bindingKey是否合法if (!router.checkBindingKey(bindingKey)){throw new MqException("[VirtualHost]bindingKey非法!bindingkey = " + bindingKey);}//            3.创建Binding对象Binding binding = new Binding();binding.setExchangeName(exchangeName);binding.setQueueName(queueName);binding.setBindingKey(bindingKey);//            4、获取对应的交换机和队列,如果交换机或者队列不存在,这样的绑定也是无法创建的MSGQueue queue = memoryDataCenter.getQueue(queueName);if (queue == null){throw new MqException("[VirtualHost]队列不存在!queueName = " + queueName);}Exchange exchange = memoryDataCenter.getExchange(exchangeName);if (exchange == null){throw new MqException("[VirtualHost]交换机不存在!exchangeName = " + exchangeName);}//            5、将binding写入写硬盘if(queue.isDurable() && exchange.isDurable()){diskDataCenter.insertBinding(binding);}//            6、将binding写入内存memoryDataCenter.insertBinding(binding);}}System.out.println("[VirtualHost]绑定创建成功! exchangeName = " + exchangeName + "queueName = " + queueName);return true;}catch(Exception e){System.out.println("[VirtualHost]绑定创建失败! exchangeName = " + exchangeName + "queueName = " + queueName);e.printStackTrace();return false;}}

🎄 删除绑定queueUnbind

  注意点:删除绑定时,按照之前删除队列和交换机的设定一样,校验绑定的交换机和队列是否为空,为空就抛出异常,删除绑定失败。但是,如果在进行删除时,发现在删除绑定之前,就已经删了交换机或者队列了,但是绑定还在,此时前面那个逻辑就有问题了。

  所以这里,我们就不校验绑定的交换机或者队列是否存在,直接就尝试删除。

//    删除绑定public boolean queueUnbind(String queueName,String exchangeName) {queueName = virtualHostName + queueName;exchangeName = virtualHostName + exchangeName;try{synchronized (exchangeLocker){synchronized (queueLocker){//            1、获取绑定看是否已经存在Binding binding = memoryDataCenter.getBinding(exchangeName,queueName);if (binding == null){throw new MqException("[VirtualHost]删除绑定失败!绑定不存在!exchangeName = " + exchangeName + ",queueName = " + queueName);}
//
//            2、删除硬盘上面的数据diskDataCenter.deleteBinding(binding);//            3、删除内存上的数据memoryDataCenter.deleteBinding(binding);System.out.println("[VirtualHost]删除绑定成功");}}return true;}catch(Exception e){System.out.println("[VirtualHost]删除绑定失败!exchangeName = " + exchangeName + ",queueName = " + queueName);e.printStackTrace();return false;}}


🍅 5、实现basicPublish

这一块比较复杂哈~

这个API主作用是发送消息到指定的的交换机中,然后再由交换机转发给队列。

关于交换机,这里有三种交换机:

        * Direct 直接交换机 (发送时指定队列名发送)

        * Fanout 扇出交换机(每个队列都发送)

        * Topic 主题交换机(指定bindingKey和RoutingKey)

需求分析里面也提到了这三种交换机,看到这里忘记了的小伙伴建议看看,参考博客项目实战 — 消息队列(1) {需求分析}_‍️藿香正气水的博客-CSDN博客

🎄实现basicPublish类

主要分以下几步:

        (1)转换交换机的名字:虚拟机名 + 交换机名

        (2)检查routingkey是否合法

        (3)根据交换机的名字查找交换机对象

        (4)判断交换机的类型,编写具体的转发规则

                🎊 以直接交换机(direct)的方式转发消息

                        a. 构造消息对象;

                        b. 查找该队列对应的对象,并判断队列是否为空

                        c. 队列存在就给队列写入消息

                🎊 以扇出交换机(fanout)和主题交换机(topic)的方式转发消息

                        a. 获取到绑定对象,判断对应的队列是否存在

                        b. 构造下消息对象

                        c. 判断消息是否能转发给队列

                        d. 转发消息给队列

首先我们再Router类中编写再几个方法,先搭个架子,不具体实现,避免basicPublish类报错

public class Router {
//    判断routingKey和BindingKey是否合法public  boolean checkBindingKey(String bindingKey){return true;}public boolean checkRoutingKey(String routingKey){return true;}//    该方法用来判定该消息是否用来转发给绑定的队列public boolean route(ExchangeType exchangeType,Binding binding,Message message) throws MqException {return true;}private boolean routeTopic(Binding binding,Message message){return true;}
}

编写basicPublish 

//    发送消息到指定的交换机或者队列中public boolean basicPublish(String exchangeName,String routingKey,BasicProperties basicProperties,byte body[]){try {
//            1、转换交换机的名字exchangeName = virtualHostName + exchangeName;
//            2、检查这里的routingKey是否合法if (router.checkRoutingKey(routingKey)){throw new MqException("[VirtualHost]routingKey非法!routingKey = " + routingKey);}
//            3.根据交换机的名字查找到交换机对象Exchange exchange = memoryDataCenter.getExchange(exchangeName);if (exchange == null){throw new MqException("[VirtualHost]交换机不存在!exchangeName = " + exchangeName);}//            4、判断交换机的类型if(exchange.getType() == ExchangeType.DIRECT){
//                按照直接交换机的方式转发消息
//                以routingKey作为队列的名字,直接把消息写入到指定的队列中String queueName = virtualHostName + routingKey;
//                5、构造消息对象Message message = Message.createMessageWithId(routingKey,basicProperties,body);
//                6、查找该队列对应的对象MSGQueue queue = memoryDataCenter.getQueue(queueName);if (queue == null) {throw new MqException("[VirtualHost]队列不存在!queuename = " + queueName);}
//                7、队列存在,直接给队列中写入消息sendMessage(queue,message);}else {
//                按照fanout和topic的方式来转发
//                找到该交换机的所有绑定,并且遍历这些绑定消息ConcurrentHashMap<String ,Binding> bindingsMap = memoryDataCenter.getBindings(exchangeName);for (Map.Entry<String ,Binding> entry : bindingsMap.entrySet()){
//                    (1)获取到该绑定对象,判断对应的队列是否存在Binding binding = entry.getValue();MSGQueue queue = memoryDataCenter.getQueue(binding.getQueueName());if (queue == null){
//                        存在多个队列,这里为了避免因为一个队列的失败影响到其他队列的消息传输
//                        这里就不抛异常System.out.println("[VirtualHost]basicPublish发送消息时间,发现队列不存在!queueName = " + binding.getQueueName());continue;}
//                    (2)构造消息对象Message message = Message.createMessageWithId(routingKey,basicProperties,body);
//                  (3)判定这个消息是否能转发给该队列如果fanout,所有的绑定队列都要转发如果式topic。还需要判定bindingKey和routingKey是不是匹配if(!router.route(exchange.getType(),binding,message)){continue;}
//                  (4)转发消息给队列sendMessage(queue,message);}}return true;}catch (Exception e){System.out.println("[VirtualHost]消息发送失败");e.printStackTrace();return false;}}//    编写sendMessage
public void sendMessage(MSGQueue queue,Message message) throws IOException, MqException {
//        把消息写入到 硬盘 和 内存 中去
//        判定持久化int deliverMode = message.getDeliverMode();
//        deliverMode为1,不持久化,deliverMode 为2 表示持久化if(deliverMode == 2){
//            写入硬盘diskDataCenter.sendMessage(queue,message);}//      写入内存memoryDataCenter.sendMessage(queue,message);}

🎄 完善router类中的代码

 首先编写route()方法,判断该消息是否需要用来转发给绑定的队列。

 public boolean route(ExchangeType exchangeType,Binding binding,Message message) throws MqException {
//        根据不同的exchangeType使用不同的判定转发规则if (exchangeType == ExchangeType.FANOUT){
//            如果是fanout类型,那么所有队列都需要转发}else if(exchangeType == ExchangeType.TOPIC){
//             如果是topic主题交换机return routeTopic(binding,message);}else {throw new MqException("[Router]交换机类型非法! exchangeType = " + exchangeType);}return true;}

然后编写有关topic中的一套转发规则。

首先检测routingKey和bindingKey是否合法:

有关routingKey和bindingKey的一套命名规则:

        🎊 routingKey

        (1)数字、字母、下划线

        (2)使用“.”点号,将routingKey分割程多个部分,形如aaa.bbb.ccc

        🎊 bindingKey

        (1)数字、字母、下划线

        (2)使用" . "点号,把整个bindingKey分成了多个部分

        (3)支持两种特殊的通配符:“ * ” 和“ # ” 。* 和 #必须是作为被分割出来的独立部分,           由" . "分割。形如aaa.*.bbb   

                “ * ”代表可以匹配任何一个独立的部分;

                “ # ”代表可以匹配任何0个或者多个独立的部分。

           第一种情况(bindingKey中没有 * 和 #):此时必须要求routingKey和bindingKey一           模一样,才能够匹配成功。这种就相当于直接交换机。

           第二种情况(bindingKey中有“ * ”):

               设定bindingKey:aaa.*.ccc,此时如果是aaa.bbb.ccc或者aaa.b.ccc这种形式的              routingKey都能匹配成功,但是,如果是aaa.b.ccc这种就会匹配失败

          第三种情况(bindingKey中有#):相当于fanout交换机。

                设定bindingKey:aaa.#.ccc,

                如果routingKey是以下的形式:

                        aaa.bbb.ccc(匹配成功)

                        aaa.b.b.ccc(匹配成功)

                        aaa.ccc(匹配成功)

                        aaa.b.b(匹配失败)

所以,综上所述,直接交换机和扇出交换机属于主题交换机的特例。

//    routingKey构造规则:数字\字母\下划线\使用 . 分割public boolean checkRoutingKey(String routingKey){if (routingKey.length() == 0){
//            空字符串,routingKey为0,可能就是使用的fanout交换机return true;}for (int i = 0; i < routingKey.length(); i++) {char ch = routingKey.charAt(i);
//            判定该字符是否是大写字母if (ch >= 'A' && ch <= 'Z'){continue;}
//            判定该字母是否是小写字母if (ch >= 'a' && ch <= 'z'){continue;}
//            判断字母是否是阿拉伯数字if (ch >= '0' && ch <= '9'){continue;}
//            判定是否是 _ 或者 .if(ch == '_' || ch == '.'){continue;}
//            上面的条件不符合return false;}return true;}//    bindingKey构造规则:数字\字母\下划线\使用 . 分割\允许存在 * 和 # 作为通配符public  boolean checkBindingKey(String bindingKey){if (bindingKey.length() == 0){
//            合法,使用直接交换机和扇出交换机,可以为空,因为此时用不到bindingKeyreturn true;}//        检查是否存在不合法字符for (int i = 0; i < bindingKey.length(); i++) {char ch = bindingKey.charAt(i);if (ch >= 'A' && ch <= 'Z'){continue;}if (ch >= 'a' && ch <= 'z'){continue;}if (ch >= '0' && ch <= '9'){continue;}if (ch == '_' || ch == '.' || ch == '*' || ch == '#'){continue;}return false;}//        检查*或者#的位置是否正确(被 . 进行分割)
//        为什么写作\\.  ,因为,在正则表达式种,"\."和"."都是特殊的字符,所以需要双\\转义String[] words = bindingKey.split("\\.");for (String word : words){
//            如果word为*或者#,那么长度不会大于1if (word.length() > 1 && (word.contains("*") || word.contains("#"))){return false;}}    
//           约定,通配符之间的相邻关系
//           1.aaa.#.#.bbb  => 非法
//           2.aaa.#.*.bbb  => 非法
//           3.aaa.*.#.bbb  => 非法
//           4.aaa.*.*.bbb  => 合法for (int i = 0; i < words.length; i++) {
//            #.#if(words[i].equals("#") && words[i+1].equals("#")){return false;}
//           #.*if (words[i].equals("#") && words[i+1].equals("*")){return false;}
//            *.#if (words[i].equals("*") && words[i+1].equals("#")){return false;}}return true;}

编写routeTopic()方法,考虑routingKey和bindingKey之间的匹配规则

采用双指针:

根据bindingKey的下标,判定当前下标指向的部分。

  (1)指向的是普通字符串,此时要求和routingKey对应的下标指向的内容完全一致

  (2)指向的是 * ,此时无论routingKey指向的是什么,指针都是前进

  (3)遇到了 # ,并且如果#后面没有其他内容了,匹配上了,直接返回true

  (4)遇到了#,#后面仍然有其他内容,然后拿着#后面的部分,去routingKey种找是否有相同的部分,没找到就返回fasle。如果找到了,就把routingkey的箭头指向该位置,指针继续往后走。按照前面的方式,走到末尾为止

  (5)移动过程种,如果同时到达末尾,就返回true;否则返回false。

private boolean routeTopic(Binding binding,Message message){
//        1.进行切分String[] bindingTokens = binding.getBindingKey().split("\\.");String[] routingTokens = message.getRoutingKey().split("\\.");//        2.引入两个下标,指向两个数组的0下标int bindingIndex = 0;int routingIndex = 0;
//        3.进行循环while (bindingIndex < bindingTokens.length && routingIndex < routingTokens.length){if (bindingTokens[bindingIndex].equals("*")){
//                [1]如果遇见*,直接进入下一轮,*可以匹配到任何一个部分bindingIndex++;routingIndex++;continue;} else if (bindingTokens[bindingIndex].equals("#")){
//                如果遇到#,看还有没有下一个为止bindingIndex++;if (bindingIndex == bindingTokens.length){
//                    [3]直接到了末尾return true;}
//               [4] #后面还有内容,继续向后
//                findNextMatch用来查找该部分在routingKey的位置,返回改下标,没找到就返回-1routingIndex = findNextMatch(routingTokens,routingIndex,bindingTokens[bindingIndex]);if(routingIndex == -1){
//                    没找到匹配的结果,匹配失败return false;}
//                找到了匹配的结果,继续向后匹配bindingIndex++;routingIndex++;} else {
//                [1]如果遇见了普通的字符串(不含#和*),如果一样就返回trueif (!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])){return false;}bindingIndex++;routingIndex++;}}
//      [5]判断是否双方同时到达末尾if(bindingIndex == bindingTokens.length && routingIndex == routingTokens.length){return true;}return true;}private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {for (int i = routingIndex; i < routingTokens.length ; i++) {if (routingTokens[i].equals(bindingToken)){return i;}}return -1;}

 


三、测试routeTopic

🍅 1、准备工作和收尾工作

创建测试类routerTests

@SpringBootTest
public class RouterTests {private Router router = new Router();private Binding binding = null;private Message message = null;@BeforeEachpublic void setUp(){binding = new Binding();message = new Message();}@AfterEachpublic void tearDown(){binding = null;message = null;}
}

以下是一些测试用例

    [测试用例]binding key          routing key         result1 aaa                  aaa                 true2 aaa.bbb              aaa.bbb             true3 aaa.bbb              aaa.bbb.ccc         false4 aaa.bbb              aaa.ccc             false5 aaa.bbb.ccc          aaa.bbb.ccc         true6 aaa.*                aaa.bbb             true7 aaa.*.bbb            aaa.bbb.ccc         false8 *.aaa.bbb            aaa.bbb             false9 #                    aaa.bbb.ccc         true10 aaa.#                aaa.bbb             true11 aaa.#                aaa.bbb.ccc         true12 aaa.#.ccc            aaa.ccc             true13 aaa.#.ccc            aaa.bbb.ccc         true14 aaa.#.ccc            aaa.aaa.bbb.ccc     true15 #.ccc                ccc                 true16 #.ccc                aaa.bbb.ccc         true

 🍅 2、编写测试方法

根据上面的测试用例编写16个测试方法

@Testpublic void test1() throws MqException {binding.setBindingKey("aaa");message.setRoutingKey("aaa");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test2() throws MqException {binding.setBindingKey("aaa.bbb");message.setRoutingKey("aaa.bbb");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test3() throws MqException {binding.setBindingKey("aaa.bbb");message.setRoutingKey("aaa.bbb.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test4() throws MqException {binding.setBindingKey("aaa.bbb");message.setRoutingKey("aaa.ccc");Assertions.assertFalse(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test5() throws MqException {binding.setBindingKey("aaa.bbb.ccc");message.setRoutingKey("aaa.bbb.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test6() throws MqException {binding.setBindingKey("aaa.*");message.setRoutingKey("aaa.bbb");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test7() throws MqException {binding.setBindingKey("aaa.*.bbb");message.setRoutingKey("aaa.bbb.ccc");Assertions.assertFalse(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test8() throws MqException {binding.setBindingKey("*.aaa.bbb");message.setRoutingKey("aaa.bbb");Assertions.assertFalse(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test9() throws MqException {binding.setBindingKey("#");message.setRoutingKey("aaa.bbb.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test10() throws MqException {binding.setBindingKey("aaa.#");message.setRoutingKey("aaa.bbb");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test11() throws MqException {binding.setBindingKey("aaa.#");message.setRoutingKey("aaa.bbb.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test12() throws MqException {binding.setBindingKey("aaa.#.ccc");message.setRoutingKey("aaa.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test13() throws MqException {binding.setBindingKey("aaa.#.ccc");message.setRoutingKey("aaa.bbb.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test14() throws MqException {binding.setBindingKey("aaa.#.ccc");message.setRoutingKey("aaa.aaa.bbb.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test15() throws MqException {binding.setBindingKey("#.ccc");message.setRoutingKey("ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test16() throws MqException {binding.setBindingKey("#.ccc");message.setRoutingKey("aaa.bbb.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}


http://www.ppmy.cn/news/1022015.html

相关文章

基于PyTorch的图像识别

前言 图像识别是计算机视觉领域的一个重要方向&#xff0c;具有广泛的应用场景&#xff0c;如医学影像诊断、智能驾驶、安防监控等。在本项目中&#xff0c;我们将使用PyTorch来开发一个基于卷积神经网络的图像识别模型&#xff0c;用来识别图像中的物体。下面是要识别的四种物…

Scala(第一章Scala入门)

文章目录 1.1 概述 1.1.1 为什么学习Scala1.1.2 Scala发展历史1.1.3 Scala和Java关系1.1.4 Scala语言特点 1.2 Scala环境搭建1.3 Scala插件安装1.4 HelloWorld案例 1.4.1 创建IDEA项目工程1.4.2 class和object说明1.4.3 Scala程序反编译 1.5 关联Scala源码1.6官方编程指南 1.1…

优思学院|6sigma合格率计算公式

概述 在现代竞争激烈的商业环境中&#xff0c;质量控制对于制造业和服务业都至关重要。其中&#xff0c;一个强大的方法是采用6sigma&#xff08;也称为6σ&#xff09;方法来提高质量和降低缺陷率。本文将深入探讨6sigma合格率计算公式&#xff0c;解释其在质量管理中的应用&…

0基础学C#笔记09:希尔排序法

文章目录 前言一、希尔排序的思想二、使用步骤总结 前言 希尔排序可以说是插入排序的一种变种。无论是插入排序还是冒泡排序&#xff0c;如果数组的最大值刚好是在第一位&#xff0c;要将它挪到正确的位置就需要 n - 1 次移动。也就是说&#xff0c;原数组的一个元素如果距离它…

centos按用户保存历史执行命令

centos7 按用户记录历史命令的方法 在/etc/profile文件中添加以下代码。 添加完成后执行source /etc/profile 用户重新登录即可发现history被清空了。这时可以去看/usr/share/.history文件夹&#xff0c;该文件夹保存了所有用户每次登录所执行过的的操作记录。 文件路径为 /usr…

vscode vue3+vite 配置eslint

vue2webpackeslint配置 目前主流项目都在使用vue3vite&#xff0c;因此针对eslint的配置做了一下总结。 引入ESlint、pritter 安装插件&#xff0c;执行以下命令 // eslint // prettier // eslint-plugin-vue // eslint-config-prettier // eslint-plugin-prettier yarn ad…

【Java】JVM GC配置指南

1、JDK版本 以下所有优化全部基于JDK8版本&#xff0c;强烈建议低版本升级到JDK8&#xff0c;并尽可能使用update_191以后版本。 2、如何选择垃圾回收器 响应优先应用&#xff1a;面向C端对响应时间敏感的应用&#xff0c;堆内存8G以上建议选择G1&#xff0c;堆内存较小或低…

【BMC】OpenBMC开发基础3:引入新的开源配方

引入新的开源配方 前面介绍了如何在OpenBMC中通过新建配方引入自己的程序&#xff0c;也介绍了如何修改原有的程序&#xff0c;下面要介绍的是如何引入开源的新程序&#xff0c;这在OE系统上是很方便的&#xff0c;重点就在于引入新的配方。 OE为了方便开发者使用&#xff0c…