spring整合activeMQ之動態注入

1.環境
Spring採用4.1.3版本,ActiveMQ使用5.9.1版本,本文不介紹Spring集成ActiveMQ來發送和接收JMS消息的細節。

2.參考
Spring整合JMS(一)——基於ActiveMQ實現系列。

3.基礎配置
activemq.xml

<?xml version="1.0" encoding="UTF-8"?>
<activeMQ>
    <brokerUrl>tcp://192.168.0.100:9300</brokerUrl>
    <thisCode>central</thisCode>
    <destination>
        <queue code="reply" name="reply.Queue" remark="中心端接收回復消息的隊列" />
        <queue code="one" name="one.Queue" remark="一號測試隊列" />
        <queue code="two" name="two.Queue" remark="二號測試隊列" />
        <queue code="three" name="three.Queue" remark="三號測試隊列" />
        <queue code="four" name="four.Queue" remark="四號測試隊列" />
    </destination>
    <listeners>
        <queueListener code="reply" remark="中心端回覆隊列監聽器"/>
    </listeners>
</activeMQ>

applicationContext.xml 加入註解支持

<context:annotation-config/>  
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>  
<!-- 打開Spring組件自動掃面,並配置要掃描的基本包 --> 
<context:component-scan base-package="com.**.messaging.configuration"></context:component-scan>

4、動態注入隊列

ActiveMQConfiguration.java在自己項目中的com.**.messaging.configuration包中,這裏動態向spring容器注入隊列及監聽器,ActiveMQConfig是使用XStream將activemq.xml轉化後的類,方便使用,具體找度娘,就不多說了
ApplicationContextFactory.getApplicationContext()是自定義的工廠方法,獲取web環境上下文ApplicationContext,可通過實現ApplicationContextAware接口獲取ApplicationContext,
QueueMsgListener和TopicMsgListener是實現spring的MessageListener接口的監聽器,這裏是因爲要區分消息來自queue或topic所以分別做了實現。

/**
 * ActiveMQ 配置
 */
@Configuration
public class ActiveMQConfiguration{

    private static final Logger LOGGER = LoggerFactory.getLogger(ActiveMQConfiguration.class);
    private InputStream in = null;
    public static ActiveMQConfig activeMQConfig;
    @Autowired
    private ActiveMQConnectionFactory jmsConnectionFactory;

    public ActiveMQConfiguration(){
        try {
//          String path = this.getClass().getResource("/").getPath();
            in = this.getClass().getResource("/activemq.xml").openStream();
            activeMQConfig = XmlUtil.toBeanFromFile(in, ActiveMQConfig.class);  
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 
     * @return
     */
    @Bean
    public String dynamicLoading(){
        DefaultListableBeanFactory acf = (DefaultListableBeanFactory) ApplicationContextFactory.getApplicationContext().getAutowireCapableBeanFactory();
        // 通過BeanDefinitionBuilder創建bean定義
        //註冊隊列
        if(activeMQConfig.getDestination().getQueues()!=null)
            for (Destination queue : activeMQConfig.getDestination().getQueues()) {
                BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ActiveMQQueue.class);
                beanDefinitionBuilder.addConstructorArgValue(queue.getName());
                acf.registerBeanDefinition(queue.getCode(),beanDefinitionBuilder.getRawBeanDefinition());
            }
        //註冊隊列監聽容器
        if(activeMQConfig.getListeners().getQueueListener()!=null)
            for (Listener queueListener : activeMQConfig.getListeners().getQueueListener()) {
                BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultMessageListenerContainer.class);
                beanDefinitionBuilder.addPropertyValue("connectionFactory", jmsConnectionFactory);
                beanDefinitionBuilder.addPropertyValue("messageListener", acf.getBean("queueMsgListener",QueueMsgListener.class));
                beanDefinitionBuilder.addPropertyValue("destination", acf.getBean(queueListener.getCode(),ActiveMQQueue.class));
                acf.registerBeanDefinition(queueListener.getCode()+".Container",beanDefinitionBuilder.getRawBeanDefinition());
            }


        //註冊all topic(這裏固定了topic的名稱:"TOPIC.ALL")
        BeanDefinitionBuilder allBuilder = BeanDefinitionBuilder.genericBeanDefinition(ActiveMQTopic.class);
        allBuilder.addConstructorArgValue("TOPIC.ALL");
        acf.registerBeanDefinition("TOPIC.ALL",allBuilder.getRawBeanDefinition());

        //註冊all topic監聽(這裏固定了topic的監聽容器名稱:""TOPIC.ALL.Container"")
        BeanDefinitionBuilder allListenerBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultMessageListenerContainer.class);
        allListenerBuilder.addPropertyValue("connectionFactory", jmsConnectionFactory);
        allListenerBuilder.addPropertyValue("messageListener", acf.getBean("topicMsgListener",TopicMsgListener.class));
        allListenerBuilder.addPropertyValue("destination", acf.getBean("TOPIC.ALL",ActiveMQTopic.class));
        acf.registerBeanDefinition("TOPIC.ALL.Container",allListenerBuilder.getRawBeanDefinition());


