Rocket MQ更優雅的實現消息發送與消息監聽—基於Spring註解實現消息監聽

如果項目使用Spring boot集成Rocket MQ可以使用RocketMQTemplate,代碼看起來很簡潔,但是如果項目只使用了spring,就要自己手寫一堆代碼去實現消息發送與消息監聽

下面就參考RocketMQTemplate自己實現一個基於註解的消息監聽框架

先看一下RocketMQTemplate是怎麼實現註解方式註冊消息監聽的

@RocketMQMessageListener(
        topic = "test_topic", 					//topic:和消費者發送的topic相同
        consumerGroup = "test_my-consumer",     //group:不用和生產者group相同
        selectorExpression = "*") 			    //tag
@Component  //必須注入spring容器
//泛型必須和接收的消息類型相同
public class TestListner implements RocketMQListener<User> {

    @Override
    public void onMessage(User user) {
        System.out.println(user);
    }
}

接下來我們就自己實現@RocketMQMessageListener,我們爲了區別就將註解命名爲@RocketMqMsgListener

思路:

1.定義@RocketMqMsgListener

2.項目啓動後掃描所有@RocketMqMsgListener註解的類

3.根據註解中的參數自動創建消費者

4.服務停止時自動停止消費者

 

1.定義@RocketMqMsgListener

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RocketMqMsgListener {

	/**
	 * 訂閱主題
	 * @return
	 */
	String topic();
	
	/**
	 * 消費者組
	 * @return
	 */
	String consumerGroup();
	
	/**
	 * tag,訂閱子查詢條件,默認獲取所有
	 * @return
	 */
	String selectorExpression() default "*";
	
	/**
	 * 查詢類型 SelectorType.TAG根據tag查詢   SelectorType.SQL92根據sql查詢
	 * @return
	 */
	SelectorType selectorType() default SelectorType.TAG;
	
	/**
	 * 控制消息模式,如果希望所有訂閱者都接收消息全部消息,廣播是一個不錯的選擇。如果希望一個消費者接收則使用負載均衡模式
	 * @return
	 */
	MessageModel messageModel() default MessageModel.CLUSTERING;
    
    /**
     * 實例名稱
     * @return
     */
    String instanceName() default "";
}

2.項目啓動後掃描所有@RocketMqMsgListener註解的類

3.根據註解中的參數自動創建消費者

使用spring自帶掃描,類實現ApplicationListener<ContextRefreshedEvent>會在容器啓動後自動執行其中的onApplicationEvent(ContextRefreshedEvent event)方法

public class MqMsgLoadListener implements ApplicationListener<ContextRefreshedEvent>, DisposableBean {
	//掃描的類
	private Map<String, Object> beanMap = new HashMap<>();
	//消費者列表
	private List<SimpleConsumer> consumerList = new ArrayList<>();
	@Autowired
	private RocketConfig config;

	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		//如果是第二次加載則不處理了
		if(event.getApplicationContext().getParent() != null) {
			return;
		}
		
		//獲取註解類
        beanMap = event.getApplicationContext().getBeansWithAnnotation(RocketMqMsgListener.class);
        System.out.println("================"+beanMap.size());
        //未掃描到不處理
        if(beanMap == null || beanMap.size() == 0) {
        	return;
        }
        for(Object bean : beanMap.values()) {
        	createConsumer(bean);
        }
	}

	/**
	 * 創建消費者
	 * @param bean
	 */
	private void createConsumer(Object bean) {
		if(!(bean instanceof MessageListenerConcurrently)) {
			return;
		}
		//獲取註解
		RocketMqMsgListener mqMsgListener = bean.getClass().getAnnotation(RocketMqMsgListener.class);
		//配置
		ConsumerConfig consumerConfig = new ConsumerConfig();
		consumerConfig.setConsumeFromWhere(mqMsgListener.consumeFromWhere());
		consumerConfig.setTopic(mqMsgListener.topic());
		consumerConfig.setConsumerGroup(mqMsgListener.consumerGroup());
		consumerConfig.setSelectorExpression(mqMsgListener.selectorExpression());
		consumerConfig.setSelectorType(mqMsgListener.selectorType());
		consumerConfig.setMessageModel(mqMsgListener.messageModel());
		consumerConfig.setConsumeMode(mqMsgListener.consumeMode());
		if(!StringUtils.isEmpty(mqMsgListener.instanceName())) {
			consumerConfig.setInstanceName(mqMsgListener.instanceName());
		}
		//創建消費者
		SimpleConsumer consumer = new SimpleConsumer(config, consumerConfig, (MessageListenerConcurrently)bean);
		consumerList.add(consumer);
		//初始化並啓動
		try {
			consumer.init();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 服務停止
	 */
	@Override
	public void destroy() throws Exception {
		for(SimpleConsumer consumer : consumerList) {
			if(consumer == null) {
				continue;
			}
			consumer.destroy();
		}
	}
}

xml中配置


	<!-- 啓動監聽 -->
	<bean id="mqMsgLoadListener" class="com.tes.rocket.annotation.listener.MqMsgLoadListener" />

4.服務停止時自動停止消費者

實現DisposableBean接口,服務停止時會自動調用destroy()方法,在destroy()釋放消費者資源

/**
	 * 服務停止
	 */
	@Override
	public void destroy() throws Exception {
		for(SimpleConsumer consumer : consumerList) {
			if(consumer == null) {
				continue;
			}
			consumer.destroy();
		}
	}

 

使用註解

@Component
@RocketMqMsgListener(topic = "test_topic", consumerGroup = "rocketmq-test")
public class StringMessageListener implements MessageListenerConcurrently {
	private int total;

	@Override
	public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
		setTotal();
		for (MessageExt msg : msgs) {
			System.out.println("========收到消息:"+new String(msg.getBody())+"---------總數:"+total);
		}
		return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
	}
	
	public synchronized void setTotal() {
		total++;
	}

	public int getTotal(){
		return total;
	}
}

 

