MyException - 我的异常网
当前位置:我的异常网» 行业应用 » dubbo错误处理

dubbo错误处理

www.MyException.Cn  网友分享于:2018-04-18  浏览:0次
dubbo异常处理

      dubbo有自己的异常处理机制,当服务端抛出一个dubbo可以处理传递的异常时,会直接在客户端上再次抛出,由开发者自己去处理。注意:这里说的不是所有异常,而是dubbo可以处理传递的异常,具体这个后边再说。

     先看两段代码,接口代码:

     

[java] view plain copy
 
  1. public interface IPersonService {  
  2.     public Person getPerson(long id);  
  3.     /** 
  4.      * 根据名称获取 
  5.      *  
  6.      * @param name 
  7.      * @return 
  8.      */  
  9.     public Person getByNick(String name);  
  10. }  

      简单实现:

 

    

[java] view plain copy
 
  1. public class PersonServiceImpl implements IPersonService {  
  2.     private final static AtomicInteger ID = new AtomicInteger();  
  3.   
  4.     @Override  
  5.     public Person getPerson(long id) {  
  6.         Person person = new Person();  
  7.         person.setId(id);  
  8.         person.setName("某某");  
  9.         try {  
  10.             Thread.sleep(2000l);  
  11.         } catch (InterruptedException e) {  
  12.             // TODO Auto-generated catch block  
  13.             e.printStackTrace();  
  14.         }  
  15.         return person;  
  16.     }  
  17.   
  18.     @Override  
  19.     public Person getByNick(String name) {  
  20.         name = name.trim();  
  21.         Person person = new Person();  
  22.         person.setId(ID.getAndIncrement());  
  23.         person.setName(name);  
  24.         return person;  
  25.     }  
  26.   
  27. }  

        这两段代码很简答,先看getByNick方法,根据用户名称获取用户信息,里面有一个去空格的操作(主要为了触发异常),正常调用是没有问题的,但如果传入null,就会抛出很常见且低级的空指针异常。我们看下调用代码:

 

   

[java] view plain copy
 
  1. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(  
  2.                 new String[] { "app-dubbo-consumer.xml" });  
  3.         context.start();  
  4.         IPersonService personService = context.getBean(IPersonService.class);  
  5.         // 正常输入参数  
  6.         outString(personService.getByNick("某某"));  
  7.         // 输入null  
  8.         outString(personService.getByNick(null));  

     运行后,首先会打印{"id":0,"name":"某某"},然后出现java.lang.NullPointerException,一切在我们的预料内,dubbo把服务端的空指针异常传递给客户端了。

 

     正常来说,空指针异常是不应该出现的,而且客户端遇到这个错误肯定直接懵了,所以我们做下简单的修改,服务端代码:

  

[java] view plain copy
 
  1. @Override  
  2.     public Person getByNick(String name) {  
  3.         if (name == null) {  
  4.             throw new RuntimeException("亲你咋没输入昵称呢");  
  5.         }  
  6.         name = name.trim();  
  7.         Person person = new Person();  
  8.         person.setId(ID.getAndIncrement());  
  9.         person.setName(name);  
  10.         return person;  
  11.     }  

         客户端再次调用结果:

 

  

[java] view plain copy
 
  1. Exception in thread "main" java.lang.RuntimeException: 亲你咋没输入昵称呢  
  2.     at org.dubbo.provider.PersonServiceImpl.getByNick(PersonServiceImpl.java:28)  
  3.     at com.alibaba.dubbo.common.bytecode.Wrapper1.invokeMethod(Wrapper1.java)  

     结果友好了多,甚至你可以直接对exception获取异常信息作为输出。

 

       上边提到过当服务端抛出一个dubbo可以处理传递的异常时,会直接在客户端上再次抛出,但不是所有的异常都是dubbo可以处理传递的,如下边的代码:

   

