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

目录

一、什么是虚拟主机

二、编写虚拟主机代码

🍅 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.rhkb.cn/news/84676.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SSL证书DV和OV的区别?

SSL证书是在互联网通信中保护数据传输安全的一种加密工具。它能够确保客户端和服务器之间的通信得以加密&#xff0c;防止第三方窃听或篡改信息。在选择SSL证书时&#xff0c;常见的有DV证书和OV证书&#xff0c;它们在验证标准和信任级别上有所不同。那么SSL证书DV和OV的有哪些…

Linux vi/vim

Linux vi/vim 所有的 Unix Like 系统都会内建 vi 文书编辑器&#xff0c;其他的文书编辑器则不一定会存在。 但是目前我们使用比较多的是 vim 编辑器。 vim 具有程序编辑的能力&#xff0c;可以主动的以字体颜色辨别语法的正确性&#xff0c;方便程序设计。 什么是 vim&…

Mr. Cappuccino的第60杯咖啡——Spring之BeanFactory和ApplicationContext

Spring之BeanFactory和ApplicationContext 类图BeanFactory概述功能项目结构项目代码运行结果总结 ApplicationContext概述功能MessageSource&#xff08;国际化的支持&#xff09;概述项目结构项目代码运行结果 ResourcePatternResolver&#xff08;匹配资源路径&#xff09;概…

面试热题(岛屿数量)

给你一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的的二维网格&#xff0c;请你计算网格中岛屿的数量。 岛屿总是被水包围&#xff0c;并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外&#xff0c;你可以假设该网格的四条边均…

阿里云Nas文件存储的各种场景使用

文章目录 1.ECS服务器挂载NAS文件存储1.1.添加NAS挂载点1.2.为ECS挂载NAS存储image-202202012230314501.3.验证ECS服务器是否挂载了NAS存储1.4.卸载挂载的NAS存储 2.通过命令行的方式在ECS中挂载NAS存储3.KodCloud云盘系统采用NAS存储用户上传的文件3.1.配置云盘系统接入NAS存储…

爬虫013_函数的定义_调用_参数_返回值_局部变量_全局变量---python工作笔记032

然后再来看函数,可以避免重复代码 可以看到定义函数以及调用函数

【MFC】05.MFC第一大机制:程序启动机制-笔记

MFC程序开发所谓是非常简单&#xff0c;但是对于我们逆向人员来说&#xff0c;如果想要逆向MFC程序&#xff0c;那么我们就必须了解它背后的机制&#xff0c;这样我们才能够清晰地逆向出MFC程序&#xff0c;今天这篇文章就来带领大家了解MFC的第一大机制&#xff1a;程序启动机…

Vulhub之Apache HTTPD 换行解析漏洞(CVE-2017-15715)

Apache HTTPD是一款HTTP服务器&#xff0c;它可以通过mod_php来运行PHP网页。其2.4.0~2.4.29版本中存在一个解析漏洞&#xff0c;在解析PHP时&#xff0c;1.php\x0A将被按照PHP后缀进行解析&#xff0c;导致绕过一些服务器的安全策略。 1、docker-compose build、docker-compo…

Technical debt (技术负债 / 技术债)

Technical debt (技术负债 / 技术债) In software development, or any other IT field (e.g., Infrastructure, Networking, etc.) technical debt (also known as design debt or code debt) is the implied cost of future reworking required when choosing an easy but li…

支持对接鸿蒙系统的无线模块及其常见应用介绍

近距离的无线通信得益于万物互联网的快速发展&#xff0c;基于集成部近距离无线连接&#xff0c;为固定和移动设备建立通信的蓝牙技术也已经广泛应用于汽车领域、工业生产及医疗领域。为协助物联网企业终端产品能快速接入鸿蒙生态系统&#xff0c;SKYLAB联手国产芯片厂家研发推…

新能源汽车充电桩控制主板有哪些特点

