#最近難得有空,然後試着集成了下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