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 18fa78b feature: Add a new GenericService that returns provider's
attachments (#1196)
18fa78b is described below
commit 18fa78b32727706cef9b52b6abb3f568776dacc0
Author: 陈哈哈 <[email protected]>
AuthorDate: Fri Jun 25 22:21:18 2021 +0800
feature: Add a new GenericService that returns provider's attachments
(#1196)
* feature: 增加ResultGenericService,支持泛调用获取Provider返回的Attachments.
* fix: split imports, add comments;
* impr: 更新GenericService对Attachment支持的函数签名及对应实现
* rename: Rename generic proxy factory
---
common/proxy/proxy_factory/generic.go | 160 +++++++++++++++++++++++++++++
common/proxy/proxy_factory/generic_test.go | 55 ++++++++++
config/generic_service_ext.go | 41 ++++++++
3 files changed, 256 insertions(+)
diff --git a/common/proxy/proxy_factory/generic.go
b/common/proxy/proxy_factory/generic.go
new file mode 100644
index 0000000..8b55999
--- /dev/null
+++ b/common/proxy/proxy_factory/generic.go
@@ -0,0 +1,160 @@
+/*
+ * 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 proxy_factory
+
+import (
+ "context"
+ "reflect"
+)
+
+import (
+ "github.com/apache/dubbo-go/common"
+ "github.com/apache/dubbo-go/common/constant"
+ "github.com/apache/dubbo-go/common/logger"
+ "github.com/apache/dubbo-go/common/proxy"
+ "github.com/apache/dubbo-go/protocol"
+ "github.com/apache/dubbo-go/protocol/invocation"
+)
+
+var (
+ typeOfInterface =
reflect.Zero(reflect.TypeOf((*interface{})(nil)).Elem()).Type()
+ typeOfStrMap =
reflect.Zero(reflect.TypeOf((*map[string]interface{})(nil)).Elem()).Type()
+ typeOfError =
reflect.Zero(reflect.TypeOf((*error)(nil)).Elem()).Type()
+)
+
+// GenericProxyFactory proxy factory, return values with attachments
+type GenericProxyFactory struct {
+}
+
+// NewGenericProxyFactory returns a proxy factory instance
+func NewGenericProxyFactory(_ ...proxy.Option) proxy.ProxyFactory {
+ return &GenericProxyFactory{}
+}
+
+// GetProxy gets a proxy
+func (f *GenericProxyFactory) GetProxy(invoker protocol.Invoker, url
*common.URL) *proxy.Proxy {
+ return f.GetAsyncProxy(invoker, nil, url)
+}
+
+// GetAsyncProxy gets a async proxy
+func (f *GenericProxyFactory) GetAsyncProxy(invoker protocol.Invoker, callBack
interface{}, url *common.URL) *proxy.Proxy {
+ attrs := map[string]string{}
+ attrs[constant.ASYNC_KEY] = url.GetParam(constant.ASYNC_KEY, "false")
+ return proxy.NewProxyWithOptions(invoker, callBack, attrs,
+ proxy.WithProxyImplementFunc(NewGenericProxyImplFunc(attrs)))
+}
+
+// GetInvoker gets a invoker
+func (f *GenericProxyFactory) GetInvoker(url *common.URL) protocol.Invoker {
+ return &ProxyInvoker{
+ BaseInvoker: *protocol.NewBaseInvoker(url),
+ }
+}
+
+// NewGenericProxyImplFunc returns a new function with the given attributes,
+// supports only function sign: func Invoke(Context, []{Method,
[]{Arguments}}) protocol.Result.
+func NewGenericProxyImplFunc(attr map[string]string) proxy.ImplementFunc {
+ return func(p *proxy.Proxy, rpc common.RPCService) {
+ serviceValue := reflect.ValueOf(rpc)
+ serviceElem := serviceValue.Elem()
+ serviceType := serviceElem.Type()
+ numField := serviceElem.NumField()
+ for i := 0; i < numField; i++ {
+ f := serviceElem.Field(i)
+ if !(f.Kind() == reflect.Func && f.IsValid() &&
f.CanSet()) {
+ continue
+ }
+ funcField := serviceType.Field(i)
+ funcName := funcField.Tag.Get("dubbo")
+ if funcName == "" {
+ funcName = funcField.Name
+ }
+ // Only generic method: Invoke/$invoke
+ if funcName != "Invoke" && funcName != "$invoke" {
+ continue
+ }
+ // Enforce param types:
+ // Invoke(Context, []{Method, []{Arguments}}) (result,
attachments, error)
+ inNum := funcField.Type.NumIn()
+ if inNum != 2 {
+ logger.Errorf("Generic func requires 2 in-arg
type, func: %s(%s), was: %d",
+ funcField.Name,
funcField.Type.String(), inNum)
+ continue
+ }
+ // Enforce return types
+ // Invoke(Context, []{Method, []{Arguments}}) (result,
attachments, error)
+ outNum := funcField.Type.NumOut()
+ if outNum != 3 ||
+ funcField.Type.Out(0) != typeOfInterface ||
+ funcField.Type.Out(1) != typeOfStrMap ||
+ funcField.Type.Out(2) != typeOfError {
+ logger.Errorf("Generic func requires 3 out
types, func: %s(%s), "+
+ "require: (result interface{},
attachments map[string]interface{}, err error)",
+ funcField.Name, funcField.Type.String())
+ continue
+ }
+ f.Set(reflect.MakeFunc(f.Type(),
makeResultProxyFunc(funcName, p, attr)))
+ }
+ }
+}
+
+// make a function: func Invoke
+func makeResultProxyFunc(funcName string, proxy *proxy.Proxy, usrAttr
map[string]string) func([]reflect.Value) []reflect.Value {
+ // Invoke(goctx, []interface{}{service.Method, types, values}) (result,
attachments, error);
+ return func(funArgs []reflect.Value) []reflect.Value {
+ // Context
+ invCtx := funArgs[0].Interface().(context.Context)
+ invReply := reflect.New(typeOfInterface)
+ invArgs := funArgs[1].Interface().([]interface{})
+ invValues := funArgs[1]
+ inv := invocation.NewRPCInvocationWithOptions(
+ invocation.WithMethodName(funcName),
+ invocation.WithCallBack(proxy.GetCallback()),
+ invocation.WithArguments(invArgs),
+
invocation.WithParameterValues([]reflect.Value{invValues}),
+ invocation.WithReply(invReply.Interface()),
+ )
+ if invCtx == nil {
+ invCtx = context.Background()
+ }
+ // Build-in attachments
+ for k, value := range usrAttr {
+ inv.SetAttachments(k, value)
+ }
+ // User context attachments
+ attachments := invCtx.Value(constant.AttachmentKey)
+ if ssmap, ok := attachments.(map[string]string); ok {
+ for k, v := range ssmap {
+ inv.SetAttachments(k, v)
+ }
+ } else if simap, ok := attachments.(map[string]interface{}); ok
{
+ for k, v := range simap {
+ inv.SetAttachments(k, v)
+ }
+ } else {
+ logger.Errorf("Attachments requires map[string]string
OR map[string]interface{}, was: %T", attachments)
+ }
+ // Invoke and unwrap reply value
+ result := proxy.GetInvoker().Invoke(invCtx, inv)
+ if nil == result.Error() {
+ result.SetResult(invReply.Elem().Interface())
+ }
+ var ret, att, err = result.Result(), result.Attachments(),
result.Error()
+ return []reflect.Value{reflect.ValueOf(&ret).Elem(),
reflect.ValueOf(&att).Elem(), reflect.ValueOf(&err).Elem()}
+ }
+}
diff --git a/common/proxy/proxy_factory/generic_test.go
b/common/proxy/proxy_factory/generic_test.go
new file mode 100644
index 0000000..de280bd
--- /dev/null
+++ b/common/proxy/proxy_factory/generic_test.go
@@ -0,0 +1,55 @@
+/*
+ * 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 proxy_factory
+
+import (
+ "fmt"
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+import (
+ "github.com/apache/dubbo-go/common"
+ "github.com/apache/dubbo-go/protocol"
+)
+
+func TestGetResultProxy(t *testing.T) {
+ proxyFactory := NewGenericProxyFactory()
+ url := common.NewURLWithOptions()
+ proxy := proxyFactory.GetProxy(protocol.NewBaseInvoker(url), url)
+ assert.NotNil(t, proxy)
+}
+
+func TestGetAsyncResultProxy(t *testing.T) {
+ proxyFactory := NewGenericProxyFactory()
+ url := common.NewURLWithOptions()
+ proxy := proxyFactory.GetAsyncProxy(protocol.NewBaseInvoker(url),
func(res common.CallbackResponse) {
+ fmt.Println("Test callback res:", res)
+ }, url)
+ assert.NotNil(t, proxy)
+}
+
+func TestGetResultInvoker(t *testing.T) {
+ proxyFactory := NewGenericProxyFactory()
+ url := common.NewURLWithOptions()
+ invoker := proxyFactory.GetInvoker(url)
+ assert.True(t, invoker.IsAvailable())
+}
diff --git a/config/generic_service_ext.go b/config/generic_service_ext.go
new file mode 100644
index 0000000..cda1d76
--- /dev/null
+++ b/config/generic_service_ext.go
@@ -0,0 +1,41 @@
+/*
+ * 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 config
+
+import (
+ "context"
+)
+
+// GenericService2 uses for generic invoke for service call,
+// Unlike GenericService, it returns a tuple (interface{},
map[string]interface{}, error) called by DubboRPC,
+// which contains the Attachments data returned by the DubboProvider interface
+type GenericService2 struct {
+ // Invoke this field will inject impl by proxy, returns (result
interface{}, attachments map[string]interface{}, err error)
+ Invoke func(ctx context.Context, args []interface{})
(interface{}, map[string]interface{}, error) `dubbo:"$invoke"`
+ referenceStr string
+}
+
+// NewGenericService2 returns a GenericService2 instance
+func NewGenericService2(referenceStr string) *GenericService2 {
+ return &GenericService2{referenceStr: referenceStr}
+}
+
+// Reference gets referenceStr from GenericService2
+func (u *GenericService2) Reference() string {
+ return u.referenceStr
+}