Golang中如何实现类似Java的动态代理?

Golang中如何实现类似Java的动态代理?

动机

我开发了一种用于反序列化JSON的模式,该JSON的键具有多种可能的值类型。 我知道,这很麻烦,但当它来自一个你无法控制的API时,除了在你这边缓解问题之外,你还能做什么呢。

假设你有一个具有3种不同类型的对象,我希望根据类型将其反序列化到不同的接口,并且该接口可以简单地委托给原生的 map[string]interface{}

之前在Java中已经做过一次,效果非常好!

我为Java编写了一个不可变对象库,它使用 Map<String,Object> 作为委托给一个 interface 的代理,该接口使用 Dynamic Proxy 将调用路由到被委托的映射。这样做的好处是,我以极少的努力就得到了一个非常棒的“版本化”不可变对象实现。

这使我能够使用委托给 Map<String,Object>Interfaces,并在运行时切换接口以提供正确的语义。

这只是我编写的库中的一小部分,但它是其核心部分。相当直接明了。

“变更”会创建委托给前一个映射的稀疏映射。

接口方法名是映射中的键,并返回值;如果键不存在,它只是在嵌套映射中搜索,直到找到键为止。

我认为我已经进行了相当全面的搜索,但还没有在Go中找到任何做同样事情的东西。我可能错了。

我正寻求在Go中实现等效功能,并看看如何用 Interfacemap[string]interface{} 来实现,因为我要将一个我非常熟悉的、相当庞大的代码库从Java“移植”到Go。

在我重新发明这个特定的轮子之前,我想看看是否已经有人在我之前做过类似的事情。


更多关于Golang中如何实现类似Java的动态代理?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

经过大量搜索、阅读和点击谷歌上的各种线索后,我终于找到了一篇文章,它告诉了我需要知道的内容。

反射有一个很大的限制。虽然你可以使用反射来创建新的函数,但无法在运行时创建新的方法。这意味着你不能使用反射在运行时实现一个接口。

就是这样了。除了代码生成(我已经有一个解决方案,用于从JSON模式生成大量样板代码来映射嵌套的结构体/接口),没有其他办法。

我曾希望能简化那些生成的代码,比如,调用一个方法 => 拦截该方法的调用,然后执行其他操作……

显然,这需要将 Interface 的实例创建为一个结构体,并在运行时实现其方法。

更多关于Golang中如何实现类似Java的动态代理?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我想我理解了你问题中关于“覆盖”的部分:这听起来很像 context.Context.Value。你可以用类似这样的代码来实现:

type Map interface {
    Get(key interface{}) (value interface{})
    Set(key, value interface{})
}

type goMap map[interface{}]interface{}

func (m goMap) Get(key interface{}) interface{} {
    return m[key]
}

func (m goMap) Set(key, value interface{}) {
    m[key] = value
}

type fallbackMap struct {
    Map
    Fallback Map
}

func (m fallbackMap) Get(key interface{}) interface{} {
    v := m.Map.Get(key)
    if v != nil {
        return v
    }
    return v.Fallback.Get(key)
}

func MakeFallbackMap(fallback Map) Map {
    return fallbackMap{make(goMap), fallback}
}

但我不懂Java,所以不太确切理解“动态代理”是什么,也不清楚你展示的代码具体做了什么。听起来你是想拦截对某个东西的调用?你能写一些伪代码来演示在Go中使用这个API会是什么样子吗?

我看到你已经提到目前无法生成方法,但我仍然对此功能的意义感到好奇。我理解的使用方式大概是这样的:

type Property interface {
    Name() string
    Type() string
}


func DynamicPropertyOf(m map[string]interface{}) Property {
    var p Property
    ProxyMap(m, &p)    // 某个创建属性实现的魔法函数
    return p
}

func DoSomethingWithProperty(m map[string]interface{}) (name, ptype string) {
    // 这就是你希望的使用方式吗?
    p := DynamicPropertyOf(m)
    return p.Name(), p.Type()
}