完整代碼

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd 
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context.xsd ">
	
	<!-- rocket mq配置 -->
	<bean id="rocketMQConfig" class="com.test.config.RocketConfig">
		<constructor-arg name="address" value="192.168.1.113:9876"/>
	</bean>
	
	<!-- 默認生產者 -->
	<bean id="producer" class="com.test.rocket.producer.SimpleProducer" init-method="init" destroy-method="destroy">
		<constructor-arg name="producerGroup" value="rocketmq-test" />
		<constructor-arg name="config" ref="rocketMQConfig"/>
	</bean>
	
	<!-- 掃描包 -->
	<context:component-scan base-package="com.test.**.mq.**" />
	<!-- 啓動監聽 -->
	<bean id="mqMsgLoadListener" class="com.test.rocket.annotation.listener.MqMsgLoadListener" />
</beans>

RocketMqMsgListener

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RocketMqMsgListener {

	/**
	 * 訂閱主題
	 * @return
	 */
	String topic();
	
	/**
	 * 消費者組
	 * @return
	 */
	String consumerGroup();
	
	/**
	 * tag,訂閱子查詢條件,默認獲取所有
	 * @return
	 */
	String selectorExpression() default "*";
	
	/**
	 * 查詢類型 SelectorType.TAG根據tag查詢   SelectorType.SQL92根據sql查詢
	 * @return
	 */
	SelectorType selectorType() default SelectorType.TAG;
	
	/**
	 * 控制消息模式,如果希望所有訂閱者都接收消息全部消息,廣播是一個不錯的選擇。如果希望一個消費者接收則使用負載均衡模式
	 * @return
	 */
	MessageModel messageModel() default MessageModel.CLUSTERING;
    
    /**
     * 實例名稱
     * @return
     */
    String instanceName() default "";
}

RocketConfig

public class RocketConfig implements Serializable {
	private static final long serialVersionUID = 6807425160034094787L;
	
	public RocketConfig() {
		
	}
	
	/**
	 * 創建配置對象
	 * @param address 服務地址,多個用;分割,例如"192.168.25.135:9876;192.168.25.138:9876"
	 */
	public RocketConfig(String address) {
		this.address = address;
	}

	//地址
	private String address;

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}
}

ConsumerConfig

public class ConsumerConfig implements Serializable {
	private static final long serialVersionUID = 10022583900545392332L;

	/**
	 * 訂閱主題
	 * @return
	 */
	private String topic;
	
	/**
	 * 消費者組
	 * @return
	 */
	private String consumerGroup;
	
	/**
	 * 默認是tag,訂閱子查詢條件,默認獲取所有
	 * 如果selectorType爲sql,則值爲sql
	 * @return
	 */
	private String selectorExpression = "*";
	
	/**
	 * 查詢類型 SelectorType.TAG根據tag查詢   SelectorType.SQL92根據sql查詢
	 * @return
	 */
	private SelectorType selectorType;
	
	/**
	 * 控制消息模式,如果希望所有訂閱者都接收消息全部消息,廣播是一個不錯的選擇。如果希望一個消費者接收則使用負載均衡模式
	 * Control message mode, if you want all subscribers receive message all message, broadcasting is a good choice.
	 * @return
	 */
	private MessageModel messageModel;
    
    /**
     * 實例名稱
     * @return
     */
	private String instanceName;

	public String getTopic() {
		return topic;
	}

	public void setTopic(String topic) {
		this.topic = topic;
	}

	public String getConsumerGroup() {
		return consumerGroup;
	}

	public void setConsumerGroup(String consumerGroup) {
		this.consumerGroup = consumerGroup;
	}

	public String getSelectorExpression() {
		return selectorExpression;
	}

	public void setSelectorExpression(String selectorExpression) {
		this.selectorExpression = selectorExpression;
	}

	public SelectorType getSelectorType() {
		return selectorType;
	}

	public void setSelectorType(SelectorType selectorType) {
		this.selectorType = selectorType;
	}

	public MessageModel getMessageModel() {
		return messageModel;
	}

	public void setConsumeMode(ConsumeMode consumeMode) {
		this.consumeMode = consumeMode;
	}

	public String getInstanceName() {
		return instanceName;
	}