    // 創建一個連接工廠,用於程序連接到activemq代理。
    @Bean(name = "jmsConnectionFactory")
    public ActiveMQConnectionFactory jmsConnectionFactory(){
        ActiveMQConnectionFactory amqf = new ActiveMQConnectionFactory(activeMQConfig.getBrokerUrl());
        amqf.setCloseTimeout(10*1000);
        amqf.setSendTimeout(10*1000);
        return amqf;
    }

    // 創建服務端的jmsTemplate(一般情況下服務端和客戶端的jmsTemplate可以設置相同,但爲了效率因素,我們將其分開設置,服務端應儘量減少連接數量,所以使用singleConnectionFactory)。
    @Bean(name = "jmsTemplate")
    public JmsTemplate jmsTemplate(){
        JmsTemplate jt = new JmsTemplate();
        SingleConnectionFactory scf = new SingleConnectionFactory();
        scf.setTargetConnectionFactory(jmsConnectionFactory);
        scf.setReconnectOnException(true);//連接斷開後重試
        jt.setConnectionFactory(scf);
        return jt;
    }
//  @Bean(name = "Queue.Container")
//  public DefaultMessageListenerContainer queueListener(){
//      DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
//      container.setConnectionFactory(jmsConnectionFactory);
//      container.setDestination(applicationContext.getAutowireCapableBeanFactory().getBean("QUEUE.baiyun",ActiveMQQueue.class));
//      container.setMessageListener(applicationContext.getAutowireCapableBeanFactory().getBean("consumerMsgListener",ConsumerMsgListener.class));
//      return container;
//  }
//  
//  @Bean(name = "Topic.Container")
//  public DefaultMessageListenerContainer topicListener(){
//      DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
//      container.setConnectionFactory(jmsConnectionFactory);
//      container.setDestination(applicationContext.getAutowireCapableBeanFactory().getBean("TOPIC.all",ActiveMQTopic.class));
//      container.setMessageListener(applicationContext.getAutowireCapableBeanFactory().getBean("consumerMsgListener",ConsumerMsgListener.class));
//      return container;
//  }


}

acf.getBean(“…”,Class)從容器中獲取bean,第一個參數是bean的name( @Bean(name=”**”)),要和注入是使用的相同;spring會自動率先注入先使用的bean。
注掉的部分是配置固個數的隊列,爲了使隊列及監聽可動態配置,所以使用動態注入的方式;
bean JmsTemplate 中的這行代碼還是很必要的:

scf.setReconnectOnException(true);//連接斷開後重試

測試過程中關閉了ActiveMQ服務器,會導致client端獲取連接失敗,報出如下異常,啓動服務器後仍然報出,加上上面代碼使連接重試解決問題,正常生產環境也避免不了網絡中斷的情況,所以加上上面這行代碼很有必要

org.springframework.jms.UncategorizedJmsException: Uncategorized exception occured during JMS processing; nested exception is 
...
org.apache.activemq.ConnectionFailedException: The JMS connection has failed: Connection reset
...
Caused by: org.apache.activemq.ConnectionFailedException: The JMS connection has failed: Connection reset

5、生產者 producer

//1、發送接口(JmsMsg是自定義的消息實體基類,包含必須的消息發送的目的地destination,以及自己封裝的消息id等常規的信息)
public interface Sender<T extends JmsMsg>{
    MsgReturn sendMessageBefore();
    MsgReturn sendMessage(T t) ;
}
//2、實現獲取發送目的地隊列Destination和JmsTemplate,調用JmsTemplate.send(Destination,MessageCreator )發送消息,由於不同的消息類型(textMessage或bytesMessage等)的參數不盡相同,獲取MessageCreator需要在子類中去實現getMessageCreator()
@Component
public abstract class BaseSender<T extends JmsMsg>  implements Sender<T>{

    public static final Logger LOGGER = LoggerFactory.getLogger(BaseSender.class); 
    @Autowired
    private JmsTemplate JmsTemplate ;
    private ActiveMQDestination destination;
    private T baseMesage;

    @Override
    public MsgReturn sendMessageBefore() {
        LOGGER.debug("==sendBefore");
        return new MsgReturn();
    }

    private MsgReturn  onMessage() {
        try {
            destination = (ActiveMQDestination)ApplicationContextFactory.getApplicationContext().getBean(baseMesage.getDestinationCode());
            JmsTemplate =  (JmsTemplate) ApplicationContextFactory.getApplicationContext().getBean("jmsTemplate");  
            JmsTemplate.send(destination,getMessageCreator());
        } catch (BeansException e) {
            e.printStackTrace();
            return new MsgReturn(false,baseMesage.getMsgId(),e.getMessage());
        } catch (JmsException e) {
            e.printStackTrace();
            return new MsgReturn(false,baseMesage.getMsgId(),e.getMessage());
        }
        return new MsgReturn(baseMesage.getMsgId(),"success");
    }

