天天看點

解決ActiveMQ的“Invalid broker URI”異常的曆程

000

最近碰到一個問題,把解決的過程記錄下來。

故障原因

同僚的應用上線,Tomcat無法正常啟動。抛出這樣的異常:

org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:|PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'brokerURL' threw exception; nested exception is java.lang.IllegalArgumentException: Invalid broker URI: nio://10.0.0.0:91616 
        at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:121)
        at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:75)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1504)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1216)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:538)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:229)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
           

據同僚說,線上下環境,可以正常運作,線上上環境就出錯了,非常的詭異。

程式有一個env.properties的配置檔案:

broker.producer  = failover://(nio://10.0.0.3:91615?connectionTimeout=3000,nio://10.0.0.4:91615?connectionTimeout=3000)

線上和線下加載的是不同的env.properties。

在Spring xml配置檔案裡有這樣的配置:

<!-- ActiveMQ 連接配接工廠 -->
	<amq:connectionFactory id="jmsConnectionFactory" brokerURL="${broker.producer}" />           

Spring在運作時,會自動替換${}表達式裡的值。

首先檢查網絡的連通性:

線上上機器上,執行

telnet 10.0.0.3 91616

可以正常連接配接,再到ActiveMQ的Web Console上檢視Connection,發現的确沒有這個IP的連接配接。

檢查配置是否正常:

線上的配置的brokerURI是這個:

failover://(nio://10.0.0.3:91616?connectionTimeout=3000,nio://10.0.0.4:91616?connectionTimeout=3000)

直接先改為最簡單的,在vim下yy複制了一行,删除多餘的,剩下:

nio://10.0.0.3:91616

發現還是報異常。

利用ssh做端口轉發,本地測試

在本地跑了個簡單程式,用XShell的端口轉發,把本地請求轉發到線上機器上,發現可以正常發送消息。于是讓同僚回去檢查代碼裡的其它問題了。

把測試程式放到線上機器運作

但是同僚沒有找到錯誤,于是把剛才的簡單的程式打成一個fat jar包,放到線上機器上去跑,發現可以正常發送ActiveMQ消息。

程式打包用的是maven的one jar 插件,參考:

http://www.mkyong.com/maven/maven-create-a-fat-jar-file-one-jar-example/

把測試程式內建到同僚的代碼裡,運作

把測試程式放到同僚的War包的代碼裡,放到線上機器,發現可以正常發送消息。

但是同僚配置的ActiveMQ還是不能發送消息,還是報“Invalid broker URI”異常。

增加變量,反複測試,對比配置

沒辦法了,把同僚的環境變量${broker.producer},設定到測試代碼裡,發現測試代碼抛異常了。

于是确認是${broker.producer} 這個變量有問題。

但是env.properties檔案裡的配置看起來是對的。于是懷疑是配置檔案格式有問題。

備份舊檔案,建了個新配置檔案,配置上

broker.producer =nio://10.0.0.3:91615

發現,居然正常了。

于是對比兩個配置檔案,發現舊的配置檔案上,最後多了一個空格。。就是91615後面多了一個空格。

蛋疼無比,ActiveMQ居然不能識别處理配置值後面多出來的一個空格。而且Spring抛出來的異常裡也沒有這個資訊。

搜尋關鍵字"Invalid broker URI",檢視ActiveMQ代碼,找到原始異常

開始調試時,找不到原始的異常資訊在哪裡,Spring的函數調用層次太多了。于是采用代碼搜尋。

在 https://searchcode.com 搜尋"Invalid broker URI",終于找到原始的異常資訊是下面的代碼抛出來的:

URI org.apache.activemq.ActiveMQConnectionFactory.createURI(String brokerURL)
    private static URI createURI(String brokerURL) {
        try {
            return new URI(brokerURL);
        } catch (URISyntaxException e) {
            throw (IllegalArgumentException)new IllegalArgumentException("Invalid broker URI: " + brokerURL).initCause(e);
        }
    }           

很奇怪的是,IllegalArgumentException異常裡是正确地把URISyntaxException設定到cause裡了,後面的Spring卻沒有把這個資訊給列印出來。。

Spring列印異常的工作原理

為什麼Spring能把前面的異常資訊都列印出來,而原始的異常資訊卻不能列印出來?比如某個Spring異常資訊是這樣的:

Caused by: org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:

Srping會用Caused by,nested exception is,這樣的字元把所有的異常都串起來。到底這裡面是怎麼工作的?

再次搜尋"nested exception is"

查找到Spring相關的代碼。

原來所有的Spring異常類都繼承自NestedRuntimeException,而NestedRuntimeException重寫了getMessage()函數,在getMessage()函數裡,會把異常的資訊全都串起來。

而IllegalArgumentException繼承自Throwable類,Throwable類的getMessage()函數隻是簡單列印了message,并沒有把cause也輸出。

org.springframework.core.NestedRuntimeException

	/**
	 * Return the detail message, including the message from the nested exception
	 * if there is one.
	 */
	@Override
	public String getMessage() {
		return NestedExceptionUtils.buildMessage(super.getMessage(), getCause());
	}


org.springframework.core.NestedExceptionUtils
	/**
	 * Build a message for the given base message and root cause.
	 * @param message the base message
	 * @param cause the root cause
	 * @return the full exception message
	 */
	public static String buildMessage(String message, Throwable cause) {
		if (cause != null) {
			StringBuilder sb = new StringBuilder();
			if (message != null) {
				sb.append(message).append("; ");
			}
			sb.append("nested exception is ").append(cause);
			return sb.toString();
		}
		else {
			return message;
		}
	}           

其它的一些東東:

NestedExceptionUtils這個類是abstract,這樣可以防止使用者得到執行個體,這樣使用者不能用錯。這個也是一個常見的util類的技巧了。

public abstract class NestedExceptionUtils {           

總結:

這個代碼搜尋網站比較好用:https://searchcode.com

很多流行網站的資料都有,比github上要全。

後來在網上搜尋了下“Invalid broker URI”,有10萬多條結果。。估計有不少就是因為一些空格而造成的。。

ActiveMQ的開發者隻需要加上一點點的trim()的判斷處理代碼,就可以減少很多人的痛苦了。

是以防禦性程式設計還是有必要的,有的時候并不真的是使用者不會用,而是錯誤來自想像不到的地方。

URI org.apache.activemq.ActiveMQConnectionFactory.createURI(String brokerURL)
    private static URI createURI(String brokerURL) {
        try {
            if(brokerURL != null)
                brokerURL = brokerURL.trim();
            return new URI(brokerURL);
        } catch (URISyntaxException e) {
            throw (IllegalArgumentException)new IllegalArgumentException("Invalid broker URI: " + brokerURL).initCause(e);
        }
    }