当前位置: 首页 > news >正文

✔ ★Java项目——设计一个消息队列(二)

Java项目——设计一个消息队列

  • 四. 项⽬创建
  • 五. 创建核⼼类
    • 创建 Exchange(名字、类型、持久化)
    • 创建 MSGQueue(名字、持久化、独占标识)
    • 创建 Binding(交换机名字、队列名字、bindingKey用于与routingKey匹配)
    • 创建 Message(序列化、消息属性、消息体、起始位置和结束位置、有效、工厂方法)
  • 六. 数据库设计
    • 配置 sqlite
    • 实现创建表
    • 实现数据库基本操作
    • 实现 DataBaseManager
    • 测试 DataBaseManager

四. 项⽬创建

创建 SpringBoot 项⽬.
使⽤ SpringBoot 2 系列版本, Java 8.
依赖引⼊ Spring Web 和 MyBatis

五. 创建核⼼类

创建包 mqserver.core

创建 Exchange(名字、类型、持久化)

在这里插入图片描述

package com.example.mq.mqserver.core;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;import java.util.HashMap;
import java.util.Map;/** 这个类表示一个交换机*/
public class Exchange {// 此处使用 name 来作为交换机的身份标识. (唯一的)private String name;// 交换机类型, DIRECT, FANOUT, TOPICprivate ExchangeType type = ExchangeType.DIRECT;// 该交换机是否要持久化存储. true 表示需要持久化; false 表示不必持久化.private boolean durable = false;// 如果当前交换机, 没人使用了, 就会自动被删除.// 这个属性暂时先列在这里, 后续的代码中并没有真的实现这个自动删除功能~~ (RabbitMQ 是有的)private boolean autoDelete = false;// arguments 表示的是创建交换机时指定的一些额外的参数选项. 后续代码中并没有真的实现对应的功能, 先列出来. (RabbitMQ 也是有的)// 为了把这个 arguments 存到数据库中, 就需要把 Map 转成 json 格式的字符串.private Map<String, Object> arguments = new HashMap<>();public String getName() {return name;}public void setName(String name) {this.name = name;}public ExchangeType getType() {return type;}public void setType(ExchangeType type) {this.type = type;}public boolean isDurable() {return durable;}public void setDurable(boolean durable) {this.durable = durable;}public boolean isAutoDelete() {return autoDelete;}public void setAutoDelete(boolean autoDelete) {this.autoDelete = autoDelete;}// 这里的 get set 用于和数据库交互使用.public String getArguments() {// 是把当前的 arguments 参数, 从 Map 转成 String (JSON)ObjectMapper objectMapper = new ObjectMapper();try {return objectMapper.writeValueAsString(arguments);} catch (JsonProcessingException e) {e.printStackTrace();}// 如果代码真异常了, 返回一个空的 json 字符串就 okreturn "{}";}// 这个方法, 是从数据库读数据之后, 构造 Exchange 对象, 会自动调用到public void setArguments(String argumentsJson) {// 把参数中的 argumentsJson 按照 JSON 格式解析, 转成// 上述的 Map 对象ObjectMapper objectMapper = new ObjectMapper();try {this.arguments = objectMapper.readValue(argumentsJson, new TypeReference<HashMap<String, Object>>() {});} catch (JsonProcessingException e) {e.printStackTrace();}}// 在这里针对 arguments, 再提供一组 getter setter , 用来去更方便的获取/设置这里的键值对.// 这一组在 java 代码内部使用 (比如测试的时候)public Object getArguments(String key) {return arguments.get(key);}public void setArguments(String key, Object value) {arguments.put(key, value);}public void setArguments(Map<String, Object> arguments) {this.arguments = arguments;}
}

创建 MSGQueue(名字、持久化、独占标识)

在这里插入图片描述