如果是这种情况,那么这比下面的方式好在哪里呢:

type Property struct {
    Name string
    Type string
}

func PropertyOf(m map[string]interface{}) Property {
    var p Property
    ProxyMap(m, &p)    // 一个填充p字段的不那么魔法的函数
    return p
}

func DoSomethingWithProperty(m map[string]interface{}) (name, ptype string) {
    // 这就是你希望的使用方式吗?
    p := DynamicPropertyOf(m)
    return p.Name, p.Type
}

这是该想法的核心。

public class MapBackedJavaBeanProxy extends AbstractInvocationHandler {
    private MapBackedJavaBeanProxy(@Nonnull final Map<String, Object> values) {
        if (values instanceof ImmutableSortedMap) { this.values = values; }
        else { this.values = ImmutableSortedMap.copyOf(values); }
    }

    @Override
    protected Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable {
        final String methodName = method.getName();
        if (this.values.containsKey(methodName)) { return this.values.get(methodName); }
        else { throw new IllegalArgumentException(methodName + " does not exist in " + Joiner.on(',').join(values.keySet())); }
    }
}

handleInvocation() 方法是神奇的动态部分。

这基本上是将 Interface 方法名转换为一个键,以便从一个委托映射中查找返回值。这是 DynamicProxy 的基本功能。

动态地代理方法调用。

然后,你可以用它来提供一个接口的实现:

public static <T> Builder<T> as(@Nonnull final Class<T> cls)
    {
        return new Builder<T>()
        {
            @Override public T from(@Nonnull final Map<String, Object> map)
            {
                return Reflection.newProxy(cls, new MapBackedJavaBeanProxy(map));
            }
        };
    }

所以,在 Go 中,如果我有一个像这样的接口:

type Property interface {
	Name() string
	Type() string
}

我只需将其传递给 as() 方法,它就会自动将 Name()Type() 映射到 delegate_map["Name"]delegate_map["Type"]

到目前为止,我在 Go 中实现的最接近的代码如下。

type Property interface {
	Name() string
	Type() string
}

type property struct {
	name     string
	delegate map[string]interface{}
}

func (p property) Name() string {
	return p.name
}

func (p property) Type() string {
	return p.delegate["type"].(string)
}

虽然这给了我一个围绕委托映射实例的类型安全抽象,但它不像 Java 版本那样通过 method name 来实现。我必须手动实现接口中的每个方法。

我真的很希望能够像 Java 实现那样做,这对于包装像 JSON 或 XML 这样的东西非常强大,这些数据被解组到一个通用结构中,然后将其转换成一个可以动态使用的类型安全的东西。

感谢大家的回复!

type Property interface {
    Name() string
    Type() string
}

根据定义,它是不可变的,这也是我的主要目标。 它为我提供了一个更好的抽象来工作,我可以用另一个Property接口的委托实现来包装它,它仍然是同一个接口。

例如,如果我想让Name返回UpperCamelCase格式的内容,我只需用另一个接口包装它,并仅实现Name方法即可。

您的第二个示例可以通过以下方法实现:

