手写快速Web开发框架--集成RabbitMQ

#最近难得有空,然后试着集成了下RabbitMQ

###完整代码在这里 https://github.com/zhangjunapk/WinterBatis

先看下效果,访问这个路径能发送消息,然后消费者能消费消息

 

 

 

##首先定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Rabbit {
    String host();
    int port();
    String username();
    String password();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RabbitListener {
    String[] queue()default "";
    String exchangeName()default "";
    String routeKey()default "";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface RabbitProducter {
    String routeKey();
    String[] queueName();
    String exchangeName();
}

 


#我们先来看看消费者

我们来看看我们要对什么进行处理

@Component
public class Customer {
    /**
     * 监听studentQueue队列的消息,并将每个消息转换成对象来处理
     * @param student
     */
    @RabbitListener(routeKey = "test.*",exchangeName = "test",queue = "test.student")
    public void handleMessage(Student student){
        System.out.println("收到对象信息");
        System.out.println(student);
    }

    @RabbitListener(routeKey = "test.*",exchangeName = "test",queue = "test.student")
    public void handleMessagee(String student){
        System.out.println("收到字符串信息");
        System.out.println(student);
    }

}

 

#### 也就是说我们会得到这样的类,然后得到上面的注解,然后再监听队列

 

##接下来就是对这种类的处理

//rabbitmq消费者的监听处理
    private void instanceCustomer() throws IOException, TimeoutException, InstantiationException, IllegalAccessException {
        for (Class c : classes) {
            if(!c.isAnnotationPresent(Component.class))
                continue;
            for(Method m:c.getDeclaredMethods()) {
                if(!m.isAnnotationPresent(RabbitListener.class))
                    continue;
                RabbitListener annotation = m.getAnnotation(RabbitListener.class);
                RabbitMQCustomerClassHandler rabbitMQCustomerClassHandler=new RabbitMQCustomerClassHandler(annotation);
                rabbitMQCustomerClassHandler.handleClass(c);
            }
        }
    }

####因为每次得到的方法上面要监听的队列都不同,所有每次遍历到一个方法都要创建一个handler

 

##接下来看看消费者怎么来监听

 

因为我们是要对队列进行监听,这里我们就需要一些参数

* host(从配置文件获得)

* 端口(从配置文件获得)

* 队列名 (从方法上的注解获得)

* routeKey (从方法上的注解获得)

* 交换机(从方法上的注解获得)

 

##上面有的参数从配置文件得到,有的从注解中得到(通过构造方法来得到要监听的队列等信息)

先看一下抽象的ClassHandler

这里我用了模板模式,把不变的部分写死,变得部分用抽象方法来交给子类来实现

public abstract class AbsClassHandler {
    private static Properties properties=new Properties();
   public abstract void handleClass(Class c) throws Exception;
    static{
        try {
            
            properties.load(new FileInputStream(ClassUtil.getClassPath()+"\\application.properties"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Properties getProperties(){
        return properties;
    }
}

不变的部分就是配置文件,不同的部分就是对类的处理


##然后看看消费者的类handler

我们需要监听队列,上面所说的信息肯定不可少

 

private ConnectionFactory factory;
    private Connection connection;
    private Channel channel;
    private Consumer consumer;

    private Object instance;

    private String[] queue;

 

然后这些参数可以从构造方法中接收

 

public RabbitMQCustomerClassHandler(RabbitListener rabbitListener) throws IOException, TimeoutException {
        factory=new ConnectionFactory();
        factory.setHost(getHost());
        factory.setPort(ValUtil.parseInteger(getPort()));
        factory.setUsername(getUsername());
        factory.setPassword(getPassword());
        connection=factory.newConnection();
        channel=connection.createChannel();

        queue = rabbitListener.queue();
        String exchangeName = rabbitListener.exchangeName();
        String routeKey = rabbitListener.routeKey();

        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,true);
        for(String q:queue){
            channel.queueBind(q,exchangeName,routeKey);
        }
    }

接收到注解信息后,就配置链接工厂,然后声明这个方法是监听哪个交换机的,然后为每个队列进行绑定

 

##接下来就是处理消息的监听了

@Override
    public void handleClass(Class c) throws IOException, IllegalAccessException, InstantiationException {

        if(instance==null){
            instance=c.newInstance();
        }

        if(!c.isAnnotationPresent(Component.class))
            return;
        for(java.lang.reflect.Method m:c.getDeclaredMethods()){
            if(!m.isAnnotationPresent(RabbitListener.class))
                continue;

            //接下来开启监听

            Consumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                           byte[] body) throws IOException {

                    try {
                        Class generic = MethodUtil.getParamType(0,m);
                        if(!(generic==String.class)){
                            //调用json来讲对象进行转换,然后执行m方法

                            try {
                                Object o = new ObjectMapper().readValue(new String(body, "UTF-8"), generic);
                                m.invoke(instance, o);

                                System.out.println("我收到了对象的消息");
                                return;
                            }catch (Exception e){

                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    String message = new String(body, "UTF-8");

                    System.out.println("你知道吗 我收到字符串消息了");

                    System.out.println(" [x] Received '" + message + "'");

                    try {
                        m.invoke(instance,message);



                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            };
            for(String q:queue){
                channel.basicConsume(q, true, consumer);
            }
        }
    }

首先创建一个消费者,用于处理消息,这个消费者

首先,我们要得到消费者的方法里接收的参数类型,调用了MethodUtil.getParamType()方法来获得类型

我们来看看这个方法做了什么,就这样,很简单

 public static Class getParamType(int index, Method method){
        Class<?>[] parameterTypes = method.getParameterTypes();
        return parameterTypes[0];
    }

通过这个方法来获得消费者的方法里参数的类型用于转换,

为什么要进行转换呢,这里生产者我让他在发送对象的时候把对象序列化成json了,消费者进行消费的时候就需要进行反序列化

反序列化后,直接反射执行消费者的逻辑

 

 

#接下来看生产者

package org.zj.winterbatis.controller;

import org.zj.winterbatis.annotation.*;
import org.zj.winterbatis.bean.Student;
import org.zj.winterbatis.core.RabbitMQProducter;

@Controller
@RequestMapping("/mq")
public class MQController {

    @Autofired
    @RabbitProducter(routeKey = "test.demo.*",queueName = {"test.student","test.teacher"},exchangeName = "test")
    RabbitMQProducter rabbitMQProducter;

    @RequestMapping("/tt")
    @ResponceBody
    public void test(){
        rabbitMQProducter.sendStringMessage("send string");
        rabbitMQProducter.sendObjectMessage(new Student("zhangjun","555"));
    }
}

我们的生产者就是上面那个rabbitMQProducter;

注解上有routeKey,队列数组,交换机名

这个生产者是我定义的接口,正常情况下需要实现类才能用,这里我们通过jdk的动态代理来生成代理类,然后给这个Controller注入进去,就能使用了

package org.zj.winterbatis.core;

public interface RabbitMQProducter {

    /**
     * 发送消息的方法
     */
    //直接发送
    void sendStringMessage(String msg);

    //序列化成json然后发送
    void sendObjectMessage(Object obj);
}

 

jdk的动态代理生成代理类需要一个类加载器,接口,以及一个InvocationHandler,Proxy.newInstance()方法会根据这些参数生成class文件并实例化,将代理类返回给调用者

接下来我们就看看我实现的这个InvocationHandler做了什么

首先需要连接,我们肯定需要这些参数

    private String host;
    private int port;
    private String username;
    private String password;

    private RabbitProducter rabbitProducter;
    private Rabbit rabbit;
    private String routeKey;
    private String[] queueName;
    private String exchangeName;

    private ConnectionFactory factory;
    private Connection connection;
    private Channel channel;
    private AMQP.BasicProperties bp;
    private boolean inited=false;

####然后通过两个注解将参数传过来

 

public RabbitMQProducterInvocationHandler(Rabbit rabbit, RabbitProducter rabbitProducter){
        this.rabbit=rabbit;
        this.rabbitProducter=rabbitProducter;
        this.host=rabbit.host();
        this.port=rabbit.port();
        this.username=rabbit.username();
        this.password=rabbit.password();

        this.routeKey=rabbitProducter.routeKey();
        this.queueName=rabbitProducter.queueName();
        this.exchangeName=rabbitProducter.exchangeName();
    }

 

####接下来就是核心的代码,调用发送字符串或者发送对象的时候会执行

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if(!inited)
            init();

        //直接发送String
        if(method.getName().equals("sendStringMessage")){
            System.out.println("我发送了字符串的消息");
            channel.basicPublish(exchangeName,routeKey,false,bp, ValUtil.parseString(args[0]).getBytes());
        }
        //转换成json后再发送
        if(method.getName().equals("sendObjectMessage")){
            System.out.println("我发送了对象的消息");
            channel.basicPublish(exchangeName,routeKey,false,bp, new ObjectMapper().writeValueAsString(args[0]).getBytes());
        }

        return null;
    }

这里我做了一个判断,如果没有初始化过就先初始化,然后判断调用的是哪个方法,如果调用了发送对象,就把传递进来的参数进行序列化成json然后再通过channel发送,然后消费者就在管道一直监听,收到消息会执行相应的逻辑

 

下面是初始化的方法

/**
     * 对生产者进行初始化
     */
    private void init() throws IOException, TimeoutException {
        this.host=rabbit.host();
        this.port=rabbit.port();
        this.username=rabbit.username();
        this.password=rabbit.password();
        this.queueName=rabbitProducter.queueName();
        this.routeKey=rabbitProducter.routeKey();
        this.exchangeName=rabbitProducter.exchangeName();


        factory=new ConnectionFactory();
        factory.setHost(host);
        factory.setPort(port);
        factory.setUsername(username);
        factory.setPassword(password);
        connection=factory.newConnection();
        channel=connection.createChannel();
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,true);

        for(String queue:queueName){
            channel.queueBind(queue,exchangeName,routeKey);
        }
        Map<String,Object> header=new HashMap<>();
        header.put("charset","utf-8");
        AMQP.BasicProperties.Builder b=new AMQP.BasicProperties.Builder();
        b.headers(header);
         bp=b.build();
    }

声明交换机,绑定队列,然后设置消息头,不然收到消息会乱码

##ok了,我先睡会儿然后晚上整另一个有意思的东西(#滑稽)

#忘了,完整代码在这里 https://github.com/zhangjunapk/WinterBatis

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值