你是否好奇&#xff0c;新能源汽车充电桩控制主板是什么样子的?它有哪些特点?接下来&#xff0c;我们将为您揭秘。 控制主板是充电桩的大脑&#xff0c;它决定了充电桩的性能和稳定性。睿讯微充电桩主板拥有良好的整机抗干扰能力&#xff0c;能够有效地防止外部信号和电磁波的…

模仿火星科技 基于cesium+水平面积测量+可编辑

​ 当您进入Cesium的编辑水平积测量世界&#xff0c;下面是一个详细的操作过程&#xff0c;帮助您顺利使用这些功能&#xff1a; 1. 创建提示窗&#xff1a; 启动Cesium应用&#xff0c;地图场景将打开&#xff0c;欢迎您进入编辑模式。 在屏幕的一角&#xff0c;一个友好的提…

C++中如何让程序休眠自定义的时长

在C中&#xff0c;可以使用以下几种方法让程序休眠指定的时间&#xff1a; 1 使用操作系统相关的方法&#xff0c;如 Windows 中的 Sleep 函数&#xff0c;需要包含 <windows.h> 头文件 #include <windows.h> // 休眠1000毫秒&#xff08;1秒&#xff09; Sleep(…

Bert详细学习及代码实现详解

BERT概述 BERT的全称是Bidirectional Encoder Representation from Transformers&#xff0c;即双向Transformer的Encoder&#xff0c;因为decoder是不能获要预测的信息的。在大型语料库&#xff08;Wikipedia BookCorpus&#xff09;上训练一个大型模型&#xff08;12 层到 …

Java:Stream API

文章目录 1 说明2 为什么要使用Stream API3 什么是StreamStream的操作三个步骤创建Stream实例一系列中间操作终止操作 1 说明 Java8中有两大最为重要的改变。第一个是 Lambda 表达式&#xff1b;另外一个则是 Stream API。Stream API ( java.util.stream) 把真正的函数式编程风…

android studio内存分析之Memory profiler的使用

目录 Android Studio中内存分析工具Memory profiler的使用1. 打开Memory Profiler2. 工具使用3. 内存选项说明4. 内存性能分析器概览5. 内存计算方式6. 查看内存分配7. 捕获java/kotlin方式查看内存分配8. 堆转储文件导入和导出 内存性能分析器中的泄漏检测 Android Studio中内…

模仿火星科技 基于cesium+ 贴地测量+可编辑

当您进入Cesium的编辑贴地测量世界&#xff0c;下面是一个详细的操作过程&#xff0c;帮助您顺利使用这些功能&#xff1a; 1. 创建提示窗&#xff1a; 启动Cesium应用&#xff0c;地图场景将打开&#xff0c;欢迎您进入编辑模式。在屏幕的一角&#xff0c;一个友好的提示窗将…

【ROS】Ubuntu18.04安装Ros

Ubuntu18.04安装Ros 引言1 ROS安装&#xff08;一键式&#xff09;2 正常安装2.1 添加ROS软件源2.2 添加公钥2.3 更新2.4 安装ros2.5 初始化 rosdep2.6 设置环境2.7 安装rosinstall,便利的工具2.8 检验 3 rviz将bag数据可视化为点云3.1 打开ROS和rviz软件3.2 配置rviz软件可视化…

【论文阅读】基于深度学习的时序预测——Autoformer

系列文章链接 论文一&#xff1a;2020 Informer&#xff1a;长时序数据预测 论文二&#xff1a;2021 Autoformer&#xff1a;长序列数据预测 论文链接&#xff1a;https://arxiv.org/abs/2106.13008 github链接&#xff1a;https://github.com/thuml/Autoformer 解读参考&…

UDS诊断笔记

文章目录 常见缩写简介UDS寻址模式1. 物理寻址&#xff08;点对点、一对一&#xff09;2. 功能寻址&#xff08;广播、一对多&#xff09;3. 功能寻址使用场景举例 UDS报文格式UDS协议栈网络层网络层功能网络层协议1. 单帧 SF&#xff08;Single Frame&#xff09;2. 首帧 FC&a…