weibocom / motan

A cross-language remote procedure call(RPC) framework for rapid development of high performance distributed services.
Other
5.9k stars 1.78k forks source link

服务端抛出自定义业务异常,在客户端得到的也是MotanServiceException异常信息。 #309

Closed cheniqit closed 7 years ago

cheniqit commented 7 years ago

跟踪到如下图的客户端的异常信息位置,抛出的也是MotanServiceException异常信息,怎么样能在客户端得到自定义异常信息。 看到之前提出的issue是说可以的,但是我跟踪了代码为啥就不行? 一直走到图中biz exception cause is null这个分支。 补充:服务端和客户端都都是用自定义业务异常类的。

image

estn commented 7 years ago

你的客户端也要有相应的异常类就OK

rayzhang0603 commented 7 years ago

有实际抛出的具体异常信息吗?

cheniqit commented 7 years ago

@estn 客户端和服务端都引用了相同的jar,这个jar中已经包含这个异常类的信息。 具体异常类是这样的 ** public class MyException extends RuntimeException implements Serializable { private Integer code;

public MyException(String msg) {
    super(msg);
}

public MyException(Integer code, String message) {
    super(message);
    this.code = code;
}

public Integer getCode() {
    return this.code;
}

public void setCode(Integer code) {
    this.code = code;
}

} **

cheniqit commented 7 years ago

@rayzhang0603 在客户端报出来的信息是这样的。

补充一下我在服务端抛出来的是一个运行时异常,我看到你的原代码中有对运行时异常作一个特别的处理,不知道是不是这个原因。

com.weibo.api.motan.exception.MotanServiceException: error_message: biz exception cause is null, status: 503, error_code: 10001,r=null at com.weibo.api.motan.proxy.RefererInvocationHandler.invoke(RefererInvocationHandler.java:127) at com.sun.proxy.$Proxy281.createPayCash(Unknown Source) at com.mk.payapi.cash.controller.PayCashController.create(PayCashController.java:64) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:777) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:706) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868) at javax.servlet.http.HttpServlet.service(HttpServlet.java:707) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842) at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)

cheniqit commented 7 years ago

@rayzhang0603 @estn 我用的是当前最新的版本0.2.2

rayzhang0603 commented 7 years ago

知道原因了,MyException必须有无参构造函数,否则序列化时无法初始化这个对象。 这个异常表示取到了MotanBizException异常,但是没有取到实际的cause,因为无法序列化MyException。

cheniqit commented 7 years ago

@rayzhang0603 已经构造了一个,但是很可惜打印出来的还是上面的异常信息

看到代码中有这样一段,以为需要继承Exception才行,但是尝试了一下没有用还是报上面的错误的信息呢。

Throwable t = var14.getCause(); if(t != null && t instanceof Exception) { throw t; } else { String msg = t == null?"biz exception cause is null":"biz exception cause is throwable error:" + t.getClass() + ", errmsg:" + t.getMessage(); throw new MotanServiceException(msg, MotanErrorMsgConstant.SERVICE_DEFAULT_ERROR); }

cheniqit commented 7 years ago

现在跟踪到的情况是加了无参构造函数之后

Throwable t = var14.getCause();
这里得到的还是空。

rayzhang0603 commented 7 years ago

可以看看server端的error日志,看看有没有关键字“Exception caught when method invoke”,如果没有说明server端产生的异常有问题。可以debug server端DefaultProvider.invoke() line 60看看具体异常是什么。 如果server端正常,可以debug client端看一下DefaultRpcCodec.decodeResponse() line373,看看deserialize的结果是否正常。

cheniqit commented 7 years ago

服务端有打印你所说的信息,应该是没问题的。 DefaultRpcCodec.decodeResponse() 在调用this.deserialize方法返回result之后的对象信息中是一个503的proprovider error信息,但是调用该resulte的.getCause()方法发现为空,应该是这里deserialize之后将cause信息丢失了,我用的是hprose进行传输数据的。请大神帮忙看看应该会是神马原因?

image

image

cheniqit commented 7 years ago

hprose用的是2.0.30的版本

cheniqit commented 7 years ago

果真是deserialize的问题我写了如下代码,1@@@@处打印有值而2@@@@没有值 image

rayzhang0603 commented 7 years ago

@andot 呼叫小马哥帮忙看看什么原因

cheniqit commented 7 years ago

@rayzhang0603 我写了如下代码,同样2@@@@有值,唯一不同的是我的异常类重写了。我把你的代码重写编译我试下看,有结果的话可以了结。 @Override public Throwable getCause(){ return (this.cause ==null ?this :this.cause); }

image

andot commented 7 years ago

Hprose 的序列化是按照跨语言序列化设计的,因此没有专门针对异常类做特别处理。默认是序列化 public 可读写属性和 public 字段的。对于异常类,可读写属性只有 stackTrace 这一个。因此序列化时,也只有它可以被序列化,其它的只读属性因为没有对应的 set 方法,因此无法被序列化。如果要修改序列化方式,可以改为按字段序列化,在按字段序列化的情况下,所有的字段(包括私有和保护字段)都会被序列化。

如果需要对异常类做特殊的序列化和反序列化处理,你可以参考 hprose 自定义序列化和反序列化 来自己定制。

cheniqit commented 7 years ago

谢谢 @andot 的回复很有用。也谢谢作者 @rayzhang0603 的支持,反馈很及时。 现在我来反馈下我对这个问题的改动吧,直接将motan的源代码进行了改动步骤如下。 第一步 将motan写的hprose的序列化HproseSerialization类进行改动如下:

@Override public byte[] serialize(Object data) throws IOException { ByteBufferStream stream = null; try { stream = HproseFormatter.serialize(data, HproseMode.FieldMode); byte[] result = stream.toArray(); return result; } finally { if (stream != null) { stream.close(); } } }

@SuppressWarnings("unchecked")
@Override
public <T> T deserialize(byte[] data, Class<T> clz) throws IOException {
    //return new HproseReader(data).unserialize(clz);
    return HproseFormatter.unserialize(data, HproseMode.FieldMode, clz);
}

第二步因为motan的RefererInvocationHandler类的代码片段判断了是否有cause所以业务异常需要复写 getCause()方法,将自己的异常类信息设置进去,当然你也可以直接将motan的这个判断去掉。

Throwable t = e.getCause(); // 只抛出Exception,防止抛出远程的Error if (t != null && t instanceof Exception) { throw t; } else { String msg = t == null ? "biz exception cause is null" : ("biz exception cause is throwable error:" + t.getClass()

  • ", errmsg:" + t.getMessage()); throw new MotanServiceException(msg, MotanErrorMsgConstant.SERVICE_DEFAULT_ERROR); }
cheniqit commented 7 years ago

@rayzhang0603 在跟踪代码的时候发现一个bug 如图DefaultProvider类中第63行应该写为 response.setException(new MotanServiceException("provider has encountered a fatal error!", e)); 而不是 response.setException(new MotanServiceException("provider has encountered a fatal error!", e.getCause()));

image

rayzhang0603 commented 7 years ago

@cheniqit 这里不是bug,invoke时返回的异常一般是InvocationTargetException,其中的cause才是实际的业务异常,这里的逻辑是需要获取到实际的业务异常