	public void setInstanceName(String instanceName) {
		this.instanceName = instanceName;
	}
}

SelectorType

public enum SelectorType {

    /**
     * @see 根據tag查詢
     */
    TAG,

    /**
     * @see 根據sql查詢
     */
    SQL92
}

SimpleProducer

public class SimpleProducer {
	private String producerGroup;
	private RocketConfig config;
	
	private String instanceName;
	
	private DefaultMQProducer producer;
	
	public DefaultMQProducer getProducer() {
		return producer;
	}

	public SimpleProducer(RocketConfig config, String producerGroup){
		this.producerGroup = producerGroup;
		this.config = config;
	}
	
	public void init() throws MQClientException{
		producer = new DefaultMQProducer(producerGroup);
		producer.setNamesrvAddr(config.getAddress());
		if(instanceName != null) {
			producer.setInstanceName(instanceName);
		}
		producer.start();
	}
	
	public void destroy(){
		producer.shutdown();
	}

	public String getInstanceName() {
		return instanceName;
	}

	public void setInstanceName(String instanceName) {
		this.instanceName = instanceName;
	}
}

SimpleConsumer

public class SimpleConsumer {
	private DefaultMQPushConsumer consumer;
	//rocketMq配置
	private RocketConfig config;
	//消費者配置
	private ConsumerConfig consumerConfig;
	//消息監聽器
	private MessageListenerConcurrently messageListener;
	
	public SimpleConsumer(RocketConfig config, ConsumerConfig consumerConfig, MessageListenerConcurrently messageListener){
		this.messageListener = messageListener;
		this.config = config;
		this.consumerConfig = consumerConfig;
 	}
	
	public void init() throws Exception{
		consumer = new DefaultMQPushConsumer(consumerConfig.getConsumerGroup());
		//服務地址
		consumer.setNamesrvAddr(config.getAddress());
		//實例名稱
		if(consumerConfig.getInstanceName() != null) {
			consumer.setInstanceName(consumerConfig.getInstanceName());
		}
		//訂閱主題
		if(consumerConfig.getSelectorType() == SelectorType.TAG) {//如果是根據tag過濾消息
			consumer.subscribe(consumerConfig.getTopic(), consumerConfig.getSelectorExpression());
		}else if(consumerConfig.getSelectorType() == SelectorType.SQL92) {//如果是根據sql過濾消息
			consumer.subscribe(consumerConfig.getTopic(), MessageSelector.bySql(consumerConfig.getSelectorExpression()));
		}
		//消息模式
		consumer.setMessageModel(consumerConfig.getMessageModel());
		//監聽器
		consumer.registerMessageListener(messageListener);
		consumer.start();
	}

	public void destroy(){
		consumer.shutdown();
	}
	
	public DefaultMQPushConsumer getConsumer() {
		return consumer;
	}
}

MqMsgLoadListener

public class MqMsgLoadListener implements ApplicationListener<ContextRefreshedEvent>, DisposableBean {
	//掃描的類
	private Map<String, Object> beanMap = new HashMap<>();
	//消費者列表
	private List<SimpleConsumer> consumerList = new ArrayList<>();
	@Autowired
	private RocketConfig config;

	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		//如果是第二次加載則不處理了
		if(event.getApplicationContext().getParent() != null) {
			return;
		}
		
		//獲取註解類
        beanMap = event.getApplicationContext().getBeansWithAnnotation(RocketMqMsgListener.class);
        System.out.println("================"+beanMap.size());
        //未掃描到不處理
        if(beanMap == null || beanMap.size() == 0) {
        	return;
        }
        for(Object bean : beanMap.values()) {
        	createConsumer(bean);
        }
	}

	/**
	 * 創建消費者
	 * @param bean
	 */
	private void createConsumer(Object bean) {
		if(!(bean instanceof MessageListenerConcurrently)) {
			return;
		}
		//獲取註解
		RocketMqMsgListener mqMsgListener = bean.getClass().getAnnotation(RocketMqMsgListener.class);
		//配置
		ConsumerConfig consumerConfig = new ConsumerConfig();
		consumerConfig.setTopic(mqMsgListener.topic());
		consumerConfig.setConsumerGroup(mqMsgListener.consumerGroup());
		consumerConfig.setSelectorExpression(mqMsgListener.selectorExpression());
		consumerConfig.setSelectorType(mqMsgListener.selectorType());
		consumerConfig.setMessageModel(mqMsgListener.messageModel());
		if(!StringUtils.isEmpty(mqMsgListener.instanceName())) {
			consumerConfig.setInstanceName(mqMsgListener.instanceName());
		}
		//創建消費者
		SimpleConsumer consumer = new SimpleConsumer(config, consumerConfig, (MessageListenerConcurrently)bean);
		consumerList.add(consumer);
		//初始化並啓動
		try {
			consumer.init();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 服務停止
	 */
	@Override
	public void destroy() throws Exception {
		for(SimpleConsumer consumer : consumerList) {
			if(consumer == null) {
				continue;
			}
			consumer.destroy();
		}
	}
}

示例代碼拋磚引玉,要用於生產環境還需要完善

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