package com.example.mq.mqserver.core;import com.example.mq.common.ConsumerEnv;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;/** 这个类表示一个存储消息的队列* MSG => Message*/
public class MSGQueue {// 表示队列的身份标识.private String name;// 表示队列是否持久化, true 表示持久化保存, false 表示不持久化.private boolean durable = false;// 这个属性为 true, 表示这个队列只能被一个消费者使用(别人用不了). 如果为 false 则是大家都能使用// 这个 独占 功能, 也是先把字段列在这里, 具体的独占功能暂时先不实现.private boolean exclusive = false;// 为 true 表示没有人使用之后, 就自动删除. false 则是不会自动删除.// 这个 自动删除 功能, 也是先把字段列在这里, 具体的独占功能暂时先不实现.private boolean autoDelete = false;// 也是表示扩展参数. 当前也是先列在这里, 先暂时不实现private Map<String, Object> arguments = new HashMap<>();// 当前队列都有哪些消费者订阅了.private List<ConsumerEnv> consumerEnvList = new ArrayList<>();// 记录当前取到了第几个消费者. 方便实现轮询策略.private AtomicInteger consumerSeq = new AtomicInteger(0);// 添加一个新的订阅者public void addConsumerEnv(ConsumerEnv consumerEnv) {consumerEnvList.add(consumerEnv);}// 订阅者的删除暂时先不考虑.// 挑选一个订阅者, 用来处理当前的消息. (按照轮询的方式)public ConsumerEnv chooseConsumer() {if (consumerEnvList.size() == 0) {// 该队列没有人订阅的return null;}// 计算一下当前要取的元素的下标.int index = consumerSeq.get() % consumerEnvList.size();consumerSeq.getAndIncrement();return consumerEnvList.get(index);}public String getName() {return name;}public void setName(String name) {this.name = name;}public boolean isDurable() {return durable;}public void setDurable(boolean durable) {this.durable = durable;}public boolean isExclusive() {return exclusive;}public void setExclusive(boolean exclusive) {this.exclusive = exclusive;}public boolean isAutoDelete() {return autoDelete;}public void setAutoDelete(boolean autoDelete) {this.autoDelete = autoDelete;}public String getArguments() {ObjectMapper objectMapper = new ObjectMapper();try {return objectMapper.writeValueAsString(arguments);} catch (JsonProcessingException e) {e.printStackTrace();}return "{}";}public void setArguments(String argumentsJson) {ObjectMapper objectMapper = new ObjectMapper();try {this.arguments = objectMapper.readValue(argumentsJson, new TypeReference<HashMap<String, Object>>() {});} catch (JsonProcessingException e) {e.printStackTrace();}}public Object getArguments(String key) {return arguments.get(key);}public void setArguments(String key, Object value) {arguments.put(key, value);}public void setArguments(Map<String, Object> arguments) {this.arguments = arguments;}
}

创建 Binding(交换机名字、队列名字、bindingKey用于与routingKey匹配)

在这里插入图片描述

package com.example.mq.mqserver.core;/** 表示队列和交换机之间的关联关系*/
public class Binding {private String exchangeName;private String queueName;// bindingKey 就是在出题, 要求领红包的人要画个 "桌子" 出来~~private String bindingKey;// Binding 这个东西, 依附于 Exchange 和 Queue 的!!!// 比如, 对于持久化来说, 如果 Exchange 和 Queue 任何一个都没有持久化,// 此时你针对 Binding 持久化是没有任何意义的public String getExchangeName() {return exchangeName;}public void setExchangeName(String exchangeName) {this.exchangeName = exchangeName;}public String getQueueName() {return queueName;}public void setQueueName(String queueName) {this.queueName = queueName;}public String getBindingKey() {return bindingKey;}public void setBindingKey(String bindingKey) {this.bindingKey = bindingKey;}
}

创建 Message(序列化、消息属性、消息体、起始位置和结束位置、有效、工厂方法)

在这里插入图片描述

