Spring Boot集成rabbitmq使用设计模式根据不同业务类型高级封装

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注解的方法处理,实现了监听队列和处理业务的逻辑解耦,避免了相关问题的出现

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值