You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by al...@apache.org on 2021/08/27 14:13:42 UTC

[dubbo-go] branch 1.5 updated: fix generic call bug (#1413)

This is an automated email from the ASF dual-hosted git repository.

alexstocks pushed a commit to branch 1.5
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git


The following commit(s) were added to refs/heads/1.5 by this push:
     new 826f525  fix generic call bug (#1413)
826f525 is described below

commit 826f525903f031d97c2ee051dfc2de38781d83e1
Author: alchemy-lee <27...@qq.com>
AuthorDate: Fri Aug 27 22:13:36 2021 +0800

    fix generic call bug (#1413)
---
 filter/filter_impl/generic_filter.go               | 183 +++++------------
 filter/filter_impl/generic_filter_test.go          | 191 +++++++----------
 ...eneric_filter.go => generic_generalizer_map.go} |  82 ++++----
 ...ter_test.go => generic_generalizer_map_test.go} | 156 +++++++++++---
 filter/filter_impl/generic_service_filter.go       | 111 +++++-----
 filter/filter_impl/generic_service_filter_test.go  | 228 ++++++++++++---------
 protocol/dubbo/hessian2/hessian_request.go         |  91 +-------
 protocol/dubbo/hessian2/java_class.go              | 200 ++++++++++++++++++
 protocol/dubbo/hessian2/java_class_test.go         | 132 ++++++++++++
 9 files changed, 802 insertions(+), 572 deletions(-)

diff --git a/filter/filter_impl/generic_filter.go b/filter/filter_impl/generic_filter.go
index 86f8cfd..facdfda 100644
--- a/filter/filter_impl/generic_filter.go
+++ b/filter/filter_impl/generic_filter.go
@@ -19,9 +19,7 @@ package filter_impl
 
 import (
 	"context"
-	"reflect"
 	"strings"
-	"time"
 )
 
 import (
@@ -54,23 +52,47 @@ type GenericFilter struct{}
 
 // Invoke turns the parameters to map for generic method
 func (ef *GenericFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
-	if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 {
-		oldArguments := invocation.Arguments()
+	if isCallingToGenericService(invoker, invocation) {
 
-		if oldParams, ok := oldArguments[2].([]interface{}); ok {
-			newParams := make([]hessian.Object, 0, len(oldParams))
-			for i := range oldParams {
-				newParams = append(newParams, hessian.Object(objToMap(oldParams[i])))
+		mtdname := invocation.MethodName()
+		oldargs := invocation.Arguments()
+
+		types := make([]string, 0, len(oldargs))
+		args := make([]hessian.Object, 0, len(oldargs))
+
+		// get generic info from attachments of invocation, the default value is "true"
+		//generic := invocation.AttachmentsByKey(constant.GENERIC_KEY, GENERIC_SERIALIZATION_DEFAULT)
+		// get generalizer according to value in the `generic`
+		g := GetMapGeneralizer()
+
+		for _, arg := range oldargs {
+			// use the default generalizer(MapGeneralizer)
+			typ, err := g.GetType(arg)
+			if err != nil {
+				logger.Errorf("failed to get type, %v", err)
 			}
-			newArguments := []interface{}{
-				oldArguments[0],
-				oldArguments[1],
-				newParams,
+			obj, err := g.Generalize(arg)
+			if err != nil {
+				logger.Errorf("generalization failed, %v", err)
+				return invoker.Invoke(ctx, invocation)
 			}
-			newInvocation := invocation2.NewRPCInvocation(invocation.MethodName(), newArguments, invocation.Attachments())
-			newInvocation.SetReply(invocation.Reply())
-			return invoker.Invoke(ctx, newInvocation)
+			types = append(types, typ)
+			args = append(args, obj)
 		}
+
+		// construct a new invocation for generic call
+		newargs := []interface{}{
+			mtdname,
+			types,
+			args,
+		}
+		newivc := invocation2.NewRPCInvocation(constant.GENERIC, newargs, invocation.Attachments())
+		newivc.SetReply(invocation.Reply())
+		newivc.Attachments()[constant.GENERIC_KEY] = invoker.GetURL().GetParam(constant.GENERIC_KEY, "")
+
+		return invoker.Invoke(ctx, newivc)
+	} else if isMakingAGenericCall(invoker, invocation) {
+		invocation.Attachments()[constant.GENERIC_KEY] = invoker.GetURL().GetParam(constant.GENERIC_KEY, "")
 	}
 	return invoker.Invoke(ctx, invocation)
 }
@@ -86,125 +108,22 @@ func GetGenericFilter() filter.Filter {
 	return &GenericFilter{}
 }
 
-func objToMap(obj interface{}) interface{} {
-	if obj == nil {
-		return obj
-	}
-
-	t := reflect.TypeOf(obj)
-	v := reflect.ValueOf(obj)
-
-	// if obj is a POJO, get the struct from the pointer (if it is a pointer)
-	pojo, isPojo := obj.(hessian.POJO)
-	if isPojo {
-		for t.Kind() == reflect.Ptr {
-			t = t.Elem()
-			v = v.Elem()
-		}
-	}
-
-	switch t.Kind() {
-	case reflect.Struct:
-		result := make(map[string]interface{}, t.NumField())
-		if isPojo {
-			result["class"] = pojo.JavaClassName()
-		}
-		for i := 0; i < t.NumField(); i++ {
-			field := t.Field(i)
-			value := v.Field(i)
-			kind := value.Kind()
-			if !value.CanInterface() {
-				logger.Debugf("objToMap for %v is skipped because it couldn't be converted to interface", field)
-				continue
-			}
-			valueIface := value.Interface()
-			switch kind {
-			case reflect.Ptr:
-				if value.IsNil() {
-					setInMap(result, field, nil)
-					continue
-				}
-				setInMap(result, field, objToMap(valueIface))
-			case reflect.Struct, reflect.Slice, reflect.Map:
-				if isPrimitive(valueIface) {
-					logger.Warnf("\"%s\" is primitive. The application may crash if it's transferred between "+
-						"systems implemented by different languages, e.g. dubbo-go <-> dubbo-java. We recommend "+
-						"you represent the object by basic types, like string.", value.Type())
-					setInMap(result, field, valueIface)
-					continue
-				}
-
-				setInMap(result, field, objToMap(valueIface))
-			default:
-				setInMap(result, field, valueIface)
-			}
-		}
-		return result
-	case reflect.Array, reflect.Slice:
-		value := reflect.ValueOf(obj)
-		newTemps := make([]interface{}, 0, value.Len())
-		for i := 0; i < value.Len(); i++ {
-			newTemp := objToMap(value.Index(i).Interface())
-			newTemps = append(newTemps, newTemp)
-		}
-		return newTemps
-	case reflect.Map:
-		newTempMap := make(map[interface{}]interface{}, v.Len())
-		iter := v.MapRange()
-		for iter.Next() {
-			if !iter.Value().CanInterface() {
-				continue
-			}
-			key := iter.Key()
-			mapV := iter.Value().Interface()
-			newTempMap[mapKey(key)] = objToMap(mapV)
-		}
-		return newTempMap
-	case reflect.Ptr:
-		return objToMap(v.Elem().Interface())
-	default:
-		return obj
-	}
-}
-
-// mapKey converts the map key to interface type
-func mapKey(key reflect.Value) interface{} {
-	switch key.Kind() {
-	case reflect.Bool, reflect.Int, reflect.Int8,
-		reflect.Int16, reflect.Int32, reflect.Int64,
-		reflect.Uint, reflect.Uint8, reflect.Uint16,
-		reflect.Uint32, reflect.Uint64, reflect.Float32,
-		reflect.Float64, reflect.String:
-		return key.Interface()
-	default:
-		name := key.String()
-		if name == "class" {
-			panic(`"class" is a reserved keyword`)
-		}
-		return name
-	}
+// isCallingToGenericService check if it calls to a generic service
+func isCallingToGenericService(invoker protocol.Invoker, invocation protocol.Invocation) bool {
+	return isGeneric(invoker.GetURL().GetParam(constant.GENERIC_KEY, "")) &&
+		invocation.MethodName() != constant.GENERIC
 }
 
-// setInMap sets the struct into the map using the tag or the name of the struct as the key
-func setInMap(m map[string]interface{}, structField reflect.StructField, value interface{}) (result map[string]interface{}) {
-	result = m
-	if tagName := structField.Tag.Get("m"); tagName == "" {
-		result[toUnexport(structField.Name)] = value
-	} else {
-		result[tagName] = value
-	}
-	return
+// isMakingAGenericCall check if it is making a generic call to a generic service
+func isMakingAGenericCall(invoker protocol.Invoker, invocation protocol.Invocation) bool {
+	return isGeneric(invoker.GetURL().GetParam(constant.GENERIC_KEY, "")) &&
+		invocation.MethodName() == constant.GENERIC &&
+		invocation.Arguments() != nil &&
+		len(invocation.Arguments()) == 3
 }
 
-// toUnexport is to lower the first letter
-func toUnexport(a string) string {
-	return strings.ToLower(a[:1]) + a[1:]
-}
-
-// isPrimitive determines if the object is primitive
-func isPrimitive(obj interface{}) bool {
-	if _, ok := obj.(time.Time); ok {
-		return true
-	}
-	return false
+// isGeneric receives a generic field from url of invoker to determine whether the service is generic or not
+func isGeneric(generic string) bool {
+	lowerGeneric := strings.ToLower(generic)
+	return lowerGeneric == GENERIC_SERIALIZATION_DEFAULT
 }
diff --git a/filter/filter_impl/generic_filter_test.go b/filter/filter_impl/generic_filter_test.go
index 4a1f12e..bc861fd 100644
--- a/filter/filter_impl/generic_filter_test.go
+++ b/filter/filter_impl/generic_filter_test.go
@@ -18,136 +18,83 @@
 package filter_impl
 
 import (
-	"reflect"
+	"context"
+	"net/url"
 	"testing"
-	"time"
 )
 
 import (
+	hessian "github.com/apache/dubbo-go-hessian2"
+	"github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
 )
 
-func TestStruct2MapAll(t *testing.T) {
-	var testData struct {
-		AaAa string `m:"aaAa"`
-		BaBa string
-		CaCa struct {
-			AaAa string
-			BaBa string `m:"baBa"`
-			XxYy struct {
-				xxXx string `m:"xxXx"`
-				Xx   string `m:"xx"`
-			} `m:"xxYy"`
-		} `m:"caCa"`
-		DaDa time.Time
-		EeEe int
-	}
-	testData.AaAa = "1"
-	testData.BaBa = "1"
-	testData.CaCa.BaBa = "2"
-	testData.CaCa.AaAa = "2"
-	testData.CaCa.XxYy.xxXx = "3"
-	testData.CaCa.XxYy.Xx = "3"
-	testData.DaDa = time.Date(2020, 10, 29, 2, 34, 0, 0, time.Local)
-	testData.EeEe = 100
-	m := objToMap(testData).(map[string]interface{})
-	assert.Equal(t, "1", m["aaAa"].(string))
-	assert.Equal(t, "1", m["baBa"].(string))
-	assert.Equal(t, "2", m["caCa"].(map[string]interface{})["aaAa"].(string))
-	assert.Equal(t, "3", m["caCa"].(map[string]interface{})["xxYy"].(map[string]interface{})["xx"].(string))
-
-	assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"]).Kind())
-	assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"].(map[string]interface{})["xxYy"]).Kind())
-	assert.Equal(t, "2020-10-29 02:34:00", m["daDa"].(time.Time).Format("2006-01-02 15:04:05"))
-	assert.Equal(t, 100, m["eeEe"].(int))
-}
-
-type testStruct struct {
-	AaAa string
-	BaBa string `m:"baBa"`
-	XxYy struct {
-		xxXx string `m:"xxXx"`
-		Xx   string `m:"xx"`
-	} `m:"xxYy"`
-}
-
-func TestStruct2MapAllSlice(t *testing.T) {
-	var testData struct {
-		AaAa string `m:"aaAa"`
-		BaBa string
-		CaCa []testStruct `m:"caCa"`
-	}
-	testData.AaAa = "1"
-	testData.BaBa = "1"
-	var tmp testStruct
-	tmp.BaBa = "2"
-	tmp.AaAa = "2"
-	tmp.XxYy.xxXx = "3"
-	tmp.XxYy.Xx = "3"
-	testData.CaCa = append(testData.CaCa, tmp)
-	m := objToMap(testData).(map[string]interface{})
-
-	assert.Equal(t, "1", m["aaAa"].(string))
-	assert.Equal(t, "1", m["baBa"].(string))
-	assert.Equal(t, "2", m["caCa"].([]interface{})[0].(map[string]interface{})["aaAa"].(string))
-	assert.Equal(t, "3", m["caCa"].([]interface{})[0].(map[string]interface{})["xxYy"].(map[string]interface{})["xx"].(string))
-
-	assert.Equal(t, reflect.Slice, reflect.TypeOf(m["caCa"]).Kind())
-	assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"].([]interface{})[0].(map[string]interface{})["xxYy"]).Kind())
-}
-
-func TestStruct2MapAllMap(t *testing.T) {
-	var testData struct {
-		AaAa   string
-		Baba   map[string]interface{}
-		CaCa   map[string]string
-		DdDd   map[string]interface{}
-		IntMap map[int]interface{}
-	}
-	testData.AaAa = "aaaa"
-	testData.Baba = make(map[string]interface{})
-	testData.CaCa = make(map[string]string)
-	testData.DdDd = nil
-	testData.IntMap = make(map[int]interface{})
-
-	testData.Baba["kk"] = 1
-	var structData struct {
-		Str string
-	}
-	structData.Str = "str"
-	testData.Baba["struct"] = structData
-	testData.Baba["nil"] = nil
-	testData.CaCa["k1"] = "v1"
-	testData.CaCa["kv2"] = "v2"
-	testData.IntMap[1] = 1
-	m := objToMap(testData)
-
-	assert.Equal(t, reflect.Map, reflect.TypeOf(m).Kind())
-	mappedStruct := m.(map[string]interface{})
-	assert.Equal(t, reflect.String, reflect.TypeOf(mappedStruct["aaAa"]).Kind())
-	assert.Equal(t, reflect.Map, reflect.TypeOf(mappedStruct["baba"]).Kind())
-	assert.Equal(t, reflect.Map, reflect.TypeOf(mappedStruct["baba"].(map[interface{}]interface{})["struct"]).Kind())
-	assert.Equal(t, "str", mappedStruct["baba"].(map[interface{}]interface{})["struct"].(map[string]interface{})["str"])
-	assert.Equal(t, nil, mappedStruct["baba"].(map[interface{}]interface{})["nil"])
-	assert.Equal(t, reflect.Map, reflect.TypeOf(mappedStruct["caCa"]).Kind())
-	assert.Equal(t, reflect.Map, reflect.TypeOf(mappedStruct["ddDd"]).Kind())
-	intMap := mappedStruct["intMap"]
-	assert.Equal(t, reflect.Map, reflect.TypeOf(intMap).Kind())
-	assert.Equal(t, 1, intMap.(map[interface{}]interface{})[1])
-}
-
-type mockParent struct {
-	Children []*mockChild
-}
-
-func (p *mockParent) JavaClassName() string {
-	return "org.apache.dubbo.mockParent"
-}
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/protocol"
+	"github.com/apache/dubbo-go/protocol/invocation"
+	"github.com/apache/dubbo-go/protocol/mock"
+)
 
-type mockChild struct {
-	Name string
+// test isCallingToGenericService branch
+func TestFilter_Invoke(t *testing.T) {
+	invokeUrl := common.NewURLWithOptions(
+		common.WithParams(url.Values{}),
+		common.WithParamsValue(constant.GENERIC_KEY, GENERIC_SERIALIZATION_DEFAULT))
+	filter := &GenericFilter{}
+
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	normalInvocation := invocation.NewRPCInvocation("Hello", []interface{}{"arg1"}, make(map[string]interface{}))
+
+	mockInvoker := mock.NewMockInvoker(ctrl)
+	mockInvoker.EXPECT().GetURL().Return(invokeUrl).Times(2)
+	mockInvoker.EXPECT().Invoke(gomock.Not(normalInvocation)).DoAndReturn(
+		func(invocation protocol.Invocation) protocol.Result {
+			assert.Equal(t, constant.GENERIC, invocation.MethodName())
+			args := invocation.Arguments()
+			assert.Equal(t, "Hello", args[0])
+			assert.Equal(t, "java.lang.String", args[1].([]string)[0])
+			assert.Equal(t, "arg1", args[2].([]hessian.Object)[0].(string))
+			assert.Equal(t, GENERIC_SERIALIZATION_DEFAULT, invocation.AttachmentsByKey(constant.GENERIC_KEY, ""))
+			return &protocol.RPCResult{}
+		})
+
+	result := filter.Invoke(context.Background(), mockInvoker, normalInvocation)
+	assert.NotNil(t, result)
 }
 
-func (p *mockChild) JavaClassName() string {
-	return "org.apache.dubbo.mockChild"
+// test isMakingAGenericCall branch
+func TestFilter_InvokeWithGenericCall(t *testing.T) {
+	invokeUrl := common.NewURLWithOptions(
+		common.WithParams(url.Values{}),
+		common.WithParamsValue(constant.GENERIC_KEY, GENERIC_SERIALIZATION_DEFAULT))
+	filter := &GenericFilter{}
+
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	genericInvocation := invocation.NewRPCInvocation(constant.GENERIC, []interface{}{
+		"hello",
+		[]string{"java.lang.String"},
+		[]string{"arg1"},
+	}, make(map[string]interface{}))
+
+	mockInvoker := mock.NewMockInvoker(ctrl)
+	mockInvoker.EXPECT().GetURL().Return(invokeUrl).Times(3)
+	mockInvoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(
+		func(invocation protocol.Invocation) protocol.Result {
+			assert.Equal(t, constant.GENERIC, invocation.MethodName())
+			args := invocation.Arguments()
+			assert.Equal(t, "hello", args[0])
+			assert.Equal(t, "java.lang.String", args[1].([]string)[0])
+			assert.Equal(t, "arg1", args[2].([]string)[0])
+			assert.Equal(t, GENERIC_SERIALIZATION_DEFAULT, invocation.AttachmentsByKey(constant.GENERIC_KEY, ""))
+			return &protocol.RPCResult{}
+		})
+
+	result := filter.Invoke(context.Background(), mockInvoker, genericInvocation)
+	assert.NotNil(t, result)
 }
diff --git a/filter/filter_impl/generic_filter.go b/filter/filter_impl/generic_generalizer_map.go
similarity index 72%
copy from filter/filter_impl/generic_filter.go
copy to filter/filter_impl/generic_generalizer_map.go
index 86f8cfd..73531a3 100644
--- a/filter/filter_impl/generic_filter.go
+++ b/filter/filter_impl/generic_generalizer_map.go
@@ -18,74 +18,70 @@
 package filter_impl
 
 import (
-	"context"
 	"reflect"
 	"strings"
+	"sync"
 	"time"
 )
 
 import (
 	hessian "github.com/apache/dubbo-go-hessian2"
+	"github.com/mitchellh/mapstructure"
+	perrors "github.com/pkg/errors"
 )
 
 import (
-	"github.com/apache/dubbo-go/common/constant"
-	"github.com/apache/dubbo-go/common/extension"
 	"github.com/apache/dubbo-go/common/logger"
-	"github.com/apache/dubbo-go/filter"
-	"github.com/apache/dubbo-go/protocol"
-	invocation2 "github.com/apache/dubbo-go/protocol/invocation"
+	"github.com/apache/dubbo-go/protocol/dubbo/hessian2"
 )
 
-const (
-	// GENERIC
-	//generic module name
-	GENERIC = "generic"
+var (
+	mapGeneralizer     *MapGeneralizer
+	mapGeneralizerOnce sync.Once
 )
 
-func init() {
-	extension.SetFilter(GENERIC, GetGenericFilter)
+func GetMapGeneralizer() *MapGeneralizer {
+	mapGeneralizerOnce.Do(func() {
+		mapGeneralizer = &MapGeneralizer{}
+	})
+	return mapGeneralizer
 }
 
-//  when do a generic invoke, struct need to be map
+type MapGeneralizer struct{}
 
-// nolint
-type GenericFilter struct{}
-
-// Invoke turns the parameters to map for generic method
-func (ef *GenericFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
-	if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 {
-		oldArguments := invocation.Arguments()
+func (g *MapGeneralizer) Generalize(obj interface{}) (gobj interface{}, err error) {
+	gobj = objToMap(obj)
+	return
+}
 
-		if oldParams, ok := oldArguments[2].([]interface{}); ok {
-			newParams := make([]hessian.Object, 0, len(oldParams))
-			for i := range oldParams {
-				newParams = append(newParams, hessian.Object(objToMap(oldParams[i])))
-			}
-			newArguments := []interface{}{
-				oldArguments[0],
-				oldArguments[1],
-				newParams,
-			}
-			newInvocation := invocation2.NewRPCInvocation(invocation.MethodName(), newArguments, invocation.Attachments())
-			newInvocation.SetReply(invocation.Reply())
-			return invoker.Invoke(ctx, newInvocation)
-		}
+func (g *MapGeneralizer) Realize(obj interface{}, typ reflect.Type) (interface{}, error) {
+	newobj := reflect.New(typ).Interface()
+	err := mapstructure.Decode(obj, newobj)
+	if err != nil {
+		return nil, perrors.Errorf("realizing map failed, %v", err)
 	}
-	return invoker.Invoke(ctx, invocation)
-}
 
-// OnResponse dummy process, returns the result directly
-func (ef *GenericFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker,
-	_ protocol.Invocation) protocol.Result {
-	return result
+	return reflect.ValueOf(newobj).Elem().Interface(), nil
 }
 
-// GetGenericFilter returns GenericFilter instance
-func GetGenericFilter() filter.Filter {
-	return &GenericFilter{}
+func (g *MapGeneralizer) GetType(obj interface{}) (typ string, err error) {
+	typ, err = hessian2.GetJavaName(obj)
+	// no error or error is not NilError
+	if err == nil || err != hessian2.NilError {
+		return
+	}
+
+	typ = "java.lang.Object"
+	if err == hessian2.NilError {
+		logger.Debugf("the type of nil object couldn't be inferred, use the default value(\"%s\")", typ)
+		return
+	}
+
+	logger.Debugf("the type of object(=%T) couldn't be recognized as a POJO, use the default value(\"%s\")", obj, typ)
+	return
 }
 
+// objToMap converts an object(interface{}) to a map
 func objToMap(obj interface{}) interface{} {
 	if obj == nil {
 		return obj
diff --git a/filter/filter_impl/generic_filter_test.go b/filter/filter_impl/generic_generalizer_map_test.go
similarity index 53%
copy from filter/filter_impl/generic_filter_test.go
copy to filter/filter_impl/generic_generalizer_map_test.go
index 4a1f12e..cb072d1 100644
--- a/filter/filter_impl/generic_filter_test.go
+++ b/filter/filter_impl/generic_generalizer_map_test.go
@@ -27,30 +27,32 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestStruct2MapAll(t *testing.T) {
-	var testData struct {
-		AaAa string `m:"aaAa"`
-		BaBa string
-		CaCa struct {
-			AaAa string
-			BaBa string `m:"baBa"`
-			XxYy struct {
-				xxXx string `m:"xxXx"`
-				Xx   string `m:"xx"`
-			} `m:"xxYy"`
-		} `m:"caCa"`
-		DaDa time.Time
-		EeEe int
-	}
-	testData.AaAa = "1"
-	testData.BaBa = "1"
-	testData.CaCa.BaBa = "2"
-	testData.CaCa.AaAa = "2"
-	testData.CaCa.XxYy.xxXx = "3"
-	testData.CaCa.XxYy.Xx = "3"
-	testData.DaDa = time.Date(2020, 10, 29, 2, 34, 0, 0, time.Local)
-	testData.EeEe = 100
-	m := objToMap(testData).(map[string]interface{})
+type testPlainObj struct {
+	AaAa string `m:"aaAa"`
+	BaBa string
+	CaCa struct {
+		AaAa string
+		BaBa string `m:"baBa"`
+		XxYy struct {
+			xxXx string `m:"xxXx"`
+			Xx   string `m:"xx"`
+		} `m:"xxYy"`
+	} `m:"caCa"`
+	DaDa time.Time
+	EeEe int
+}
+
+func TestObjToMap(t *testing.T) {
+	obj := &testPlainObj{}
+	obj.AaAa = "1"
+	obj.BaBa = "1"
+	obj.CaCa.BaBa = "2"
+	obj.CaCa.AaAa = "2"
+	obj.CaCa.XxYy.xxXx = "3"
+	obj.CaCa.XxYy.Xx = "3"
+	obj.DaDa = time.Date(2020, 10, 29, 2, 34, 0, 0, time.Local)
+	obj.EeEe = 100
+	m := objToMap(obj).(map[string]interface{})
 	assert.Equal(t, "1", m["aaAa"].(string))
 	assert.Equal(t, "1", m["baBa"].(string))
 	assert.Equal(t, "2", m["caCa"].(map[string]interface{})["aaAa"].(string))
@@ -71,7 +73,7 @@ type testStruct struct {
 	} `m:"xxYy"`
 }
 
-func TestStruct2MapAllSlice(t *testing.T) {
+func TestObjToMap_Slice(t *testing.T) {
 	var testData struct {
 		AaAa string `m:"aaAa"`
 		BaBa string
@@ -96,7 +98,7 @@ func TestStruct2MapAllSlice(t *testing.T) {
 	assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"].([]interface{})[0].(map[string]interface{})["xxYy"]).Kind())
 }
 
-func TestStruct2MapAllMap(t *testing.T) {
+func TestObjToMap_Map(t *testing.T) {
 	var testData struct {
 		AaAa   string
 		Baba   map[string]interface{}
@@ -136,18 +138,112 @@ func TestStruct2MapAllMap(t *testing.T) {
 	assert.Equal(t, 1, intMap.(map[interface{}]interface{})[1])
 }
 
+var mockMapGeneralizer = GetMapGeneralizer()
+
 type mockParent struct {
-	Children []*mockChild
+	Gender, Email, Name string
+	Age                 int
+	Child               *mockChild
 }
 
-func (p *mockParent) JavaClassName() string {
+func (p mockParent) JavaClassName() string {
 	return "org.apache.dubbo.mockParent"
 }
 
 type mockChild struct {
-	Name string
+	Gender, Email, Name string
+	Age                 int
 }
 
-func (p *mockChild) JavaClassName() string {
+func (c *mockChild) JavaClassName() string {
 	return "org.apache.dubbo.mockChild"
 }
+
+func TestPOJOClassName(t *testing.T) {
+	c := &mockChild{
+		Age:    20,
+		Gender: "male",
+		Email:  "lmc@example.com",
+		Name:   "lmc",
+	}
+	p := mockParent{
+		Age:    30,
+		Gender: "male",
+		Email:  "xavierniu@example.com",
+		Name:   "xavierniu",
+		Child:  c,
+	}
+
+	m, err := mockMapGeneralizer.Generalize(p)
+	assert.Nil(t, err)
+	// parent
+	assert.Equal(t, "xavierniu", m.(map[string]interface{})["name"].(string))
+	assert.Equal(t, 30, m.(map[string]interface{})["age"].(int))
+	assert.Equal(t, "org.apache.dubbo.mockParent", m.(map[string]interface{})["class"].(string))
+	// child
+	assert.Equal(t, 20, m.(map[string]interface{})["child"].(map[string]interface{})["age"].(int))
+	assert.Equal(t, "lmc", m.(map[string]interface{})["child"].(map[string]interface{})["name"].(string))
+	assert.Equal(t, "org.apache.dubbo.mockChild", m.(map[string]interface{})["child"].(map[string]interface{})["class"].(string))
+
+	r, err := mockMapGeneralizer.Realize(m, reflect.TypeOf(p))
+	assert.Nil(t, err)
+	rMockParent, ok := r.(mockParent)
+	assert.True(t, ok)
+	// parent
+	assert.Equal(t, "xavierniu", rMockParent.Name)
+	assert.Equal(t, 30, rMockParent.Age)
+	// child
+	assert.Equal(t, "lmc", rMockParent.Child.Name)
+	assert.Equal(t, 20, rMockParent.Child.Age)
+}
+
+func TestPOJOArray(t *testing.T) {
+	c1 := &mockChild{
+		Age:    20,
+		Gender: "male",
+		Email:  "lmc@example.com",
+		Name:   "lmc",
+	}
+	c2 := &mockChild{
+		Age:    21,
+		Gender: "male",
+		Email:  "lmc1@example.com",
+		Name:   "lmc1",
+	}
+
+	pojoArr := []*mockChild{c1, c2}
+
+	m, err := mockMapGeneralizer.Generalize(pojoArr)
+	assert.Nil(t, err)
+	assert.Equal(t, "lmc", m.([]interface{})[0].(map[string]interface{})["name"].(string))
+	assert.Equal(t, 20, m.([]interface{})[0].(map[string]interface{})["age"].(int))
+	assert.Equal(t, "lmc1", m.([]interface{})[1].(map[string]interface{})["name"].(string))
+	assert.Equal(t, 21, m.([]interface{})[1].(map[string]interface{})["age"].(int))
+
+	r, err := mockMapGeneralizer.Realize(m, reflect.TypeOf(pojoArr))
+	assert.Nil(t, err)
+	rPojoArr, ok := r.([]*mockChild)
+	assert.True(t, ok)
+	assert.Equal(t, "lmc", rPojoArr[0].Name)
+	assert.Equal(t, 20, rPojoArr[0].Age)
+	assert.Equal(t, "lmc1", rPojoArr[1].Name)
+	assert.Equal(t, 21, rPojoArr[1].Age)
+}
+
+func TestNullField(t *testing.T) {
+	p := mockParent{
+		Age:    30,
+		Gender: "male",
+		Email:  "xavierniu@example.com",
+		Name:   "xavierniu",
+	}
+
+	m, _ := mockMapGeneralizer.Generalize(p)
+	assert.Nil(t, m.(map[string]interface{})["child"])
+
+	r, err := mockMapGeneralizer.Realize(m, reflect.TypeOf(p))
+	assert.Nil(t, err)
+	rMockParent, ok := r.(mockParent)
+	assert.True(t, ok)
+	assert.Nil(t, rMockParent.Child)
+}
diff --git a/filter/filter_impl/generic_service_filter.go b/filter/filter_impl/generic_service_filter.go
index 6056224..886bb05 100644
--- a/filter/filter_impl/generic_service_filter.go
+++ b/filter/filter_impl/generic_service_filter.go
@@ -19,12 +19,10 @@ package filter_impl
 
 import (
 	"context"
-	"reflect"
 )
 
 import (
 	hessian "github.com/apache/dubbo-go-hessian2"
-	"github.com/mitchellh/mapstructure"
 	perrors "github.com/pkg/errors"
 )
 
@@ -54,74 +52,79 @@ type GenericServiceFilter struct{}
 
 // Invoke is used to call service method by invocation
 func (ef *GenericServiceFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
-	logger.Infof("invoking generic service filter.")
-	logger.Debugf("generic service filter methodName:%v,args:%v", invocation.MethodName(), len(invocation.Arguments()))
-
 	if invocation.MethodName() != constant.GENERIC || len(invocation.Arguments()) != 3 {
 		return invoker.Invoke(ctx, invocation)
 	}
 
-	var (
-		ok         bool
-		err        error
-		methodName string
-		newParams  []interface{}
-		genericKey string
-		argsType   []reflect.Type
-		oldParams  []hessian.Object
-	)
-
-	url := invoker.GetURL()
-	methodName = invocation.Arguments()[0].(string)
-	// get service
-	svc := common.ServiceMap.GetServiceByServiceKey(url.Protocol, url.ServiceKey())
-	// get method
-	method := svc.Method()[methodName]
+	// get real invocation info from the generic invocation
+	mtdname := invocation.Arguments()[0].(string)
+	// types are not required in dubbo-go, for dubbo-go client to dubbo-go server, types could be nil
+	types := invocation.Arguments()[1]
+	args := invocation.Arguments()[2].([]hessian.Object)
+
+	logger.Debugf(`received a generic invocation: 
+		MethodName: %s,
+		Types: %s,
+		Args: %s
+	`, mtdname, types, args)
+
+	// get the type of the argument
+	ivkUrl := invoker.GetURL()
+	svc := common.ServiceMap.GetServiceByServiceKey(ivkUrl.Protocol, ivkUrl.ServiceKey())
+	method := svc.Method()[mtdname]
 	if method == nil {
-		logger.Errorf("[Generic Service Filter] Don't have this method: %s", methodName)
-		return &protocol.RPCResult{}
-	}
-	argsType = method.ArgsType()
-	genericKey = invocation.AttachmentsByKey(constant.GENERIC_KEY, GENERIC_SERIALIZATION_DEFAULT)
-	if genericKey == GENERIC_SERIALIZATION_DEFAULT {
-		oldParams, ok = invocation.Arguments()[2].([]hessian.Object)
-	} else {
-		logger.Errorf("[Generic Service Filter] Don't support this generic: %s", genericKey)
-		return &protocol.RPCResult{}
-	}
-	if !ok {
-		logger.Errorf("[Generic Service Filter] wrong serialization")
-		return &protocol.RPCResult{}
+		return &protocol.RPCResult{
+			Err: perrors.Errorf("\"%s\" method is not found, service key: %s", mtdname, ivkUrl.ServiceKey()),
+		}
 	}
-	if len(oldParams) != len(argsType) {
-		logger.Errorf("[Generic Service Filter] method:%s invocation arguments number was wrong", methodName)
-		return &protocol.RPCResult{}
+	argsType := method.ArgsType()
+
+	// get generic info from attachments of invocation, the default value is "true"
+	//generic := invocation.AttachmentsByKey(constant.GENERIC_KEY, GENERIC_SERIALIZATION_DEFAULT)
+	// get generalizer according to value in the `generic`
+	g := GetMapGeneralizer()
+
+	if len(args) != len(argsType) {
+		return &protocol.RPCResult{
+			Err: perrors.Errorf("the number of args(=%d) is not matched with \"%s\" method", len(args), mtdname),
+		}
 	}
-	// oldParams convert to newParams
-	newParams = make([]interface{}, len(oldParams))
-	for i := range argsType {
-		newParam := reflect.New(argsType[i]).Interface()
-		err = mapstructure.Decode(oldParams[i], newParam)
-		newParam = reflect.ValueOf(newParam).Elem().Interface()
+
+	// realize
+	newargs := make([]interface{}, len(argsType))
+	for i := 0; i < len(argsType); i++ {
+		newarg, err := g.Realize(args[i], argsType[i])
 		if err != nil {
-			logger.Errorf("[Generic Service Filter] decode arguments map to struct wrong: error{%v}", perrors.WithStack(err))
-			return &protocol.RPCResult{}
+			return &protocol.RPCResult{
+				Err: perrors.Errorf("realization failed, %v", err),
+			}
 		}
-		newParams[i] = newParam
+		newargs[i] = newarg
 	}
-	newInvocation := invocation2.NewRPCInvocation(methodName, newParams, invocation.Attachments())
-	newInvocation.SetReply(invocation.Reply())
-	return invoker.Invoke(ctx, newInvocation)
+
+	// build a normal invocation
+	newivc := invocation2.NewRPCInvocation(mtdname, newargs, invocation.Attachments())
+	newivc.SetReply(invocation.Reply())
+
+	return invoker.Invoke(ctx, newivc)
 }
 
 // nolint
 func (ef *GenericServiceFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 && result.Result() != nil {
-		v := reflect.ValueOf(result.Result())
-		if v.Kind() == reflect.Ptr {
-			v = v.Elem()
+		// get generic info from attachments of invocation, the default value is "true"
+		//generic := invocation.AttachmentsByKey(constant.GENERIC_KEY, constant.GenericSerializationDefault)
+		// get generalizer according to value in the `generic`
+		g := GetMapGeneralizer()
+
+		obj, err := g.Generalize(result.Result())
+		if err != nil {
+			err = perrors.Errorf("generalizaion failed, %v", err)
+			result.SetError(err)
+			result.SetResult(nil)
+			return result
 		}
-		result.SetResult(objToMap(v.Interface()))
+		result.SetResult(obj)
 	}
 	return result
 }
diff --git a/filter/filter_impl/generic_service_filter_test.go b/filter/filter_impl/generic_service_filter_test.go
index c755a2d..d32e803 100644
--- a/filter/filter_impl/generic_service_filter_test.go
+++ b/filter/filter_impl/generic_service_filter_test.go
@@ -19,131 +19,157 @@ package filter_impl
 
 import (
 	"context"
-	"errors"
-	"reflect"
+	"fmt"
+	"net/url"
 	"testing"
 )
 
 import (
 	hessian "github.com/apache/dubbo-go-hessian2"
+	"github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
 )
 
 import (
 	"github.com/apache/dubbo-go/common"
-	"github.com/apache/dubbo-go/common/proxy/proxy_factory"
+	"github.com/apache/dubbo-go/common/constant"
 	"github.com/apache/dubbo-go/protocol"
 	"github.com/apache/dubbo-go/protocol/invocation"
+	"github.com/apache/dubbo-go/protocol/mock"
 )
 
-type TestStruct struct {
-	AaAa string
-	BaBa string `m:"baBa"`
-	XxYy struct {
-		xxXx string `m:"xxXx"`
-		Xx   string `m:"xx"`
-	} `m:"xxYy"`
-}
+type MockHelloService struct{}
 
-func (c *TestStruct) JavaClassName() string {
-	return "com.test.testStruct"
+func (s *MockHelloService) Hello(who string) (string, error) {
+	return fmt.Sprintf("hello, %s", who), nil
 }
 
-type TestService struct{}
-
-// nolint
-func (ts *TestService) MethodOne(_ context.Context, test1 *TestStruct, test2 []TestStruct,
-	test3 interface{}, test4 []interface{}, test5 *string) (*TestStruct, error) {
-	if test1 == nil {
-		return nil, errors.New("param test1 is nil")
-	}
-	if test2 == nil {
-		return nil, errors.New("param test2 is nil")
-	}
-	if test3 == nil {
-		return nil, errors.New("param test3 is nil")
-	}
-	if test4 == nil {
-		return nil, errors.New("param test4 is nil")
-	}
-	if test5 == nil {
-		return nil, errors.New("param test5 is nil")
-	}
-	return &TestStruct{}, nil
+func (s *MockHelloService) JavaClassName() string {
+	return "org.apache.dubbo.hello"
 }
 
-// nolint
-func (*TestService) Reference() string {
-	return "com.test.Path"
+func (s *MockHelloService) Reference() string {
+	return "org.apache.dubbo.test"
 }
 
-func TestGenericServiceFilterInvoke(t *testing.T) {
-	hessian.RegisterPOJO(&TestStruct{})
-	methodName := "$invoke"
-	m := make(map[string]interface{})
-	m["AaAa"] = "nihao"
-	x := make(map[string]interface{})
-	x["xxXX"] = "nihaoxxx"
-	m["XxYy"] = x
-	aurguments := []interface{}{
-		"MethodOne",
-		nil,
-		[]hessian.Object{
-			hessian.Object(m),
-			hessian.Object(append(make([]map[string]interface{}, 1), m)),
-			hessian.Object("111"),
-			hessian.Object(append(make([]map[string]interface{}, 1), m)),
-			hessian.Object("222")},
-	}
-	s := &TestService{}
-	_, _ = common.ServiceMap.Register("com.test.Path", "testprotocol", "", "", s)
-	rpcInvocation := invocation.NewRPCInvocation(methodName, aurguments, nil)
-	filter := GetGenericServiceFilter()
-	url, _ := common.NewURL("testprotocol://127.0.0.1:20000/com.test.Path")
-	result := filter.Invoke(context.Background(), &proxy_factory.ProxyInvoker{BaseInvoker: *protocol.NewBaseInvoker(url)}, rpcInvocation)
-	assert.NotNil(t, result)
+func TestServiceFilter_Invoke(t *testing.T) {
+	filter := &GenericServiceFilter{}
+
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	mockInvoker := mock.NewMockInvoker(ctrl)
+
+	// methodName is not "$invoke"
+	invocation1 := invocation.NewRPCInvocation("test", nil, nil)
+	mockInvoker.EXPECT().Invoke(gomock.Eq(invocation1))
+	_ = filter.Invoke(context.Background(), mockInvoker, invocation1)
+	// arguments are nil
+	invocation2 := invocation.NewRPCInvocation(constant.GENERIC, nil, nil)
+	mockInvoker.EXPECT().Invoke(gomock.Eq(invocation2))
+	_ = filter.Invoke(context.Background(), mockInvoker, invocation2)
+	// the number of arguments is not 3
+	invocation3 := invocation.NewRPCInvocation(constant.GENERIC, []interface{}{"hello"}, nil)
+	mockInvoker.EXPECT().Invoke(gomock.Eq(invocation3))
+	_ = filter.Invoke(context.Background(), mockInvoker, invocation3)
+
+	// hello service
+	service := &MockHelloService{}
+	// invoke URL
+	ivkUrl := common.NewURLWithOptions(
+		common.WithProtocol("test"),
+		common.WithParams(url.Values{}),
+		common.WithParamsValue(constant.INTERFACE_KEY, service.Reference()),
+		common.WithParamsValue(constant.GENERIC_KEY, GENERIC_SERIALIZATION_DEFAULT))
+	// registry RPC service
+	_, err := common.ServiceMap.Register(ivkUrl.GetParam(constant.INTERFACE_KEY, ""),
+		ivkUrl.Protocol,
+		"",
+		"",
+		service)
+	assert.Nil(t, err)
+
+	// mock
+	mockInvoker.EXPECT().GetURL().Return(ivkUrl).Times(3)
+
+	// invoke a method without errors using default generalization
+	invocation4 := invocation.NewRPCInvocation(constant.GENERIC,
+		[]interface{}{
+			"Hello",
+			[]string{"java.lang.String"},
+			[]hessian.Object{"world"},
+		}, map[string]interface{}{
+			constant.GENERIC_KEY: "true",
+		})
+	// invoke a non-existed method
+	invocation5 := invocation.NewRPCInvocation(constant.GENERIC,
+		[]interface{}{
+			"hello11",
+			[]string{"java.lang.String"},
+			[]hessian.Object{"world"},
+		}, map[string]interface{}{
+			constant.GENERIC_KEY: "true",
+		})
+	// invoke a method with incorrect arguments
+	invocation6 := invocation.NewRPCInvocation(constant.GENERIC,
+		[]interface{}{
+			"Hello",
+			[]string{"java.lang.String", "java.lang.String"},
+			[]hessian.Object{"world", "haha"},
+		}, map[string]interface{}{
+			constant.GENERIC_KEY: "true",
+		})
+
+	mockInvoker.EXPECT().Invoke(gomock.All(
+		gomock.Not(invocation1),
+		gomock.Not(invocation2),
+		gomock.Not(invocation3),
+	)).DoAndReturn(
+		func(invocation protocol.Invocation) protocol.Result {
+			switch invocation.MethodName() {
+			case "Hello":
+				who := invocation.Arguments()[0].(string)
+				result, _ := service.Hello(who)
+				return &protocol.RPCResult{
+					Rest: result,
+				}
+			default:
+				panic("this branch shouldn't be reached")
+			}
+		}).AnyTimes()
+
+	result := filter.Invoke(context.Background(), mockInvoker, invocation4)
 	assert.Nil(t, result.Error())
-}
+	assert.Equal(t, "hello, world", result.Result())
 
-func TestGenericServiceFilterResponseTestStruct(t *testing.T) {
-	ts := &TestStruct{
-		AaAa: "aaa",
-		BaBa: "bbb",
-		XxYy: struct {
-			xxXx string `m:"xxXx"`
-			Xx   string `m:"xx"`
-		}{},
-	}
-	result := &protocol.RPCResult{
-		Rest: ts,
-	}
-	aurguments := []interface{}{
-		"MethodOne",
-		nil,
-		[]hessian.Object{nil},
-	}
-	filter := GetGenericServiceFilter()
-	methodName := "$invoke"
-	rpcInvocation := invocation.NewRPCInvocation(methodName, aurguments, nil)
-	r := filter.OnResponse(context.TODO(), result, nil, rpcInvocation)
-	assert.NotNil(t, r.Result())
-	assert.Equal(t, reflect.ValueOf(r.Result()).Kind(), reflect.Map)
+	result = filter.Invoke(context.Background(), mockInvoker, invocation5)
+	assert.Equal(t,
+		fmt.Sprintf("\"hello11\" method is not found, service key: %s", ivkUrl.ServiceKey()),
+		fmt.Sprintf("%v", result.Error().(error)))
+
+	result = filter.Invoke(context.Background(), mockInvoker, invocation6)
+	assert.Equal(t,
+		"the number of args(=2) is not matched with \"Hello\" method",
+		fmt.Sprintf("%v", result.Error().(error)))
 }
 
-func TestGenericServiceFilterResponseString(t *testing.T) {
-	str := "111"
-	result := &protocol.RPCResult{
-		Rest: str,
-	}
-	aurguments := []interface{}{
-		"MethodOne",
-		nil,
-		[]hessian.Object{nil},
+func TestServiceFilter_OnResponse(t *testing.T) {
+	filter := &GenericServiceFilter{}
+
+	// invoke a method without errors
+	invocation1 := invocation.NewRPCInvocation(constant.GENERIC,
+		[]interface{}{
+			"hello",
+			[]interface{}{"java.lang.String"},
+			[]interface{}{"world"},
+		}, map[string]interface{}{
+			constant.GENERIC_KEY: "true",
+		})
+
+	rpcResult := &protocol.RPCResult{
+		Rest: "result",
 	}
-	filter := GetGenericServiceFilter()
-	methodName := "$invoke"
-	rpcInvocation := invocation.NewRPCInvocation(methodName, aurguments, nil)
-	r := filter.OnResponse(context.TODO(), result, nil, rpcInvocation)
-	assert.NotNil(t, r.Result())
-	assert.Equal(t, reflect.ValueOf(r.Result()).Kind(), reflect.String)
+
+	result := filter.OnResponse(context.Background(), rpcResult, nil, invocation1)
+	assert.Equal(t, "result", result.Result())
 }
diff --git a/protocol/dubbo/hessian2/hessian_request.go b/protocol/dubbo/hessian2/hessian_request.go
index 94aa34d..b281273 100644
--- a/protocol/dubbo/hessian2/hessian_request.go
+++ b/protocol/dubbo/hessian2/hessian_request.go
@@ -19,7 +19,6 @@ package hessian2
 
 import (
 	"encoding/binary"
-	"reflect"
 	"strconv"
 	"strings"
 	"time"
@@ -39,95 +38,7 @@ import (
 /////////////////////////////////////////
 
 func getArgType(v interface{}) string {
-	if v == nil {
-		return "V"
-	}
-
-	switch v := v.(type) {
-	// Serialized tags for base types
-	case nil:
-		return "V"
-	case bool:
-		return "Z"
-	case []bool:
-		return "[Z"
-	case byte:
-		return "B"
-	case []byte:
-		return "[B"
-	case int8:
-		return "B"
-	case []int8:
-		return "[B"
-	case int16:
-		return "S"
-	case []int16:
-		return "[S"
-	case uint16: // Equivalent to Char of Java
-		return "C"
-	case []uint16:
-		return "[C"
-	// case rune:
-	//	return "C"
-	case int:
-		return "J"
-	case []int:
-		return "[J"
-	case int32:
-		return "I"
-	case []int32:
-		return "[I"
-	case int64:
-		return "J"
-	case []int64:
-		return "[J"
-	case time.Time:
-		return "java.util.Date"
-	case []time.Time:
-		return "[Ljava.util.Date"
-	case float32:
-		return "F"
-	case []float32:
-		return "[F"
-	case float64:
-		return "D"
-	case []float64:
-		return "[D"
-	case string:
-		return "java.lang.String"
-	case []string:
-		return "[Ljava.lang.String;"
-	case []hessian.Object:
-		return "[Ljava.lang.Object;"
-	case map[interface{}]interface{}:
-		// return  "java.util.HashMap"
-		return "java.util.Map"
-	case hessian.POJOEnum:
-		return v.(hessian.POJOEnum).JavaClassName()
-	//  Serialized tags for complex types
-	default:
-		t := reflect.TypeOf(v)
-		if reflect.Ptr == t.Kind() {
-			t = reflect.TypeOf(reflect.ValueOf(v).Elem())
-		}
-		switch t.Kind() {
-		case reflect.Struct:
-			return "java.lang.Object"
-		case reflect.Slice, reflect.Array:
-			if t.Elem().Kind() == reflect.Struct {
-				return "[Ljava.lang.Object;"
-			}
-			// return "java.util.ArrayList"
-			return "java.util.List"
-		case reflect.Map: // Enter here, map may be map[string]int
-			return "java.util.Map"
-		default:
-			return ""
-		}
-	}
-
-	// unreachable
-	// return "java.lang.RuntimeException"
+	return GetClassDesc(v)
 }
 
 func getArgsTypeList(args []interface{}) (string, error) {
diff --git a/protocol/dubbo/hessian2/java_class.go b/protocol/dubbo/hessian2/java_class.go
new file mode 100644
index 0000000..781ff21
--- /dev/null
+++ b/protocol/dubbo/hessian2/java_class.go
@@ -0,0 +1,200 @@
+/*
+ * 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 hessian2
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+	"time"
+)
+
+import (
+	hessian "github.com/apache/dubbo-go-hessian2"
+	perrors "github.com/pkg/errors"
+)
+
+var (
+	NilError            = perrors.Errorf("object should not be nil")
+	UnexpectedTypeError = perrors.Errorf("object should be a POJO")
+	notBasicClassError  = perrors.Errorf("object isn't a basic class")
+)
+
+// GetJavaName returns java name of an object
+func GetJavaName(obj interface{}) (string, error) {
+	if obj == nil {
+		return "", NilError
+	}
+
+	t := reflect.TypeOf(obj)
+
+	// basic types, e.g. bool, int, etc.
+	if jtype, err := getBasicJavaName(t); err == nil {
+		return jtype, nil
+	}
+
+	// complicated types, e.g. array, slice, etc.
+	switch t.Kind() {
+	case reflect.Array, reflect.Slice:
+		sb := &strings.Builder{}
+		itemtyp := t
+		for itemtyp.Kind() == reflect.Array || itemtyp.Kind() == reflect.Slice {
+			sb.WriteString("[]")
+			itemtyp = itemtyp.Elem()
+		}
+		var (
+			javaName string
+			err      error
+		)
+		if javaName, err = getBasicJavaName(itemtyp); err != nil {
+			if javaName, err = GetJavaName(reflect.New(itemtyp).Elem().Interface()); err != nil {
+				return "", err
+			}
+		}
+		return fmt.Sprintf("%s%s", javaName, sb), nil
+	case reflect.Map:
+		return "java.util.Map", nil
+	default:
+		pojo, ok := obj.(hessian.POJO)
+		if !ok {
+			return "", UnexpectedTypeError
+		}
+		return pojo.JavaClassName(), nil
+	}
+}
+
+func getBasicJavaName(typ reflect.Type) (string, error) {
+	switch typ.Kind() {
+	case reflect.Bool:
+		return "boolean", nil
+	case reflect.Int, reflect.Int64: // in 64-bit processor, Int takes a 64-bit space
+		return "long", nil
+	case reflect.Int32:
+		return "int", nil
+	case reflect.Int8, reflect.Int16:
+		return "short", nil
+	case reflect.Uint, reflect.Uint64: // in 64-bit processor, Uint takes a 64-bit space
+		return "unsigned long", nil
+	case reflect.Uint32:
+		return "unsigned int", nil
+	case reflect.Uint16:
+		return "unsigned short", nil
+	case reflect.Uint8:
+		return "char", nil
+	case reflect.Float32:
+		return "float", nil
+	case reflect.Float64:
+		return "double", nil
+	case reflect.String:
+		return "java.lang.String", nil
+	}
+
+	return "", notBasicClassError
+}
+
+// GetClassDesc get class desc.
+// - boolean[].class => "[Z"
+// - Object.class => "Ljava/lang/Object;"
+func GetClassDesc(v interface{}) string {
+	if v == nil {
+		return "V"
+	}
+
+	switch v := v.(type) {
+	// Serialized tags for base types
+	case nil:
+		return "V"
+	case bool:
+		return "Z"
+	case []bool:
+		return "[Z"
+	case byte:
+		return "B"
+	case []byte:
+		return "[B"
+	case int8:
+		return "B"
+	case []int8:
+		return "[B"
+	case int16:
+		return "S"
+	case []int16:
+		return "[S"
+	case uint16: // Equivalent to Char of Java
+		return "C"
+	case []uint16:
+		return "[C"
+	// case rune:
+	//	return "C"
+	case int:
+		return "J"
+	case []int:
+		return "[J"
+	case int32:
+		return "I"
+	case []int32:
+		return "[I"
+	case int64:
+		return "J"
+	case []int64:
+		return "[J"
+	case time.Time:
+		return "java.util.Date"
+	case []time.Time:
+		return "[Ljava.util.Date"
+	case float32:
+		return "F"
+	case []float32:
+		return "[F"
+	case float64:
+		return "D"
+	case []float64:
+		return "[D"
+	case string:
+		return "java.lang.String"
+	case []string:
+		return "[Ljava.lang.String;"
+	case []hessian.Object:
+		return "[Ljava.lang.Object;"
+	case map[interface{}]interface{}:
+		// return  "java.util.HashMap"
+		return "java.util.Map"
+	case hessian.POJOEnum:
+		return v.(hessian.POJOEnum).JavaClassName()
+	//  Serialized tags for complex types
+	default:
+		t := reflect.TypeOf(v)
+		if reflect.Ptr == t.Kind() {
+			t = reflect.TypeOf(reflect.ValueOf(v).Elem())
+		}
+		switch t.Kind() {
+		case reflect.Struct:
+			return "java.lang.Object"
+		case reflect.Slice, reflect.Array:
+			if t.Elem().Kind() == reflect.Struct {
+				return "[Ljava.lang.Object;"
+			}
+			// return "java.util.ArrayList"
+			return "java.util.List"
+		case reflect.Map: // Enter here, map may be map[string]int
+			return "java.util.Map"
+		default:
+			return ""
+		}
+	}
+}
diff --git a/protocol/dubbo/hessian2/java_class_test.go b/protocol/dubbo/hessian2/java_class_test.go
new file mode 100644
index 0000000..246f139
--- /dev/null
+++ b/protocol/dubbo/hessian2/java_class_test.go
@@ -0,0 +1,132 @@
+/*
+ * 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 hessian2
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetJavaName(t *testing.T) {
+	_, err := GetJavaName(nil)
+	assert.Equal(t, NilError, err)
+
+	typ, err := GetJavaName(true)
+	assert.Equal(t, "boolean", typ)
+	assert.Nil(t, err)
+
+	typ, err = GetJavaName(1)
+	assert.Equal(t, "long", typ)
+	assert.Nil(t, err)
+	typ, err = GetJavaName(int64(1))
+	assert.Equal(t, "long", typ)
+	assert.Nil(t, err)
+
+	typ, err = GetJavaName(int32(1))
+	assert.Equal(t, "int", typ)
+	assert.Nil(t, err)
+
+	typ, err = GetJavaName(int16(1))
+	assert.Equal(t, "short", typ)
+	assert.Nil(t, err)
+	typ, err = GetJavaName(int8(1))
+	assert.Equal(t, "short", typ)
+	assert.Nil(t, err)
+
+	typ, err = GetJavaName(uint(1))
+	assert.Equal(t, "unsigned long", typ)
+	assert.Nil(t, err)
+	typ, err = GetJavaName(uint64(1))
+	assert.Equal(t, "unsigned long", typ)
+	assert.Nil(t, err)
+
+	typ, err = GetJavaName(uint32(1))
+	assert.Equal(t, "unsigned int", typ)
+	assert.Nil(t, err)
+
+	typ, err = GetJavaName(uint16(1))
+	assert.Equal(t, "unsigned short", typ)
+	assert.Nil(t, err)
+	typ, err = GetJavaName(byte('a'))
+	assert.Equal(t, "char", typ)
+	assert.Nil(t, err)
+
+	typ, err = GetJavaName(float32(1.0))
+	assert.Equal(t, "float", typ)
+	assert.Nil(t, err)
+
+	typ, err = GetJavaName(1.0)
+	assert.Equal(t, "double", typ)
+	assert.Nil(t, err)
+
+	typ, err = GetJavaName("hello")
+	assert.Equal(t, "java.lang.String", typ)
+	assert.Nil(t, err)
+
+	typ, err = GetJavaName([]string{"hello"})
+	assert.Equal(t, "java.lang.String[]", typ)
+	assert.Nil(t, err)
+	typ, err = GetJavaName([]*mockPOJOPtr{{}})
+	assert.Equal(t, "org.apache.dubbo.mockPOJOPtr[]", typ)
+	assert.Nil(t, err)
+	typ, err = GetJavaName([]mockPOJO{{}})
+	assert.Equal(t, "org.apache.dubbo.mockPOJO[]", typ)
+	assert.Nil(t, err)
+	typ, err = GetJavaName([][]string{{"hello"}})
+	assert.Equal(t, "java.lang.String[][]", typ)
+	assert.Nil(t, err)
+	typ, err = GetJavaName([][]*mockPOJOPtr{{&mockPOJOPtr{}}})
+	assert.Equal(t, "org.apache.dubbo.mockPOJOPtr[][]", typ)
+	assert.Nil(t, err)
+	typ, err = GetJavaName([1]string{"hello"})
+	assert.Equal(t, "java.lang.String[]", typ)
+	assert.Nil(t, err)
+
+	typ, err = GetJavaName(map[string]string{"key1": "value1"})
+	assert.Equal(t, "java.util.Map", typ)
+	assert.Nil(t, err)
+
+	typ, err = GetJavaName(mockPOJO{})
+	assert.Equal(t, "org.apache.dubbo.mockPOJO", typ)
+	assert.Nil(t, err)
+	typ, err = GetJavaName(&mockPOJOPtr{})
+	assert.Equal(t, "org.apache.dubbo.mockPOJOPtr", typ)
+	assert.Nil(t, err)
+
+	_, err = GetJavaName(&mockNonPOJO{})
+	assert.Equal(t, UnexpectedTypeError, err)
+}
+
+type mockPOJOPtr struct {
+	TestField string
+}
+
+func (m *mockPOJOPtr) JavaClassName() string {
+	return "org.apache.dubbo.mockPOJOPtr"
+}
+
+type mockPOJO struct{}
+
+func (m mockPOJO) JavaClassName() string {
+	return "org.apache.dubbo.mockPOJO"
+}
+
+type mockNonPOJO struct{}