[java] view plain copy
 
  1. @Override  
  2.     public Person getByNick(String name) {  
  3.         if (name == null) {  
  4.             // 第三方异常  
  5.             throw new PersistenceException("mybatis插入异常");  
  6.         }  
  7.         name = name.trim();  
  8.         Person person = new Person();  
  9.         person.setId(ID.getAndIncrement());  
  10.         person.setName(name);  
  11.         return person;  
  12.     }  

 

        这里我们模拟抛出了一个mybatis的异常,在客户端调用会像上边的结果一样吗?答案是否定的,看下输出结果:

 

[java] view plain copy
 
  1. Exception in thread "main" java.lang.RuntimeException: org.apache.ibatis.exceptions.PersistenceException: mybatis插入异常  
  2. org.apache.ibatis.exceptions.PersistenceException: mybatis插入异常  
  3.     at org.dubbo.provider.PersonServiceImpl.getByNick(PersonServiceImpl.java:32)  
  4.     at com.alibaba.dubbo.common.bytecode.Wrapper1.invokeMethod(Wrapper1.java)  

       不要感觉奇怪,这个也是在可以接受的范围内,因为PersistenceException异常类在客户端是不存在的,所以不可能接收到PersistenceException异常,dubbo把他进行了封装。

 

        针对这点,在接口包中里面定义了一个全局的异常类,注意一定是接口所在的工程中,如:UicException(用户模块异常),这种方案也是官方建议的,服务端代码如下:

  

[java] view plain copy
 
  1. @Override  
  2.     public Person getByNick(String name) {  
  3.         if (name == null) {  
  4.             // 自定义异常  
  5.             throw new UicException("mybatis插入异常"0);  
  6.         }  
  7.         name = name.trim();  
  8.         Person person = new Person();  
  9.         person.setId(ID.getAndIncrement());  
  10.         person.setName(name);  
  11.         return person;  
  12.     }  

 

           错误信息如下

 

[java] view plain copy
 
  1. Exception in thread "main" org.dubbo.api.exception.UicException: mybatis插入异常  
  2.     at org.dubbo.provider.PersonServiceImpl.getByNick(PersonServiceImpl.java:30)  

         这个正是我们想要的异常信息,上边特别提到异常一定要在接口所在的工程中,如果异常类不在接口工程中,而是在另一个服务端和客户端都引入的包中呢?我们曾经碰到这样一个情况,有一个common的异常类放在一个很底层的工具包内,接口工程引入了这个包,在服务端抛出的异常都都是这个commonexception,一厢情愿的认为客户端会正常去捕获处理commonexception。

 

         但结果很意外,客户端出现的异常跟上边抛出的PersistenceException情况一样,dubbo用RuntimException进行了包装,我们无法从异常中获取有效的信息!遇到这种情况有点发懵,这个异常类在客户端和服务端都有呀,为啥不能正确接收呢。还好之前看dubbo源码的时候大概记得异常处理的位置,很好找到了目标代码:

 

