Skip to content

Commit

Permalink
feat: better unwrapping of java exception (#74)
Browse files Browse the repository at this point in the history
* feat: better unwrapping of java exception

* srv-side exception processing

* modify FromError

* fix typo problem

* add basic Exception type

* modify NewException returned value

* introduct java.lang.Exception

* gofumpt format

* add docs

* modify indentation of docs
  • Loading branch information
DMwangnima authored Jan 3, 2024
1 parent c3b7d6b commit 708aa3f
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 45 deletions.
83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,89 @@ service EchoService {
}
```

### 异常处理

**codec-dubbo** 将异常定义为实现了以下接口的错误,你可以像处理错误一样处理 java 中的异常:
```go
type Throwabler interface {
Error() string
JavaClassName() string
GetStackTrace() []StackTraceElement
}
```

#### 常见异常

**codec-dubbo**[pkg/hessian2/exception](https://github.com/kitex-contrib/codec-dubbo/tree/main/pkg/hessian2/exception)目录下提供了java中常见的异常,目前支持 java.lang.Exception ,更多异常将在后续迭代中加入。
常见异常无需命令行工具的支持,直接引用即可。

##### client端提取异常

```go
import (
hessian2_exception "github.com/kitex-contrib/codec-dubbo/pkg/hessian2/exception"
)

func main() {
resp, err := cli.Greet(context.Background(), true)
if err != nil {
// FromError 返回 Throwabler
exceptionRaw, ok := hessian2_exception.FromError(err)
if !ok {
// 视作常规错误处理
} else {
// 若不关心 exceptionRaw 的具体类型,直接调用 Throwabler 提供的方法即可
klog.Errorf("get %s type Exception", exceptionRaw.JavaClassName())

// 若想获得 exceptionRaw 的具体类型,需要进行类型转换,但前提是已知该具体类型
exception := exceptionRaw.(*hessian2_exception.Exception)
}
}
}
```

##### server端返回异常

```go
import (
hessian2_exception "github.com/kitex-contrib/codec-dubbo/pkg/hessian2/exception"
)

func (s *GreetServiceImpl) Greet(ctx context.Context, req string) (resp string, err error) {
return "", hessian2_exception.NewException("Your detailed message")
}
```

#### 自定义异常

java 中的自定义业务异常往往会继承一个基础异常,这里以 CustomizedException 为例,CustomizedException 继承了 java.lang.Exception:
```java
public class CustomizedException extends Exception {
private final String customizedMessage;
public CustomizedException(String customizedMessage) {
super();
this.customizedMessage = customizedMessage;
}

public String getCustomizedMessage() {
return this.customizedMessage;
}
}
```

为了在 kitex 侧定义与之对应的异常,在 **thrift** 中编写如下定义:

```thrift
exception CustomizedException {
1: required java.Exception exception (thrift.nested="true")
2: required string customizedMessage
}(JavaClassName="org.cloudwego.kitex.samples.api.CustomizedException")
```

[其它类型](#其它类型javalangobject-javautildate)一样,需要在使用 **kitex** 脚手架工具生成代码时添加 `-hessian2 java_extension` 参数来拉取拓展包。

使用方法与[常见异常](#常见异常)一致。

## 服务注册与发现

> 目前仅支持基于 zookeeper 的**接口级**服务发现与服务注册,**应用级**服务发现以及服务注册计划在后续迭代中支持。
Expand Down
84 changes: 84 additions & 0 deletions README_ENG.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,90 @@ service EchoService {
}
```
### Exception Handling
**codec-dubbo** defines exceptions as **error** that implement the following interface. You can handle exceptions in Java as you could handle **error** in Go:
```go
type Throwabler interface {
Error() string
JavaClassName() string
GetStackTrace() []StackTraceElement
}
```
#### Common Exceptions
**codec-dubbo** provides commonly used Java exceptions in the [pkg/hessian2/exception](https://github.com/kitex-contrib/codec-dubbo/tree/main/pkg/hessian2/exception) directory. Currently, it supports java.lang.Exception, and more exceptions will be added in subsequent iterations.
Common exceptions do not require command line tool support and could be directly referenced.
##### Extracting Exception on the Client Side
```go
import (
hessian2_exception "github.com/kitex-contrib/codec-dubbo/pkg/hessian2/exception"
)
func main() {
resp, err := cli.Greet(context.Background(), true)
if err != nil {
// FromError returns a Throwabler
exceptionRaw, ok := hessian2_exception.FromError(err)
if !ok {
// Treat as a regular error handling
} else {
// If you are not concerned with the specific type of exceptionRaw, just call the methods provided by Throwabler
klog.Errorf("get %s type Exception", exceptionRaw.JavaClassName())
// If you want to obtain the specific type of exceptionRaw, you need to perform a type conversion, but this requires knowing the specific type
exception := exceptionRaw.(*hessian2_exception.Exception)
}
}
}
```
##### Returning Exception on the Server Side
```go
import (
hessian2_exception "github.com/kitex-contrib/codec-dubbo/pkg/hessian2/exception"
)
func (s *GreetServiceImpl) Greet(ctx context.Context, req string) (resp string, err error) {
return "", hessian2_exception.NewException("Your detailed message")
}
```
#### Customized Exceptions
Customized business exceptions in Java often inherit a base exception. Here, we use CustomizedException as an example, which inherits from java.lang.Exception:
```java
public class CustomizedException extends Exception {
private final String customizedMessage;
public CustomizedException(String customizedMessage) {
super();
this.customizedMessage = customizedMessage;
}
public String getCustomizedMessage() {
return this.customizedMessage;
}
}
```
To define a corresponding exception on the kitex side, write the following definition in **thrift**:
```thrift
exception CustomizedException {
1: required java.Exception exception (thrift.nested="true")
2: required string customizedMessage
}(JavaClassName="org.cloudwego.kitex.samples.api.CustomizedException")
```
Like [other types](#other-types--javalangobject-javautildate-), you need to add the `-hessian2 java_extension` parameter when generating code with the **kitex** scaffolding tool to pull the extension package.
The usage is consistent with [Common Exceptions](#common-exceptions).
## Service Registry and Service Discovery
Expand Down
28 changes: 15 additions & 13 deletions java/java-reflection.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,26 @@ import (
// IDL Path: java.thrift

var file_java_thrift_go_types = []interface{}{
(*Object)(nil), // Struct 0: java.Object
(*Date)(nil), // Struct 1: java.Date
(*Object)(nil), // Struct 0: java.Object
(*Date)(nil), // Struct 1: java.Date
(*Exception)(nil), // Struct 2: java.Exception
}

var (
file_java_thrift *thrift_reflection.FileDescriptor
file_idl_java_rawDesc = []byte{
0x1f, 0x8b, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x8c, 0x8f, 0x4d, 0xe, 0x82, 0x30,
0x10, 0x85, 0x9f, 0xa5, 0xe0, 0xcf, 0x50, 0xeb, 0x49, 0x7a, 0x9, 0x5d, 0xb9, 0xd0, 0x33, 0x8c,
0xa6, 0x22, 0xa4, 0x6a, 0x22, 0x95, 0xf3, 0x9b, 0x29, 0x65, 0x65, 0x4c, 0x58, 0x7d, 0x4d, 0xfa,
0xde, 0xf7, 0x32, 0x84, 0x5, 0x0, 0xea, 0x78, 0x60, 0x17, 0xef, 0xef, 0xf6, 0x16, 0xd, 0x14,
0x11, 0x0, 0x18, 0x14, 0xe9, 0x21, 0x1, 0xd5, 0xbc, 0x0, 0x68, 0x89, 0x59, 0xe8, 0x5a, 0xbe,
0x2d, 0x4a, 0xa1, 0xfa, 0x35, 0x10, 0x14, 0x80, 0xea, 0x7c, 0xe9, 0xfc, 0x35, 0x5a, 0x14, 0xf5,
0x68, 0xd3, 0x64, 0xb3, 0xcd, 0x1c, 0x79, 0xe0, 0x7d, 0xe0, 0xbe, 0x3f, 0xf1, 0xc3, 0x4f, 0x1b,
0xbb, 0xa4, 0x8, 0xfc, 0x6c, 0xdc, 0x58, 0x25, 0x94, 0xd2, 0xc4, 0xbf, 0x5, 0x7d, 0xe0, 0xe8,
0xe7, 0xfb, 0xb7, 0x49, 0xf0, 0x89, 0x6d, 0x70, 0x52, 0x9c, 0xec, 0x16, 0x55, 0xbe, 0x67, 0x99,
0xb9, 0xca, 0x5c, 0x67, 0x6e, 0x12, 0xf1, 0xd, 0x0, 0x0, 0xff, 0xff, 0xaf, 0x1, 0x47, 0x6a,
0x2b, 0x1, 0x0, 0x0,
0x1f, 0x8b, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x8c, 0x90, 0x41, 0xae, 0x82, 0x30,
0x18, 0x84, 0xe7, 0x41, 0xe1, 0x89, 0x3f, 0x58, 0x13, 0xef, 0xc1, 0x25, 0xd4, 0x8d, 0xb, 0x3d,
0xc3, 0x2f, 0xa9, 0x8, 0xa9, 0x60, 0xa4, 0x12, 0x8f, 0x6f, 0x5a, 0x8a, 0x2e, 0x8c, 0x91, 0xd5,
0xd7, 0xa4, 0x33, 0xdf, 0xa4, 0x25, 0xfc, 0x1, 0xa0, 0x9a, 0x7b, 0xce, 0xcd, 0xf9, 0x56, 0x9d,
0x4c, 0x86, 0x80, 0x8, 0x0, 0x32, 0x84, 0xee, 0x60, 0x3, 0x41, 0xd9, 0x2, 0x10, 0x36, 0x26,
0x21, 0x52, 0x7b, 0x2d, 0x11, 0x59, 0x86, 0x9f, 0x6, 0x42, 0x0, 0x20, 0x3e, 0x1c, 0x6b, 0x55,
0x18, 0x89, 0x30, 0x1d, 0x6c, 0x82, 0xa4, 0xb7, 0x65, 0x3b, 0xee, 0x79, 0xad, 0xb9, 0xeb, 0xf6,
0x7c, 0x51, 0xe3, 0xc6, 0xd2, 0x29, 0x34, 0x37, 0x65, 0x3e, 0x54, 0x9, 0x91, 0x6d, 0xe2, 0xdb,
0x82, 0xd8, 0xb0, 0x51, 0xd3, 0xfd, 0xb, 0x27, 0xb8, 0x9b, 0x4a, 0xe7, 0xb6, 0xf8, 0xcb, 0x9e,
0x6c, 0x1f, 0x85, 0xba, 0x9a, 0xaa, 0x6d, 0xa6, 0x4f, 0xac, 0xde, 0x4f, 0x78, 0xb5, 0xc7, 0x1d,
0x89, 0xd8, 0xff, 0xdb, 0xbf, 0xe7, 0xcc, 0x33, 0xf1, 0x9c, 0x3b, 0xe2, 0x19, 0x0, 0x0, 0xff,
0xff, 0xf9, 0xde, 0xba, 0xfa, 0x93, 0x1, 0x0, 0x0,
}
)

Expand Down
8 changes: 8 additions & 0 deletions java/java.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ package java

import (
"time"

hessian2_exception "github.com/kitex-contrib/codec-dubbo/pkg/hessian2/exception"
)

type Object = interface{}
Expand All @@ -34,3 +36,9 @@ type Date = time.Time
func NewDate() *Date {
return new(Date)
}

type Exception = hessian2_exception.Exception

func NewException(detailMessage string) *Exception {
return hessian2_exception.NewException(detailMessage)
}
2 changes: 2 additions & 0 deletions java/java.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ namespace go java
struct Object {} (JavaClassName="java.lang.Object")

struct Date {} (JavaClassName="java.util.Date")

struct Exception {} (JavaClassName="java.lang.Exception")
7 changes: 5 additions & 2 deletions pkg/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"context"
"fmt"

hessian2_exception "github.com/kitex-contrib/codec-dubbo/pkg/hessian2/exception"

"github.com/kitex-contrib/codec-dubbo/registries"

"github.com/cloudwego/kitex/pkg/remote"
Expand Down Expand Up @@ -182,12 +184,13 @@ func (m *DubboCodec) encodeExceptionPayload(ctx context.Context, message remote.
if !ok {
return nil, fmt.Errorf("%v exception does not implement Error", data)
}
if exception, ok := data.(hessian2.Throwabler); ok {
// exception is wrapped by kerrors.DetailedError
if exception, ok := hessian2_exception.FromError(errRaw); ok {
if err := encoder.Encode(exception); err != nil {
return nil, err
}
} else {
if err := encoder.Encode(hessian2.NewException(errRaw.Error())); err != nil {
if err := encoder.Encode(hessian2_exception.NewException(errRaw.Error())); err != nil {
return nil, err
}
}
Expand Down
30 changes: 0 additions & 30 deletions pkg/hessian2/exception.go

This file was deleted.

59 changes: 59 additions & 0 deletions pkg/hessian2/exception/exception.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2023 CloudWeGo Authors
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package exception

import (
"github.com/apache/dubbo-go-hessian2/java_exception"
)

type Throwabler = java_exception.Throwabler

type Exception = java_exception.Exception

func NewException(detailMessage string) *Exception {
return java_exception.NewException(detailMessage)
}

// FromError extracts Throwabler from passed err.
//
// - If err is nil, it returns nil and false
//
// - If err implements Unwrap(), it would unwrap err until getting the real cause.
// Then it would check cause whether implementing Throwabler. If yes, it returns
// Throwabler and true.
//
// If not, it checks err whether implementing Throwabler directly. If yes,
// it returns Throwabler and true.
func FromError(err error) (Throwabler, bool) {
if err == nil {
return nil, false
}
for {
if wrapper, ok := err.(interface{ Unwrap() error }); ok {
err = wrapper.Unwrap()
} else {
break
}
}
if exception, ok := err.(Throwabler); ok {
return exception, true
}
return nil, false
}
Loading

0 comments on commit 708aa3f

Please sign in to comment.