type Property struct {
    Name string `json:"name"` <= 标签是使其工作的"魔法"
    Type string `json:"type" <= 标签是使其工作的"魔法"
}

不幸的是,那个标签的"魔法"只对已导出的字段有效。如果我想从一个不可变的结构体进行UnmarshalMarshall操作,我必须做类似以下的事情。

type Property interface {
	Name() string
	Type() string
}

type property struct {
	name     string
	_type     string
}

func (p *property) Name() string {
	return p.name
}

func (p *property) Type() string {
	return p._type
}

func (p *property) MarshalJSON() ([]byte, error) {
    // 一堆类似Java的样板映射代码 :-(
}
func (p *property) UnmarshalJSON(bytes []byte) error {
   // 一堆类似Java的样板映射代码 :-(
}

能够直接委托给一个map[string]interface{},可以消除我现在必须生成的所有样板代码,并使编组和解组操作变成一行代码。

如果有人对如何以更符合Go语言风格的方式更好地实现这一点有任何建议,我洗耳恭听!

这样做的动机是,我现在正在使用JSON Schema来定义我的领域对象,并且我编写了一个生成器来生成所有的样板代码。它工作得很好,但现在我正在重构生成器代码,它必须读取JSON Schema,这显然是JSON格式,并且除了map[string]interface{}之外,无法将其Unmarshall到任何其他类型,因为它可能包含几乎任何内容。

因此,我正在研究我在其他语言中开发的一种方法,即先Unmarshall到一个map,然后只需用一个代理接口包装每个map,该接口仅委托给map。这样,就实现了类型安全的map访问。

主要是因为JSON Schema的属性定义可以是多变量类型。我可以在运行时根据type进行切换,并选择适当的包装接口。有点像标准库的xml解析器对Token解析所做的那样。

在Go中实现类似Java动态代理的功能,可以通过reflect包和接口组合来实现。以下是一个示例,展示如何创建一个动态代理,将接口方法调用委托给一个map[string]interface{}

package main

import (
    "fmt"
    "reflect"
)

// 定义一个接口,模拟需要代理的方法
type MyInterface interface {
    GetName() string
    GetAge() int
}

// 动态代理结构体,包含一个委托的map
type DynamicProxy struct {
    delegate map[string]interface{}
}

// 实现InvocationHandler,处理接口方法调用
func (dp *DynamicProxy) Invoke(methodName string, args ...interface{}) []interface{} {
    // 从map中获取方法名对应的值
    if val, ok := dp.delegate[methodName]; ok {
        // 根据返回值类型处理
        switch v := val.(type) {
        case string:
            return []interface{}{v}
        case int:
            return []interface{}{v}
        default:
            return []interface{}{nil}
        }
    }
    return []interface{}{nil}
}

// 创建代理实例的函数
func NewProxy(delegate map[string]interface{}) MyInterface {
    dp := &DynamicProxy{delegate: delegate}
    
    // 使用反射创建接口的实例
    var result MyInterface
    val := reflect.ValueOf(&result).Elem()
    
    // 创建动态类型
    proxyType := reflect.New(reflect.TypeOf((*MyInterface)(nil)).Elem()).Elem()
    
    // 设置方法调用处理器
    handler := func(args []reflect.Value) []reflect.Value {
        methodName := args[0].MethodByName
        // 这里简化处理,实际需要根据方法名调用Invoke
        ret := dp.Invoke("GetName") // 示例,实际应根据方法名动态调用
        if len(ret) > 0 {
            return []reflect.Value{reflect.ValueOf(ret[0])}
        }
        return []reflect.Value{}
    }
    
    // 创建动态方法
    dynamicFunc := reflect.MakeFunc(reflect.TypeOf((*MyInterface)(nil)).Elem(), handler)
    proxyType.Set(dynamicFunc)
    
    return result
}

func main() {
    // 示例委托map
    delegate := map[string]interface{}{
        "GetName": "John Doe",
        "GetAge":  30,
    }
    
    // 创建代理实例
    proxy := NewProxy(delegate)
    
    // 调用接口方法
    fmt.Println("Name:", proxy.GetName())
    fmt.Println("Age:", proxy.GetAge())
}

这个示例展示了如何通过反射和map委托来实现动态代理。需要注意的是,Go的反射机制与Java的动态代理有所不同,需要手动处理方法调用和返回值。此外,Go中没有直接的动态代理支持,因此实现可能更为复杂。

对于更复杂的场景,可以考虑使用代码生成工具(如go generate)来生成代理代码,或者使用第三方库(如github.com/bouk/monkey)进行方法替换。但请注意,这些方法可能涉及底层操作,需要谨慎使用。

回到顶部