1、pom
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.1.0</version>
</dependency>
2、Mq账号密码配置类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix="mq")
@Data
@Component
public class MqConfig {
private String host="127.0.0.1";
private Integer port=5672;
private String userName="guest";
private String password="guest";
}
3、发送Mq时一些必要信息的接口
public interface MqRequest {
String url();
Class<?> clazz();
default String exchangeName(){
return "exchange_demo";
}
default String routingKey(){
return "routingkey_demo";
}
default String queueName(){
return "queue_demo";
}
}
3、通过枚举定义具体发送信息的内容,包括url,以及传送的Class类对象,该枚举只重写了url和clazz()方法,根据具体的业务需求枚举里面可以重写queueName,exchangeName以及routingKey等信息
import org.example.common.interfaces.MqRequest;
import org.example.entity.User;
public enum MqEnum implements MqRequest {
COMMON("/user/test2","测试方法2", User.class)
;
private String url;
private String desc;
private Class<?> clazz;
MqEnum(String url, String desc, Class<?> clazz) {
this.url = url;
this.desc = desc;
this.clazz = clazz;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public Class<?> getClazz() {
return clazz;
}
public void setClazz(Class<?> clazz) {
this.clazz = clazz;
}
@Override
public String url() {
return url;
}
@Override
public Class<?> clazz() {
return clazz;
}
}
4、具体传送数据时总的信息类
import lombok.Data;
import java.io.Serializable;
@Data
public class MqMessage<T> implements Serializable {
protected String url;
protected T data;
}
5、具体发送Mq消息的工具类
import com.alibaba.fastjson.JSON;
import lombok.SneakyThrows;
import org.example.common.config.MqConfig;
import org.example.common.interfaces.MqRequest;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import org.example.common.model.MqMessage;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class MqUtil {
@SneakyThrows
public static <T> void sendMq(MqRequest mqRequest,T t){
Class<?> aClass= mqRequest.clazz();
if(!aClass.getName().equals(t.getClass().getName())){
throw new IllegalArgumentException("数据类型不匹配");
}
// 创建连接
Connection connection = getConnection();
// 创建信道
Channel channel = connection.createChannel();
// 初始化测试用的 Exchange 和 Queue
initExchangeAndQueue(channel,mqRequest);
MqMessage<T> message=new MqMessage<>();
message.setUrl(mqRequest.url());
message.setData(t);
String json=JSON.toJSONString(message);
channel.basicPublish(mqRequest.exchangeName(), mqRequest.routingKey(), MessageProperties.PERSISTENT_TEXT_PLAIN, json.getBytes());
// 关闭
channel.close();
connection.close();
}
// 创建 RabbitMQ Exchange 和 Queue ,然后使用 ROUTING_KEY 路由键将两者绑定。
// 该步骤,其实可以在 RabbitMQ Management 上操作,并不一定需要在代码中
private static void initExchangeAndQueue(Channel channel,MqRequest mqRequest) throws IOException {
// 创建交换器:direct、持久化、不自动删除
channel.exchangeDeclare(mqRequest.exchangeName(), BuiltinExchangeType.DIRECT, true, false, null);
// 创建队列:持久化、非排他、非自动删除的队列
channel.queueDeclare(mqRequest.queueName(), true, false, false, null);
// 将交换器与队列通过路由键绑定
channel.queueBind(mqRequest.queueName(), mqRequest.exchangeName(), mqRequest.routingKey());
}
public static Connection getConnection() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
MqConfig mqConfig=SpringUtils.getBean(MqConfig.class);
factory.setHost(mqConfig.getHost());
factory.setPort(mqConfig.getPort());
factory.setUsername(mqConfig.getUserName());
factory.setPassword(mqConfig.getPassword());
return factory.newConnection();
}
}
6、发送Mq消息的测试方法,发送消息时url相当于具体的方法,消费者可以根据不同的url执行不同的消费类处理,
@Test
public void test5() {
User user = new User();
user.setId(14L);
user.setName("喵喵喵");
MqUtil.sendMq(MqEnum.COMMON, user);
}
7、消费者处理则使用类似@RestController的注解方式根据不同的url处理不同的业务,此处定义两个注解
package org.example.common.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface MqController {
String value();
}
package org.example.common.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MqMapping {
String value();
}
8、使用BeanPostProcessor找到添加@MqController和@MqMapping 所有注解的方法信息和类信息
package org.example.common.config;
import org.example.common.annotation.MqController;
import org.example.common.annotation.MqMapping;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
private static final Map<String, Method> methodMap = new ConcurrentHashMap<>();
private static final Map<String, Class<?>> classMap = new ConcurrentHashMap<>();
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class<?> clazz = bean.getClass();
MqController mqController = clazz.getAnnotation(MqController.class);
if (mqController != null) {
for (Method method : clazz.getMethods()) {
MqMapping mqMapping = method.getAnnotation(MqMapping.class);
if (mqMapping != null) {
String key = mqController.value() + mqMapping.value();
if (methodMap.containsKey(key)) {
throw new RuntimeException("路径重复");
}
methodMap.put(key, method);
classMap.put(key, clazz);
}
}
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public static Class<?> getClass(String url) {
Class<?> clazz=classMap.get(url);
if(clazz==null){
throw new RuntimeException("参数类型错误");
}
return clazz;
}
public static Method getMethod(String url) {
Method method=methodMap.get(url);
if(method==null){
throw new RuntimeException("参数类型错误");
}
return method;
}
}
9、添加工具类根据url和json字符串直接执行 @MqMapping注解的方法并拿到结果
package org.example.common.handler.impl;
import com.alibaba.fastjson.JSONObject;
import lombok.SneakyThrows;
import org.example.common.config.MyBeanPostProcessor;
import org.example.common.util.SpringUtils;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class ConsumerHandler {
@SneakyThrows
public static Object exec(String url, String json) {
Class<?> c = MyBeanPostProcessor.getClass(url);
Method method=MyBeanPostProcessor.getMethod(url);
Object o= SpringUtils.getBean(c);
Class<?>[] params=method.getParameterTypes();
List<Object> list=new ArrayList<>();
for(Class<?> cm:params){
Object param=JSONObject.parseObject(json,cm);
list.add(param);
}
return method.invoke(o,list.toArray());
}
}
7、抽象的消费类处理
package org.example.common.command;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.example.common.config.MqConfig;
import org.example.common.handler.impl.ConsumerHandler;
import org.example.common.model.MqMessage;
import org.example.common.util.MqUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import java.io.IOException;
/**
* 抽象消费者监听队列,监听某一个队列的消息
*/
@Slf4j
public abstract class AbstractMqConsumer implements CommandLineRunner {
@Autowired
private MqConfig mqConfig;
public abstract String getQueueName();
@SneakyThrows
public void process(Channel channel, Envelope envelope, String json) {
MqMessage<?> message = JSONObject.parseObject(json, MqMessage.class);
log.info("消息转化后的内容:" + JSON.toJSONString(message));
ConsumerHandler.exec(message.getUrl(), JSON.toJSONString(message.getData()));
// ack 消息已经消费
channel.basicAck(envelope.getDeliveryTag(), false);
}
@Override
public void run(String... args) throws Exception {
log.info("成功创建mq消费者:" + this.getClass().getName() + " 监听队列名称:" + getQueueName());
// 创建连接
Connection connection = MqUtil.getConnection();
// 创建信道
final Channel channel = connection.createChannel();
channel.basicQos(64); // 设置客户端最多接收未被 ack 的消息数量为 64 。
// 创建消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body);
log.info("收到" + getQueueName() + "消息,路由健值:" + envelope.getRoutingKey() + " 消息内容:" + message);
process(channel, envelope, message);
}
};
// 订阅消费 QUEUE_NAME 队列
channel.basicConsume(getQueueName(), consumer);
}
}
8、具体的消费类,推荐一个系统只用一个消费类,然后根据不同的url处理不同的内容
package org.example.common.command;
import org.springframework.stereotype.Component;
@Component
public class CommonMqConsumer extends AbstractMqConsumer{
@Override
public String getQueueName() {
return "queue_demo";
}
}
9、具体的业务处理类
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.example.common.annotation.MqController;
import org.example.common.annotation.MqMapping;
import org.example.entity.User;
@MqController("/user")
@Slf4j
public class Consumer1 {
@MqMapping("/test1")
public User test1(User user){
log.info("test1:"+ JSON.toJSONString(user));
return user;
}
@MqMapping("/test2")
public User test2(User user){
log.info("test2:"+ JSON.toJSONString(user));
return user;
}
}
总结,消费者只需要重写getQueueName方法根据业务需求监听不同队列的消息内容即可,具体处理过程由@MqMapping注解的方法处理,实现了监听队列和处理业务的逻辑解耦,避免了相关问题的出现