spring activeMQ使用連接池工廠報錯

1:環境和版本

java:jdk7

spring:4.1.3

activemq:5.8.0

2:spring與activeMQ的結合配置


 
  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <beans xmlns="http://www.springframework.org/schema/beans"

  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

  5. ">

  6.  
  7.  
  8. <!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供 -->

  9. <bean id="activeMQConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">

  10. <property name="brokerURL" value="tcp://localhost:61636"></property>

  11. </bean>

  12. <!-- ActiveMQ爲我們提供了一個PooledConnectionFactory,往裏面注入一個ActiveMQConnectionFactory可以用來將Connection,

  13. Session和MessageProducer池化,這樣可以大大的減少我們的資源消耗。

  14. 問題:使用poolConnectionFactory時候,用JMSTemplate同步循環接收消息,因爲JMSTemplate會自動在接收消息後關閉連接,

  15. 所以循環到第二次的時候會報錯,這個問題待解決

  16. 問題:使用poolConnectionFactory時候,用監聽來接收消息,會有部分消息殘留在隊列裏面,問題待解決

  17. 結論:還是先別用連接池了-->

  18. <bean id="poolConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" >

  19. <property name="connectionFactory" ref="activeMQConnectionFactory" />

  20. <property name="maxConnections" value="10"/>

  21. </bean>

  22. <!-- Spring用於管理真正的ConnectionFactory的ConnectionFactory 這裏我使用的是singleConnectionFactory-->

  23. <bean id="singleConnectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">

  24. <property name="targetConnectionFactory" ref="activeMQConnectionFactory"/>

  25. </bean>

  26. <bean id="cachingConnectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">

  27. <property name="targetConnectionFactory" ref="activeMQConnectionFactory"/>

  28. </bean>

  29.  
  30.  
  31. <!-- 配置生產者 -->

  32. <!-- Spring提供的JMS工具類,它可以進行消息發送、接收等 -->

  33. <bean id="senderJmsTemplate" class="org.springframework.jms.core.JmsTemplate">

  34. <!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 -->

  35. <property name="connectionFactory" ref="singleConnectionFactory"/>

  36.  
  37. <!-- NON_PERSISTENT非持久化 1 ,PERSISTENT持久化 2 -->

  38. <property name="deliveryMode" value="2"/>

  39.  
  40. <property name="sessionTransacted" value="true"/>

  41. <property name="sessionAcknowledgeModeName" value="AUTO_ACKNOWLEDGE"/>

  42. </bean>

  43. <!--這個是隊列目的地,點對點的 -->

  44. <bean id="activeMQQueue" class="org.apache.activemq.command.ActiveMQQueue">

  45. <constructor-arg value="FirstQueue"/>

  46. </bean>

  47. <!--這個是主題目的地,一對多的 -->

  48. <bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">

  49. <constructor-arg value="topic"/>

  50. </bean>

  51.  
  52. <!-- 自定義消費者 -->

  53. <!-- Spring提供的JMS工具類,它可以進行消息發送、接收等 -->

  54. <bean id="receiverJmsTemplate" class="com.system.freemwork.amq.SimpleJmsTemplate">

  55. <!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 -->

  56. <property name="connectionFactory" ref="singleConnectionFactory"/>

  57.  
  58. <!-- 如果是原生的amq創建的session,將session設置爲true時候,ack會固定被設置爲AUTO_ACKNOWLEDGE

  59. 所以想要手動確認,那麼session的事物必須設置爲false,並且ack設置爲CLIENT_ACKNOWLEDGE -->

  60. <property name="sessionTransacted" value="false"/>

  61. <property name="sessionAcknowledgeModeName" value="CLIENT_ACKNOWLEDGE"/>

  62. <property name="receiveTimeout" value="1000"/>

  63. <property name="autoAcknowledge" value="true"/>

  64. </bean>

  65. </beans>

3:編寫send測試類


 
  1. package com.test.spring;

  2.  
  3. import org.junit.Test;

  4. import org.springframework.beans.factory.annotation.Autowired;

  5. import org.springframework.jms.core.JmsTemplate;

  6.  
  7. /**

  8. * 類描述:sender測試類

  9. *

  10. * @author fengyong

  11. * @version 1.0

  12. * @since 1.0

  13. * Created by fengyong on 16/8/3 下午7:45.

  14. */

  15. public class ActiveMqSender extends BaseTest {

  16. @Autowired

  17. private JmsTemplate senderJmsTemplate;

  18.  
  19. @Test

  20. public void activeMq(){

  21. for(int i = 1;i<=10;i++){

  22. senderJmsTemplate.convertAndSend("FirstQueue","我是第"+i+"個");

  23. }

  24. System.out.print("全部執行完畢!!!");

  25. }

  26. }

