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()方法,網上
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行為自定義異常,則要引入自定義的包等).
這樣就從根本上解決了異常處理的問題.後續有其他問題,也可以直接修改.