手寫快速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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章