#最近难得有空,然后试着集成了下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