4:編寫receiver測試類


 
  1. package com.test.spring;

  2.  
  3. import com.system.freemwork.amq.SimpleJmsTemplate;

  4. import org.apache.activemq.command.ActiveMQQueue;

  5. import org.junit.Test;

  6. import org.springframework.beans.factory.annotation.Autowired;

  7. import org.springframework.jms.core.JmsTemplate;

  8.  
  9. import javax.jms.JMSException;

  10. import javax.jms.TextMessage;

  11.  
  12. /**

  13. * 類描述:receiver測試類

  14. *

  15. * @author fengyong

  16. * @version 1.0

  17. * @since 1.0

  18. * Created by fengyong on 16/8/8 下午5:28.

  19. */

  20. public class ActiveMqReceiver extends BaseTest{

  21. @Autowired

  22. private SimpleJmsTemplate receiverJmsTemplate;

  23. @Autowired

  24. private ActiveMQQueue activeMQQueue;

  25.  
  26. /**

  27. * 坑爹的方法,如果session事物設置爲true,receiver直接將sessioin進行commit,

  28. *

  29. * 如果設置爲false,receiver方法會直接判斷進行消息確認,無法做到手動的消息確認,所以一旦發生異常,這條消息不會回到消息隊列中

  30. *

  31. * session的提交可以認爲是消息確認收到

  32. * @throws JMSException

  33. */

  34. @Test

  35. public void receiver() throws JMSException {

  36. int i=1;

  37. while (true){

  38. i++;

  39. TextMessage message = (TextMessage)receiverJmsTemplate.receive(activeMQQueue);

  40. if (null != message) {

  41. System.out.println("收到消息==================" + message.getText());

  42.  
  43. } else {

  44. System.out.print("超時10秒");

  45. break;

  46. }

  47. }

  48. }

  49. }

 

注:如果session事物設置爲true,receiver直接將sessioin進行commit.源碼如下

if (session.getTransacted()) {
   // Commit necessary - but avoid commit call within a JTA transaction.
   if (isSessionLocallyTransacted(session)) {
      // Transacted session created by this template -> commit.
      JmsUtils.commitIfNecessary(session);
   }
}

如果session事物設置爲false,receiver方法會直接判斷進行消息確認,無法做到手動的消息確認,所以一旦發生異常,這條消息不會回到消息隊列中.源碼如下

else if (isClientAcknowledge(session)) {
   // Manually acknowledge message, if any.
   if (message != null) {
      message.acknowledge();
   }
}

所以需要修改源碼不讓其在receiver的時候自動確認收到消息