[java] view plain copy
 
  1. if (result.hasException() && GenericService.class != invoker.getInterface()) {  
  2.                try {  
  3.                    Throwable exception = result.getException();  
  4.   
  5.                    // 如果是checked异常,直接抛出  
  6.                    if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) {  
  7.                        return result;  
  8.                    }  
  9.                    // 在方法签名上有声明,直接抛出  
  10.                    try {  
  11.                        Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());  
  12.                        Class<?>[] exceptionClassses = method.getExceptionTypes();  
  13.                        for (Class<?> exceptionClass : exceptionClassses) {  
  14.                            if (exception.getClass().equals(exceptionClass)) {  
  15.                                return result;  
  16.                            }  
  17.                        }  
  18.                    } catch (NoSuchMethodException e) {  
  19.                        return result;  
  20.                    }  
  21.   
  22.                    // 未在方法签名上定义的异常,在服务器端打印ERROR日志  
  23.                    logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()  
  24.                            + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()  
  25.                            + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);  
  26.   
  27.                    // 异常类和接口类在同一jar包里,直接抛出  
  28.                    String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());  
  29.                    String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());  
  30.                    if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){  
  31.                        return result;  
  32.                    }  
  33.                    // 是JDK自带的异常,直接抛出  
  34.                    String className = exception.getClass().getName();  
  35.                    if (className.startsWith("java.") || className.startsWith("javax.")) {  
  36.                        return result;  
  37.                    }  
  38.                    // 是Dubbo本身的异常,直接抛出  
  39.                    if (exception instanceof RpcException) {  
  40.                        return result;  
  41.                    }  
  42.   
  43.                    // 否则,包装成RuntimeException抛给客户端  
  44.                    return new RpcResult(new RuntimeException(StringUtils.toString(exception)));  
  45.                } catch (Throwable e) {  
  46.                    logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()  
  47.                            + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()  
  48.                            + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);  
  49.                    return result;  
  50.                }  
  51.            }  


          看完源码以后,做出了新的设计,CommonException不变,各个接口模块(maven工程为单位)单独定义异常对象继承CommonException(保证异常类和接口在同一模块,然后用里氏代换)每个模块抛出自己的模块异常(如用户模块抛出UicException),客户端中用CommonException统一捕获处理。

 

          这里还要定义两个拦截器,首先是服务端,保证所有抛出的异常是当前模块的异常,代码如下:

 

[java] view plain copy
 
  1. public class UICExceptionAspect {  
  2.     private static Log log = LogFactory.getLog(UICExceptionAspect.class);  
  3.   
  4.     public void afterThrowing(Exception ex) {  
  5.   
  6.         if (ex instanceof UICException) {  
  7.             throw (UICException) ex;  
  8.         } else if (ex instanceof DayimaException) {  
  9.             DayimaException de = (DayimaException) ex;  
  10.             throw new UICException(de.getErrCode(), de.getErrMsg());  
  11.         } else {  
  12.             log.error(ex.getMessage(), ex);  
  13.             throw new UICException("1", ex.getMessage());  
  14.         }  
  15.   
  16.     }  
  17. }  


         其次是客户端的,保证异常可以正确的友好的输出,所有CommonException可以直接输出(获取根据错误码获取错误信息),非CommonException异常根据自己需要去处理,如果是dubbo自带异常肯定要屏蔽异常信息,如打印日志后输出“网络异常”。

 

       还有另一种dubbo调用方案,普通service层外边嵌套一层用来做dubbo的服务,普通service层处理了事务之类,dubbo服务层每一个方法都是客户端要引用的,直接调用普通service层方法,但做了手动的try catch处理,封装自己的返回码,客户端只需要根据返回码去做处理,这种开发成本和文档成本有点高,没太深入去考虑。

        以上是我自己工作中的dubbo异常实践,以后会继续写些其他的心得,记录自己的成长。

文章评论

十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
程序员都该阅读的书
程序员都该阅读的书
每天工作4小时的程序员
每天工作4小时的程序员
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
漫画:程序员的工作
漫画:程序员的工作
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
Java程序员必看电影
Java程序员必看电影
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
一个程序员的时间管理
一个程序员的时间管理
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
如何成为一名黑客
如何成为一名黑客
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
那些争议最大的编程观点
那些争议最大的编程观点
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
程序员和编码员之间的区别
程序员和编码员之间的区别
编程语言是女人
编程语言是女人
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
我是如何打败拖延症的
我是如何打败拖延症的
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
我的丈夫是个程序员
我的丈夫是个程序员
代码女神横空出世
代码女神横空出世
 程序员的样子
程序员的样子
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
旅行,写作,编程
旅行,写作,编程
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
鲜为人知的编程真相
鲜为人知的编程真相
10个调试和排错的小建议
10个调试和排错的小建议
程序员必看的十大电影
程序员必看的十大电影
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有