package com.example.mq.mqserver.core;import java.io.Serializable;
import java.util.Arrays;
import java.util.UUID;/** 表示一个要传递的消息* 注意!!! 此处的 Message 对象, 是需要能够在网络上传输, 并且也需要能写入到文件中.* 此时就需要针对 Message 进行序列化和反序列化.* 此处使用 标准库 自带的 序列化/反序列化 操作.*/
public class Message implements Serializable {// 这两个属性是 Message 最核心的部分.private BasicProperties basicProperties = new BasicProperties();private byte[] body;// 下面的属性则是辅助用的属性.// Message 后续会存储到文件中(如果持久化的话).// 一个文件中会存储很多的消息. 如何找到某个消息, 在文件中的具体位置呢?// 使用下列的两个偏移量来进行表示. [offsetBeg, offsetEnd)// 这俩属性并不需要被序列化保存到文件中~~ 此时消息一旦被写入文件之后, 所在的位置就固定了. 并不需要单独存储.// 这俩属性存在的目的, 主要就是为了让内存中的 Message 对象, 能够快速找到对应的硬盘上的 Message 的位置.private transient long offsetBeg = 0;  // 消息数据的开头距离文件开头的位置偏移(字节)private transient long offsetEnd = 0;  // 消息数据的结尾距离文件开头的位置偏移(字节)// 使用这个属性表示该消息在文件中是否是有效消息. (针对文件中的消息, 如果删除, 使用逻辑删除的方式)// 0x1 表示有效. 0x0 表示无效.private byte isValid = 0x1;// 创建一个工厂方法, 让工厂方法帮我们封装一下创建 Message 对象的过程.// 这个方法中创建的 Message 对象, 会自动生成唯一的 MessageId// 万一 routingKey 和 basicProperties 里的 routingKey 冲突, 以外面的为主.public static Message createMessageWithId(String routingKey, BasicProperties basicProperties, byte[] body) {Message message = new Message();if (basicProperties != null) {message.setBasicProperties(basicProperties);}// 此处生成的 MessageId 以 M- 作为前缀.message.setMessageId("M-" + UUID.randomUUID());message.setRoutingKey(routingKey);message.body = body;// 此处是把 body 和 basicProperties 先设置出来. 他俩是 Message 的核心内容.// 而 offsetBeg, offsetEnd, isValid, 则是消息持久化的时候才会用到. 在把消息写入文件之前再进行设定.// 此处只是在内存中创建一个 Message 对象.return message;}public String getMessageId() {return basicProperties.getMessageId();}public void setMessageId(String messageId) {basicProperties.setMessageId(messageId);}public String getRoutingKey() {return basicProperties.getRoutingKey();}public void setRoutingKey(String routingKey) {basicProperties.setRoutingKey(routingKey);}public int getDeliverMode() {return basicProperties.getDeliverMode();}public void setDeliverMode(int mode) {basicProperties.setDeliverMode(mode);}public BasicProperties getBasicProperties() {return basicProperties;}public void setBasicProperties(BasicProperties basicProperties) {this.basicProperties = basicProperties;}public byte[] getBody() {return body;}public void setBody(byte[] body) {this.body = body;}public long getOffsetBeg() {return offsetBeg;}public void setOffsetBeg(long offsetBeg) {this.offsetBeg = offsetBeg;}public long getOffsetEnd() {return offsetEnd;}public void setOffsetEnd(long offsetEnd) {this.offsetEnd = offsetEnd;}public byte getIsValid() {return isValid;}public void setIsValid(byte isValid) {this.isValid = isValid;}@Overridepublic String toString() {return "Message{" +"basicProperties=" + basicProperties +", body=" + Arrays.toString(body) +", offsetBeg=" + offsetBeg +", offsetEnd=" + offsetEnd +", isValid=" + isValid +'}';}
}

六. 数据库设计

对于 Exchange, MSGQueue, Binding, 我们使⽤数据库进⾏持久化保存.
此处我们使⽤的数据库是 SQLite, 是⼀个更轻量的数据库.
SQLite 只是⼀个动态库(当然, 官⽅也提供了可执⾏程序 exe), 我们在 Java 中直接引⼊ SQLite 依赖, 即
可直接使⽤, 不必安装其他的软件

配置 sqlite

引⼊ pom.xml 依赖
在这里插入图片描述
配置数据源 application.yml
在这里插入图片描述

Username 和 password 空着即可.
此处我们约定, 把数据库⽂件放到 ./data/meta.db 中.
SQLite 只是把数据单纯的存储到⼀个⽂件中. ⾮常简单⽅便.

实现创建表

@Mapper
public interface MetaMapper {// 提供三个核心建表方法void createExchangeTable();void createQueueTable();void createBindingTable();
}

本⾝ MyBatis 针对 MySQL / Oracle ⽀持执⾏多个 SQL 语句的, 但是针对 SQLite 是不⽀持的, 只能写
成多个⽅法

    <update id="createExchangeTable">create table if not exists exchange (name varchar(50) primary key,type int,durable boolean,autoDelete boolean,arguments varchar(1024));</update><update id="createQueueTable">create table if not exists queue (name varchar(50) primary key,durable boolean,exclusive boolean,autoDelete boolean,arguments varchar(1024));</update><update id="createBindingTable">create table if not exists binding (exchangeName varchar(50),queueName varchar(50),bindingKey varchar(256));</update>