5:新建SimpleJmsTemplate繼承JmsTemplate


 
  1. package com.system.freemwork.amq;

  2.  
  3. import org.springframework.jms.JmsException;

  4. import org.springframework.jms.connection.ConnectionFactoryUtils;

  5. import org.springframework.jms.connection.JmsResourceHolder;

  6. import org.springframework.jms.core.JmsTemplate;

  7. import org.springframework.jms.core.SessionCallback;

  8. import org.springframework.jms.support.JmsUtils;

  9. import org.springframework.transaction.support.TransactionSynchronizationManager;

  10. import org.springframework.util.Assert;

  11. import sun.misc.resources.Messages_ja;

  12.  
  13. import javax.jms.Connection;

  14. import javax.jms.JMSException;

  15. import javax.jms.Message;

  16. import javax.jms.MessageConsumer;

  17. import javax.jms.Session;

  18.  
  19. /**

  20. * 類描述:自定義JmsTemplate,實現客戶手動確認

  21. *

  22. * @author fengyong

  23. * @version 1.0

  24. * @since 1.0

  25. * Created by fengyong on 16/8/10 上午10:03.

  26. */

  27. public class SimpleJmsTemplate extends JmsTemplate {

  28.  
  29.  
  30. private final JmsTemplateResourceFactory transactionalResourceFactory = new JmsTemplateResourceFactory();

  31. /**

  32. * 是否開啓手動確認標記

  33. */

  34. private Boolean autoAcknowledge;

  35.  
  36. MessageConsumer consumer = null;

  37. Session sessionToClose = null;

  38. Connection conToClose = null;

  39. boolean startConnection = false;

  40.  
  41. /**

  42. * 接收消息

  43. * @param session

  44. * @param consumer

  45. * @return

  46. * @throws JMSException

  47. */

  48. protected Message doReceive(Session session, MessageConsumer consumer) throws JMSException {

  49. try {

  50. this.consumer = consumer;

  51. // Use transaction timeout (if available).

  52. long timeout = getReceiveTimeout();

  53. JmsResourceHolder resourceHolder =

  54. (JmsResourceHolder) TransactionSynchronizationManager.getResource(getConnectionFactory());

  55. if (resourceHolder != null && resourceHolder.hasTimeout()) {

  56. timeout = Math.min(timeout, resourceHolder.getTimeToLiveInMillis());

  57. }

  58. Message message = doReceive(consumer, timeout);

  59. if (session.getTransacted()) {

  60. // Commit necessary - but avoid commit call within a JTA transaction.

  61. // 如果開啓了jta事物,那麼不會進行提交,jta事物會直接覆蓋掉session事物

  62. if (isSessionLocallyTransacted(session)) {

  63. // Transacted session created by this template -> commit.

  64. JmsUtils.commitIfNecessary(session);

  65. }

  66. }

  67. //autoAcknowledge如果爲真,不進行自動確認

  68. else if (isClientAcknowledge(session) && !autoAcknowledge) {

  69. // Manually acknowledge message, if any.

  70. if (message != null) {

  71. message.acknowledge();

  72. }

  73. }

  74. return message;

  75. }

  76. finally {

  77. consumer = null;

  78. }

  79. }

  80.  
  81. /**

  82. * 自定義的消息確認,關閉consumer和sesseionToClose是父類本身就要執行的,這裏直接拷貝下來,能不改的地方儘量不改

  83. * 該子類只是爲了自定義確認消息

  84. * @param message

  85. * @throws JMSException

  86. */

  87. public void msgAckAndcloseSession(Message message) throws JMSException {

  88. message.acknowledge();

  89. JmsUtils.closeMessageConsumer(consumer);

  90. JmsUtils.closeSession(sessionToClose);

  91. ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), startConnection);

  92. }

  93.  
  94. /**

  95. * 由於上面的doReceive(Session session, MessageConsumer consumer)需要調用這個方法,

  96. * 而在父類裏面這個方法是私有的,所以直接拷貝下來了

  97. * @param consumer

  98. * @param timeout

  99. * @return

  100. * @throws JMSException

  101. */

  102. private Message doReceive(MessageConsumer consumer, long timeout) throws JMSException {

  103. if (timeout == RECEIVE_TIMEOUT_NO_WAIT) {

  104. return consumer.receiveNoWait();

  105. }

  106. else if (timeout > 0) {

  107. return consumer.receive(timeout);

  108. }

  109. else {

  110. return consumer.receive();

  111. }

  112. }

  113.  
  114.  
  115. /**

  116. * 該方法是爲了防止確認消息前session被關閉,不然確認消息前session關閉會導致異常發生

  117. * transactionalResourceFactory在父類中是私有且不可修改,因爲只有這一個方法用到了transactionalResourceFactory

  118. * 所以直接將JmsTemplateResourceFactory拷貝下來使用

  119. * @param action

  120. * @param startConnection

  121. * @param <T>

  122. * @return

  123. * @throws JmsException

  124. */

  125. public <T> T execute(SessionCallback<T> action, boolean startConnection) throws JmsException {

  126. Assert.notNull(action, "Callback object must not be null");

  127. this.startConnection = startConnection;

  128.  
  129. try {

  130. Session sessionToUse = ConnectionFactoryUtils.doGetTransactionalSession(

  131. getConnectionFactory(), transactionalResourceFactory, startConnection);

  132. if (sessionToUse == null) {

  133. conToClose = createConnection();

  134. sessionToClose = createSession(conToClose);

  135. if (startConnection) {

  136. conToClose.start();

  137. }

  138. sessionToUse = sessionToClose;

  139. }

  140. if (logger.isDebugEnabled()) {

  141. logger.debug("Executing callback on JMS Session: " + sessionToUse);

  142. }

  143. return action.doInJms(sessionToUse);

  144. }

  145. catch (JMSException ex) {

  146. throw convertJmsAccessException(ex);

  147. }

  148. finally {

  149. sessionToClose = null;

  150. conToClose = null;

  151. startConnection = false;

  152. }

  153. }

  154.  
  155.  
  156. /**

  157. * Sets new 是否開啓手動確認標記.

  158. *

  159. * @param autoAcknowledge New value of 是否開啓手動確認標記.

  160. */

  161. public void setAutoAcknowledge(Boolean autoAcknowledge) {

  162. this.autoAcknowledge = autoAcknowledge;

  163. }

  164.  
  165. /**

  166. * Gets 是否開啓手動確認標記.

  167. *

  168. * @return Value of 是否開啓手動確認標記.

  169. */

  170. public Boolean getAutoAcknowledge() {

  171. return autoAcknowledge;

  172. }

  173.  
  174.  
  175. /**

  176. * 直接拷貝下來的

  177. */

  178. private class JmsTemplateResourceFactory implements ConnectionFactoryUtils.ResourceFactory {

  179.  
  180. @Override

  181. public Connection getConnection(JmsResourceHolder holder) {

  182. return SimpleJmsTemplate.this.getConnection(holder);

  183. }

  184.  
  185. @Override

  186. public Session getSession(JmsResourceHolder holder) {

  187. return SimpleJmsTemplate.this.getSession(holder);

  188. }

  189.  
  190. @Override

  191. public Connection createConnection() throws JMSException {

  192. return SimpleJmsTemplate.this.createConnection();

  193. }

  194.  
  195. @Override

  196. public Session createSession(Connection con) throws JMSException {

  197. return SimpleJmsTemplate.this.createSession(con);

  198. }

  199.  
  200. @Override

  201. public boolean isSynchedLocalTransactionAllowed() {

  202. return SimpleJmsTemplate.this.isSessionTransacted();

  203. }

  204. }

  205.  
  206. }

