1. 程式人生 > >dubbo(四)異常處理

dubbo(四)異常處理

dubbo的異常處理網上描述的文章很多,複製黏貼也不在少數.在這裡記錄下自己的一些體會.

還是帶著問題(目錄)來寫吧.

1.dubbo官方推薦的異常處理方式是什麼?

2.dubbo處理異常的邏輯是什麼樣的?為什麼要這樣處理?

3.丟擲自定義異常有哪些方式?

4.在dubbo:provider中設定filter="-exception", 去掉異常的filter會怎麼樣?

5.最終採用的異常處理方案

1.dubbo官方推薦的異常處理方式是什麼?

在dubbo官方文件的服務化最佳實踐中,推薦的處理方式如下:


2.dubbo處理異常的邏輯是什麼樣的?為什麼要這樣處理?

dubbo的異常處理類是com.alibaba.dubbo.rpc.filter.ExceptionFilter 類,原始碼這裡就不貼了.歸納下對異常的處理分為下面幾類:

1)如果provider實現了GenericService介面,直接丟擲

2)如果是checked異常,直接丟擲

3)在方法簽名上有宣告,直接丟擲

4)異常類和介面類在同一jar包裡,直接丟擲

5)是JDK自帶的異常,直接丟擲

6)是Dubbo本身的異常,直接丟擲

7)否則,包裝成RuntimeException拋給客戶端

網上有些文章對7)的處理有疑問,不理解原因,其實就是為了防止客戶端反序列化失敗.前面幾種情況都能保證反序列化正常.

3.丟擲自定義異常有哪些方式?

丟擲自定義異常其實就是使用上面2中的邏輯.所以相對應的有以下幾種方式:

1)provider實現GenericService介面.(我沒試過這種方式,應該是要自己實現$invoke()方法,網上

說直接把$invoke()方法廢棄掉不知道是怎麼處理的,直接返回null肯定是不行的)

2)自定義異常宣告為checked異常(這沒啥說了,不過一般自定義異常都是unchecked)

3)在方法簽名上宣告丟擲異常(這種基本上所有介面都要寫,麻煩)

4)異常類和介面類在同一jar包裡(存在鏈式呼叫時,這種可能不適用)

5)自定義異常的包名以java.或javax.開頭(dubbo判斷jdk自帶異常的條件,一般專案都有自己的命名規範,這樣乾的估計很少)

除了上面對應的,還可以用一種奇葩的方式,直接去掉異常的filter,如下:

6) <dubbo:provider filter="-exception" />

4.在dubbo:provider中設定filter="-exception", 去掉異常的filter會怎麼樣?

我在處理自定義異常的時候,覺得3中提到前5種方式都不太適合,所以使用了這種方式.

這種方式對provider中丟擲的異常不做任何處理,直接返回給consumer,會遇到一些奇怪的問題;

例如,在provider中有以下程式碼:

		String respone = "avs";
		JSONObject resultjson = JSONObject.fromObject(respone);

這段程式碼會丟擲 net.sf.json.JSONException ,但是在provider中不會列印任何異常資訊.

而在consumer中,會報出下面的錯誤:

com.alibaba.com.caucho.hessian.io.HessianFieldException: org.apache.commons.lang.exception.NestableRuntimeException.delegate: 'org.apache.commons.lang.exception.NestableDelegate' could not be instantiated
	at com.alibaba.com.caucho.hessian.io.JavaDeserializer.logDeserializeError(JavaDeserializer.java:671)
	at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:400)
	at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:233)
	at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:157)
	at com.alibaba.com.caucho.hessian.io.SerializerFactory.readObject(SerializerFactory.java:397)
	at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2070)
	at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2005)
	at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1990)
	at com.alibaba.dubbo.common.serialize.support.hessian.Hessian2ObjectInput.readObject(Hessian2ObjectInput.java:88)
	at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.decode(DecodeableRpcResult.java:92)
	at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.decode(DecodeableRpcResult.java:109)
	at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec.decodeBody(DubboCodec.java:97)
	at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.decode(ExchangeCodec.java:126)
	at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.decode(ExchangeCodec.java:87)
	at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCountCodec.decode(DubboCountCodec.java:46)
	at com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter$InternalDecoder.messageReceived(NettyCodecAdapter.java:134)
	at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:80)
	at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564)
	at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:559)
	at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:274)
	at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:261)
	at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:349)
	at org.jboss.netty.channel.socket.nio.NioWorker.processSelectedKeys(NioWorker.java:280)
	at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:200)
	at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:108)
	at org.jboss.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:44)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
	at java.lang.Thread.run(Thread.java:745)
Caused by: com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'org.apache.commons.lang.exception.NestableDelegate' could not be instantiated
	at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:275)
	at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:155)
	at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2067)
	at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1592)
	at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1576)
	at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:396)
	... 27 more
Caused by: java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
	at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:271)
	... 32 more
Caused by: java.lang.IllegalArgumentException: The Nestable implementation passed to the NestableDelegate(Nestable) constructor must extend java.lang.Throwable
	at org.apache.commons.lang.exception.NestableDelegate.<init>(NestableDelegate.java:112)
	... 37 more

這是個因為異常傳遞引起的錯誤,原異常資訊也丟失了,無法定位問題.

為了解決這個問題,想到的方案是在provider中,把所有入口方法都進行catch處理,轉換為自定義異常.

想當然的就用spring的AOP功能來實現.(解決一個bug,引入另外的bug大笑).

	<bean id="serviceEx" class="com.dingcheng.common.exception.ServiceExceptionHandler" />  
	<aop:config>
		<aop:aspect id="exServiceAop" ref="serviceEx">  
            <aop:pointcut id="exParam" expression="execution(* com.dingcheng.*.service.*Service.*(..))" />  
            <aop:after-throwing pointcut-ref="exParam" method="doThrowing" throwing="ex" />  
        </aop:aspect> 
	</aop:config>	


使用AOP進行異常攔截後引發了另外一個問題,就是ServiceA.a()裡面呼叫ServiceB.b()這種情況.

b()裡丟擲異常,aop處理一次,然後a再丟擲異常,aop再處理一次,多次呼叫就多次處理.

理想的解決方案是像事務管理那樣可以配置傳播性,但是這個不支援這種配置,

所以這種方案也不夠好.

5.最終採用的異常處理方案

丟擲一個自定義異常有這麼麻煩嗎? 主要原因是dubbo沒有支援的原因.

既然這樣,我們把dubbo變的支援不就可以了?

是的.把原始碼改一下就OK了.如下圖:


在異常處理這裡,加上自定義異常處理的程式碼.

或者直接將112行的RuntimeException替換成自己的自定義異常!

修改原始碼後,可以替換maven倉庫的jar,或者是在自己專案裡面建一個ExceptionFilter,包名和dubbo的相同,用來覆蓋掉dubbo的

(如果替換112行為自定義異常,則要引入自定義的包等).

這樣就從根本上解決了異常處理的問題.後續有其他問題,也可以直接修改.