实现数据库基本操作

    // 针对上述三个基本概念, 进行 插入 和 删除void insertExchange(Exchange exchange);List<Exchange> selectAllExchanges();void deleteExchange(String exchangeName);void insertQueue(MSGQueue queue);List<MSGQueue> selectAllQueues();void deleteQueue(String queueName);void insertBinding(Binding binding);List<Binding> selectAllBindings();void deleteBinding(Binding binding);
    <insert id="insertExchange" parameterType="com.example.mq.mqserver.core.Exchange">insert into exchange values(#{name}, #{type}, #{durable}, #{autoDelete}, #{arguments});</insert><select id="selectAllExchanges" resultType="com.example.mq.mqserver.core.Exchange">select * from exchange;</select><delete id="deleteExchange" parameterType="java.lang.String">delete from exchange where name = #{exchangeName};</delete><insert id="insertQueue" parameterType="com.example.mq.mqserver.core.MSGQueue">insert into queue values(#{name}, #{durable}, #{exclusive}, #{autoDelete}, #{arguments});</insert><select id="selectAllQueues" resultType="com.example.mq.mqserver.core.MSGQueue">select * from queue;</select><delete id="deleteQueue" parameterType="java.lang.String">delete from queue where name = #{queueName};</delete><insert id="insertBinding" parameterType="com.example.mq.mqserver.core.Binding">insert into binding values(#{exchangeName}, #{queueName}, #{bindingKey});</insert><select id="selectAllBindings" resultType="com.example.mq.mqserver.core.Binding">select * from binding;</select><delete id="deleteBinding" parameterType="com.example.mq.mqserver.core.Binding">delete from binding where exchangeName = #{exchangeName} and queueName = #{queueName};</delete>

实现 DataBaseManager

package com.example.mq.mqserver.datacenter;import com.example.mq.MqApplication;
import com.example.mq.mqserver.core.Binding;
import com.example.mq.mqserver.core.Exchange;
import com.example.mq.mqserver.core.ExchangeType;
import com.example.mq.mqserver.core.MSGQueue;
import com.example.mq.mqserver.mapper.MetaMapper;
import org.springframework.beans.factory.annotation.Autowired;import java.io.File;
import java.util.List;/** 通过这个类, 来整合上述的数据库操作.*/
public class DataBaseManager {// 要做的是从 Spring 中拿到现成的对象private MetaMapper metaMapper;// 针对数据库进行初始化public void init() {// 手动的获取到 MetaMappermetaMapper = MqApplication.context.getBean(MetaMapper.class);if (!checkDBExists()) {// 数据库不存在, 就进行建建库表操作// 先创建一个 data 目录File dataDir = new File("./data");dataDir.mkdirs();// 创建数据表createTable();// 插入默认数据createDefaultData();System.out.println("[DataBaseManager] 数据库初始化完成!");} else {// 数据库已经存在了, 啥都不必做即可System.out.println("[DataBaseManager] 数据库已经存在!");}}public void deleteDB() {File file = new File("./data/meta.db");boolean ret = file.delete();if (ret) {System.out.println("[DataBaseManager] 删除数据库文件成功!");} else {System.out.println("[DataBaseManager] 删除数据库文件失败!");}File dataDir = new File("./data");// 使用 delete 删除目录的时候, 需要保证目录是空的.ret = dataDir.delete();if (ret) {System.out.println("[DataBaseManager] 删除数据库目录成功!");} else {System.out.println("[DataBaseManager] 删除数据库目录失败!");}}private boolean checkDBExists() {File file = new File("./data/meta.db");if (file.exists()) {return true;}return false;}// 这个方法用来建表.// 建库操作并不需要手动执行. (不需要手动创建 meta.db 文件)// 首次执行这里的数据库操作的时候, 就会自动的创建出 meta.db 文件来 (MyBatis 帮我们完成的)private void createTable() {metaMapper.createExchangeTable();metaMapper.createQueueTable();metaMapper.createBindingTable();System.out.println("[DataBaseManager] 创建表完成!");}// 给数据库表中, 添加默认的数据.// 此处主要是添加一个默认的交换机.// RabbitMQ 里有一个这样的设定: 带有一个 匿名 的交换机, 类型是 DIRECT.private void createDefaultData() {// 构造一个默认的交换机.Exchange exchange = new Exchange();exchange.setName("");exchange.setType(ExchangeType.DIRECT);exchange.setDurable(true);exchange.setAutoDelete(false);metaMapper.insertExchange(exchange);System.out.println("[DataBaseManager] 创建初始数据完成!");}// 把其他的数据库的操作, 也在这个类中封装一下.public void insertExchange(Exchange exchange) {metaMapper.insertExchange(exchange);}public List<Exchange> selectAllExchanges() {return metaMapper.selectAllExchanges();}public void deleteExchange(String exchangeName) {metaMapper.deleteExchange(exchangeName);}public void insertQueue(MSGQueue queue) {metaMapper.insertQueue(queue);}public List<MSGQueue> selectAllQueues() {return metaMapper.selectAllQueues();}public void deleteQueue(String queueName) {metaMapper.deleteQueue(queueName);}public void insertBinding(Binding binding) {metaMapper.insertBinding(binding);}public List<Binding> selectAllBindings() {return metaMapper.selectAllBindings();}public void deleteBinding(Binding binding) {metaMapper.deleteBinding(binding);}
}

测试 DataBaseManager

package com.example.mq;import com.example.mq.mqserver.core.Binding;
import com.example.mq.mqserver.core.Exchange;
import com.example.mq.mqserver.core.ExchangeType;
import com.example.mq.mqserver.core.MSGQueue;
import com.example.mq.mqserver.datacenter.DataBaseManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.context.SpringBootTest;import java.util.List;// 加上这个注解之后, 改类就会被识别为单元测试类.
@SpringBootTest
public class DataBaseManagerTests {private DataBaseManager dataBaseManager = new DataBaseManager();// 接下来下面这里需要编写多个 方法 . 每个方法都是一个/一组单元测试用例.// 还需要做一个准备工作. 需要写两个方法, 分别用于进行 "准备工作" 和 "收尾工作"// 使用这个方法, 来执行准备工作. 每个用例执行前, 都要调用这个方法.@BeforeEachpublic void setUp() {// 由于在 init 中, 需要通过 context 对象拿到 metaMapper 实例的.// 所以就需要先把 context 对象给搞出来.MqApplication.context = SpringApplication.run(MqApplication.class);dataBaseManager.init();}// 使用这个方法, 来执行收尾工作. 每个用例执行后, 都要调用这个方法.@AfterEachpublic void tearDown() {// 这里要进行的操作, 就是把数据库给清空~~ (把数据库文件, meta.db 直接删了就行了)// 注意, 此处不能直接就删除, 而需要先关闭上述 context 对象!!// 此处的 context 对象, 持有了 MetaMapper 的实例, MetaMapper 实例又打开了 meta.db 数据库文件.// 如果 meta.db 被别人打开了, 此时的删除文件操作是不会成功的 (Windows 系统的限制, Linux 则没这个问题).// 另一方面, 获取 context 操作, 会占用 8080 端口. 此处的 close 也是释放 8080.MqApplication.context.close();dataBaseManager.deleteDB();}@Testpublic void testInitTable() {// 由于 init 方法, 已经在上面 setUp 中调用过了. 直接在测试用例代码中, 检查当前的数据库状态即可.// 直接从数据库中查询. 看数据是否符合预期.// 查交换机表, 里面应该有一个数据(匿名的 exchange); 查队列表, 没有数据; 查绑定表, 没有数据.List<Exchange> exchangeList = dataBaseManager.selectAllExchanges();List<MSGQueue> queueList = dataBaseManager.selectAllQueues();List<Binding> bindingList = dataBaseManager.selectAllBindings();// 直接打印结果, 通过肉眼来检查结果, 固然也可以. 但是不优雅, 不方便.// 更好的办法是使用断言.// System.out.println(exchangeList.size());// assertEquals 判定结果是不是相等.// 注意这俩参数的顺序. 虽然比较相等, 谁在前谁在后, 无所谓.// 但是 assertEquals 的形参, 第一个形参叫做 expected (预期的), 第二个形参叫做 actual (实际的)Assertions.assertEquals(1, exchangeList.size());Assertions.assertEquals("", exchangeList.get(0).getName());Assertions.assertEquals(ExchangeType.DIRECT, exchangeList.get(0).getType());Assertions.assertEquals(0, queueList.size());Assertions.assertEquals(0, bindingList.size());}private Exchange createTestExchange(String exchangeName) {Exchange exchange = new Exchange();exchange.setName(exchangeName);exchange.setType(ExchangeType.FANOUT);exchange.setAutoDelete(false);exchange.setDurable(true);exchange.setArguments("aaa", 1);exchange.setArguments("bbb", 2);return exchange;}@Testpublic void testInsertExchange() {// 构造一个 Exchange 对象, 插入到数据库中. 再查询出来, 看结果是否符合预期.Exchange exchange = createTestExchange("testExchange");dataBaseManager.insertExchange(exchange);// 插入完毕之后, 查询结果List<Exchange> exchangeList = dataBaseManager.selectAllExchanges();Assertions.assertEquals(2, exchangeList.size());Exchange newExchange = exchangeList.get(1);Assertions.assertEquals("testExchange", newExchange.getName());Assertions.assertEquals(ExchangeType.FANOUT, newExchange.getType());Assertions.assertEquals(false, newExchange.isAutoDelete());Assertions.assertEquals(true, newExchange.isDurable());Assertions.assertEquals(1, newExchange.getArguments("aaa"));Assertions.assertEquals(2, newExchange.getArguments("bbb"));}@Testpublic void testDeleteExchange() {// 先构造一个交换机, 插入数据库; 然后再按照名字删除即可!Exchange exchange = createTestExchange("testExchange");dataBaseManager.insertExchange(exchange);List<Exchange> exchangeList = dataBaseManager.selectAllExchanges();Assertions.assertEquals(2, exchangeList.size());Assertions.assertEquals("testExchange", exchangeList.get(1).getName());// 进行删除操作dataBaseManager.deleteExchange("testExchange");// 再次查询exchangeList = dataBaseManager.selectAllExchanges();Assertions.assertEquals(1, exchangeList.size());Assertions.assertEquals("", exchangeList.get(0).getName());}private MSGQueue createTestQueue(String queueName) {MSGQueue queue = new MSGQueue();queue.setName(queueName);queue.setDurable(true);queue.setAutoDelete(false);queue.setExclusive(false);queue.setArguments("aaa", 1);queue.setArguments("bbb", 2);return queue;}@Testpublic void testInsertQueue() {MSGQueue queue = createTestQueue("testQueue");dataBaseManager.insertQueue(queue);List<MSGQueue> queueList = dataBaseManager.selectAllQueues();Assertions.assertEquals(1, queueList.size());MSGQueue newQueue = queueList.get(0);Assertions.assertEquals("testQueue", newQueue.getName());Assertions.assertEquals(true, newQueue.isDurable());Assertions.assertEquals(false, newQueue.isAutoDelete());Assertions.assertEquals(false, newQueue.isExclusive());Assertions.assertEquals(1, newQueue.getArguments("aaa"));Assertions.assertEquals(2, newQueue.getArguments("bbb"));}@Testpublic void testDeleteQueue() {MSGQueue queue = createTestQueue("testQueue");dataBaseManager.insertQueue(queue);List<MSGQueue> queueList = dataBaseManager.selectAllQueues();Assertions.assertEquals(1, queueList.size());// 进行删除dataBaseManager.deleteQueue("testQueue");queueList = dataBaseManager.selectAllQueues();Assertions.assertEquals(0, queueList.size());}private Binding createTestBinding(String exchangeName, String queueName) {Binding binding = new Binding();binding.setExchangeName(exchangeName);binding.setQueueName(queueName);binding.setBindingKey("testBindingKey");return binding;}@Testpublic void testInsertBinding() {Binding binding = createTestBinding("testExchange", "testQueue");dataBaseManager.insertBinding(binding);List<Binding> bindingList = dataBaseManager.selectAllBindings();Assertions.assertEquals(1, bindingList.size());Assertions.assertEquals("testExchange", bindingList.get(0).getExchangeName());Assertions.assertEquals("testQueue", bindingList.get(0).getQueueName());Assertions.assertEquals("testBindingKey", bindingList.get(0).getBindingKey());}@Testpublic void testDeleteBinding() {Binding binding = createTestBinding("testExchange", "testQueue");dataBaseManager.insertBinding(binding);List<Binding> bindingList = dataBaseManager.selectAllBindings();Assertions.assertEquals(1, bindingList.size());// 删除Binding toDeleteBinding = createTestBinding("testExchange", "testQueue");dataBaseManager.deleteBinding(toDeleteBinding);bindingList = dataBaseManager.selectAllBindings();Assertions.assertEquals(0, bindingList.size());}
}

相关文章:

✔ ★Java项目——设计一个消息队列(二)

Java项目——设计一个消息队列 四. 项⽬创建五. 创建核⼼类创建 Exchange&#xff08;名字、类型、持久化&#xff09;创建 MSGQueue&#xff08;名字、持久化、独占标识&#xff09;创建 Binding&#xff08;交换机名字、队列名字、bindingKey用于与routingKey匹配&#xff09…...

Java语言实现生产者/消费者问题

经典例题&#xff1a;生产者/消费者问题 生产者(Productor)将产品放在柜台(Counter)&#xff0c;而消费者(Customer)从柜台 处取走产品&#xff0c;生产者一次只能生产固定数量的产品(比如:1&#xff09;&#xff0c; 这时柜台中不能 再放产品,此时生产者应停止生产等待消费者…...

bugku-web-file_get_contents

<?php extract($_GET); if (!empty($ac)){$f trim(file_get_contents($fn));if ($ac $f){echo "<p>This is flag:" ." $flag</p>";}else{echo "<p>sorry!</p>";} } ?> 这里涉及到几个不常用的函数 这里直接构…...

Python数据处理和常用库(如NumPy、Pandas)

Python是一种功能强大的编程语言&#xff0c;广泛应用于数据处理和分析领域。在Python中&#xff0c;有一些常用的库可以帮助我们进行数据处理和分析&#xff0c;其中包括NumPy和Pandas。下面是关于这两个库的简介和使用示例&#xff1a;NumPy&#xff08;Numerical Python&…...

[SystemVerilog]Simulation and Test Benches

Simulation and Test Benches 测试语言中有很大一部分专门用于测试台和测试。在本章中&#xff0c;我们将介绍为硬件设计编写高效测试台的一些常用技术。 6.1 How SystemVerilog Simulator Works 在深入研究如何编写适当的测试台之前&#xff0c;我们需要深入了解模拟器的工作原…...

lightgbm-安装失败(解决方案)

1.pip install lightgbm 报错&#xff0c;出现长篇标黄和标红的&#xff0c;本人表示看不懂&#xff0c;直接忽略&#xff0c;如下所示&#xff1a; 2.尝试pip install lightgbm -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com&#xff0c;安装也报错&…...

halcon图像相减算子sub_image

1.图像相减算子 sub_image(ImageMinuend , ImageSubtrahend : ImageSub : Mult , Add :) &#xff08;1&#xff09;参数解释&#xff1a; ImageMinuend &#xff1a;输入参数需要被减的图片 ImageSubtrahend &#xff1a;输入参数拿来减的图片 ImageSub &#xff1a;输出…...

final、finally 和 finalize 有什么区别?

final 是一个关键字&#xff0c;用于声明一个类、方法或变量。当用 final 修饰一个类时&#xff0c;表示该类不能被继承&#xff1b;当用 final 修饰一个方法时&#xff0c;表示该方法不能被子类重写&#xff1b;当用 final 修饰一个变量时&#xff0c;表示该变量只能被赋值一次…...

智能运维场景 | 科技风险预警,能实现到什么程度?

[ 原作者&#xff1a;擎创夏洛克&#xff0c;本文略做了节选和改编 ] 每次一说到“风险预警”&#xff0c;就会有客户问我们能做怎样的风险预警。实际上在智能运维厂商来说&#xff0c;此风险非彼风险&#xff0c;不是能做银行的业务上的风险预警&#xff08;比如贷款风险等&a…...

中颖51芯片学习3. 定时器

中颖51芯片学习3. 定时器 一、SH79F9476定时器简介1. 简介2. 定时器运行模式 二、定时器21. 说明&#xff08;1&#xff09;时钟&#xff08;2&#xff09;工作模式 2. 寄存器&#xff08;1&#xff09;控制寄存器 T2CON&#xff08;2&#xff09;定时器2模式控制寄存器 T2MOD …...

[python] Numpy库用法(持续更新)

先导入一下 import numpy as np 一、np.random用法 生成随机整数&#xff1a;np.random.randint(low, high, size) low: 最小值high: 最大值size: 生成的数组大小&#xff08;可以是多维&#xff0c;下面同理&#xff09; 生成随机浮点数&#xff1a;np.random.uniform(low, …...

vue快速入门(十七)v-model数据双向绑定修饰符

注释很详细&#xff0c;直接上代码 上一篇 新增内容 v-model.trim 自动去除首尾空格v-model.number 自动转换成数字类型 源码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" con…...

2024-2025年申报各类科研项目基金撰写及技巧

随着社会经济发展和科技进步&#xff0c;基金项目对创新性的要求越来越高。申请人需要提出独特且有前瞻性的研究问题&#xff0c;具备突破性的科学思路和方法。因此&#xff0c;基金项目申请往往需要进行跨学科的技术融合。申请人需要与不同领域结合&#xff0c;形成多学科交叉…...

Python基于Django的微博热搜、微博舆论可视化系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…...

【Linux学习】初识Linux指令(一)

文章目录 1.指令操作与图形化界面操作1.什么是指令操作&#xff0c;什么是图形化界面操作&#xff1f; 2.Linux下基本指令1.Linux下的复制粘贴2.Linux两个who命令3.补充知识4.pwd指令5. ls 指令6.cd 指令1.目录树2.相对路径与绝对路劲3.常用cd指令 7.tree指令8. touch指令9.sta…...

基于Python实现盈利8371%的交易策略

本文介绍了通过Python和Benzinga API构建自动化交易策略的方法&#xff0c;帮助交易者方便的回测交易策略。原文: An Algo Trading Strategy which made 8,371%: A Python Case Study Behnam Norouzi Unsplash 导言 传统自动化交易策略(如均线交叉或 RSI 临界点突破策略)已被证…...

如何在Linux中找到正在运行的Java应用的JAR文件

当你在Linux服务器上工作时&#xff0c;可能需要找到某个正在运行的Java应用的JAR文件位置。这对于诊断问题、更新应用或理解部署结构非常有用。以下是一个步骤详细的指南&#xff0c;帮助你找到这些信息。 1. 确定Java进程 首先&#xff0c;你需要确定正在运行的Java应用的进…...

几分钟学会TypeScript

目录 一、类型推断和类型注解二.类型注解&#xff0c;声明时指定类型三、类型断言四、基础类型和联合类型字符串数字和浮点类型布尔空和undefined多类型值限定 五、数组 元组 枚举数组元组,?代表可选参数枚举枚举使用 六、函数函数作为参数 七、类、接口与抽象类类访问修饰符类…...

最新版手机软件App下载排行网站源码/App应用商店源码

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 一款简洁蓝色的手机软件应用app下载排行&#xff0c;app下载平台&#xff0c;最新手机app发布网站响应式织梦模板。 主要有&#xff1a;主页、app列表页、app介绍详情页、新闻资讯列…...

R语言计算:t分布及t检验

t分布理论基础 t分布也称Student’s t-distribution&#xff0c;主要出现在小样本统计推断中&#xff0c;特别是当样本量较小且总体标准差未知时&#xff0c;用于估计正态分布的均值。其定义基于正态分布和 X 2 X^{2} X2分布&#xff08;卡方分布&#xff09;。如果随机变量X服…...

uni-app的地图定位与距离测算功能的实现

文章目录 一、引言二、uni-app地图定位实现三、距离测算技术四、完整代码五、结论本文着重探讨了如何在uni-app中实现地图定位,以及如何计算当前定位与目标位置之间的距离。 一、引言 在移动应用开发中,地图定位与距离测算是常见的功能需求。无论是出行导航、位置签到,还是…...

如何从应用商店Microsoft Store免费下载安装HEVC视频扩展插件

在电脑上打开一张HEIC类型的图片提示缺少HEVC解码器&#xff0c;无法打开查看&#xff0c;现象如下&#xff1a; 这种情况一般会提示我们需要下载安装HEVC解码器&#xff0c;点击“立即下载并安装”会跳转到应用商店&#xff0c;但是我们发现需要付费7元才能下载安装 免费安装…...

【vue】v-if 条件渲染

v-if 不适用于频繁切换显示模式的场景 修改web.user&#xff0c;可看到条件渲染的效果 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initi…...

Day37:LeedCode 738.单调递增的数字 968.监控二叉树 蓝桥杯 翻转

738. 单调递增的数字 当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。 给定一个整数 n &#xff0c;返回 小于或等于 n 的最大数字&#xff0c;且数字呈 单调递增 。 示例 1: 输入: n 10 输出: 9 思路: 假设这个数是98,…...

详解Qt元对象系统

Qt库作为一款流行的跨平台C应用程序开发框架&#xff0c;其中的元对象系统是其核心特性之一。Qt元对象系统不仅提供了诸如信号槽&#xff08;Signals & Slots&#xff09;、属性系统&#xff08;Property System&#xff09;等功能&#xff0c;还实现了对C对象的运行时类型…...

无法用raven-js,如何直接使用TraceKit标准化错误字符串(一次有趣的探索)

引子&#xff1a;网上三年前&#xff08;2020&#xff09;的文章介绍了一个raven-js 简单说就是把堆栈信息格式化兼容各浏览器&#xff0c;便于查看错误来源。 **but&#xff1a;**到处找了一下raven-js&#xff0c;已经没有官方出处了&#xff0c;只在Sentry的源码仓库里发现…...

Docker学习笔记(二):在Linux中部署Docker(Centos7下安装docker、环境配置,以及镜像简单使用)

一、前言 记录时间 [2024-4-6] 前置文章&#xff1a;Docker学习笔记&#xff08;一&#xff09;&#xff1a;入门篇&#xff0c;Docker概述、基本组成等&#xff0c;对Docker有一个初步的认识 在上文中&#xff0c;笔者进行了Docker概述&#xff0c;介绍其历史、优势、作用&am…...

uniapp 检查更新

概览 在uniapp中检查并更新应用&#xff0c;可以使用uni-app自带的更新机制。以下是一个简单的示例代码&#xff0c;用于在应用启动时检查更新&#xff1a; // 在App.vue或者其他合适的地方调用 onLaunch: function() {// 当uni-app初始化完成时执行// 判断平台const platfor…...

(Java)数据结构——正则表达式

前言 本博客是博主用于复习数据结构以及算法的博客&#xff0c;如果疏忽出现错误&#xff0c;还望各位指正。 正则表达式概念 正则表达式&#xff0c;又称规则表达式&#xff08;Regular Expression&#xff09;&#xff0c;是一种文本模式&#xff0c;包括普通字符&#xf…...

第6章 6.3.1 正则表达式的语法(MATLAB入门课程)

讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 正则表达式可以由一般的字符、转义字符、元字符、限定符等元素组…...