6:第二個receiver測試類


 
  1. package com.test.spring;

  2.  
  3. import com.system.freemwork.amq.SimpleJmsTemplate;

  4. import org.apache.activemq.command.ActiveMQQueue;

  5. import org.junit.Test;

  6. import org.springframework.beans.factory.annotation.Autowired;

  7. import org.springframework.jms.core.JmsTemplate;

  8.  
  9. import javax.jms.JMSException;

  10. import javax.jms.TextMessage;

  11.  
  12. /**

  13. * 類描述:receiver測試類

  14. *

  15. * @author fengyong

  16. * @version 1.0

  17. * @since 1.0

  18. * Created by fengyong on 16/8/8 下午5:28.

  19. */

  20. public class ActiveMqReceiver extends BaseTest{

  21. @Autowired

  22. private SimpleJmsTemplate receiverJmsTemplate;

  23. @Autowired

  24. private ActiveMQQueue activeMQQueue;

  25.  
  26.  
  27. @Test

  28. public void recerver() throws JMSException {

  29. int i=1;

  30. while (true){

  31. i++;

  32. TextMessage message = (TextMessage)receiverJmsTemplate.receive(activeMQQueue);

  33. if (null != message) {

  34. System.out.println("收到消息==================" + message.getText());

  35. if(i==4){

  36. throw new RuntimeException("Exception");

  37. }

  38. receiverJmsTemplate.msgAckAndcloseSession(message);

  39. } else {

  40. System.out.print("超時10秒");

  41. break;

  42. }

  43. }

  44. }

  45. }

 

7:測試結果


 
  1. 收到消息==================我是第1個

  2. 收到消息==================我是第2個

  3. 收到消息==================我是第3個

  4.  
  5. java.lang.RuntimeException

  6. : Exception

  7. at com.test.spring.ActiveMqReceiver.show(ActiveMqReceiver.java:43)

  8. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

  9. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

  10. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

  11. at java.lang.reflect.Method.invoke(Method.java:606)

  12. at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)

  13. at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)

  14. at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)

  15. at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)

  16. at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73)

注:接收第三個時失敗,雖然這裏打印出來了,但是第三個消息並沒有被確認接收,同步自定義接收消息修改成功

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