    protected MessageCreator getMessageCreator(){
        return null;
    }

    @Override
    public final MsgReturn sendMessage(T t) {
        this.baseMesage = t;
        MsgReturn msgReturn = new MsgReturn();
        msgReturn = sendMessageBefore();
        if(msgReturn.getResult()){
            LOGGER.info("==開始發送");
            msgReturn = onMessage();
            LOGGER.info("==結束髮送:"+msgReturn);
        }
        return msgReturn;
    }
    public T getBaseMesage() {
        return baseMesage;
    }
    public void setBaseMesage(T baseMesage) {
        this.baseMesage = baseMesage;
    }
}

//3、這是發送bytesMessage的實現類,(BytesMsg繼承自定義的JmsMsg),getMessageCreator()中將自定義的參數轉換成標準的Message
@Component
public class BytesMsgSender extends BaseSender<BytesMsg> {

    public static final Logger LOGGER = Logger.getLogger(BytesMsgSender.class); 
    @Override
    public MsgReturn sendMessageBefore() {
        super.sendMessageBefore();
//      int fileSize = 10485760;//10MB
//      if(getBaseMesage().getFileSize()>fileSize)
//          return new MsgReturn(false,null,"==發送的文件超過指定大小");
        return new MsgReturn();

    }

    @Override
    public MessageCreator getMessageCreator(){
        return new MessageCreator() {
            public Message createMessage(Session session)throws JMSException {
                BytesMessage message = session.createBytesMessage();
                message.writeBytes(getBaseMesage().getBytes());

                if(getBaseMesage().getMsgId()==null)
                    getBaseMesage().setMsgId(UUID.randomUUID().toString());
                message.setStringProperty(Global.MSG_ID, getBaseMesage().getMsgId());
    //...   
                message.setStringProperty(Global.FILE_NAME, getBaseMesage().getFileName());
                message.setStringProperty(Global.FILE_MD5, getBaseMesage().getFileMD5());
                message.setIntProperty(Global.FILE_SIZE, getBaseMesage().getFileSize());

                return message;
            }
        };
    }
}

6、消費者 customer

//1、消息接受接口,注入的監聽器在消息到達時會調用onMessage(),這裏需要定義這個方法。
public interface Reciever extends MessageListener{
    void onMessage();
    void replyCallBack();//回調
}

//2、實現對消息的轉化
@Component("baseReciever")
public abstract class BaseReciever implements Reciever{

    public static final Logger LOGGER = Logger.getLogger(BaseReciever.class);

    private Message message;
    private JmsMsg jmsMsg;

    public final void onMessage(Message message) {
        this.message = message; //標準的message
        initJmsMsg();
        onMessage();
        replyCallBack();
    }
    //將標準的message轉化爲自定義的message
    public void initJmsMsg() {
        try {

            if(message instanceof TextMessage){
                TextMessage msg = (TextMessage) message;
                TextMsg textMsg = new TextMsg();

                textMsg.setText(msg.getText());

                textMsg.setMsgId(message.getStringProperty(Global.MSG_ID));
                //...
                this.jmsMsg = textMsg;
            }else if(message instanceof BytesMessage){
                BytesMessage msg = (BytesMessage) message;
                byte[] bytes = new byte[new Long(msg.getBodyLength()).intValue()];
                msg.readBytes(bytes);

                BytesMsg bytesMsg = new BytesMsg();

                bytesMsg.setBytes(bytes);   

                bytesMsg.setMsgId(message.getStringProperty(Global.MSG_ID));
                //...
                this.jmsMsg = bytesMsg;
            }
        } catch (JMSException e) {
            e.printStackTrace();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public JmsMsg getJmsMsg() {
        return jmsMsg;
    }
    public void setJmsMsg(JmsMsg jmsMsg) {
        this.jmsMsg = jmsMsg;
    }
    @Override
    public void replyCallBack() {
    }
    @Override
    public void onMessage() {
    }
}

//3、監聽實現,因業務不同所以分別實現;onMessage()中實現具體業務,replyCallBack()可以在收到消息後發送回覆消息等
@Component
public class QueueMsgListener extends BaseReciever {

    @Override
    public void onMessage() {
        //getJmsMsg()
        //...
    }
    @Override
    public void replyCallBack() {
        //getJmsMsg()
        //...
    }
}
@Component
public class TopicMsgListenerextends BaseReciever {

    @Override
    public void onMessage() {
        //getJmsMsg()
        //...
    }
    @Override
    public void replyCallBack() {
        //getJmsMsg()
        //...
    }
}

這其中還是有很多個點可以重新整合的,使代碼更精簡,但大概也說明了個思路;
另外在監聽中可以使用任務隊列,若多個隊列監聽同時收到大量消息,並且消息的消化需要耗費資源,那使用任務隊列還是很有必要的,這裏就不注重這些細節了;

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