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中实现等效功能,并看看如何用 Interface 和 map[string]interface{} 来实现,因为我要将一个我非常熟悉的、相当庞大的代码库从Java“移植”到Go。
在我重新发明这个特定的轮子之前,我想看看是否已经有人在我之前做过类似的事情。
更多关于Golang中如何实现类似Java的动态代理?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
经过大量搜索、阅读和点击谷歌上的各种线索后,我终于找到了一篇文章,它告诉了我需要知道的内容。
反射有一个很大的限制。虽然你可以使用反射来创建新的函数,但无法在运行时创建新的方法。这意味着你不能使用反射在运行时实现一个接口。
就是这样了。除了代码生成(我已经有一个解决方案,用于从JSON模式生成大量样板代码来映射嵌套的结构体/接口),没有其他办法。
我曾希望能简化那些生成的代码,比如,调用一个方法 => 拦截该方法的调用,然后执行其他操作……
显然,这需要将 Interface 的实例创建为一个结构体,并在运行时实现其方法。
更多关于Golang中如何实现类似Java的动态代理?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我看到你已经提到目前无法生成方法,但我仍然对此功能的意义感到好奇。我理解的使用方式大概是这样的:
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" <= 标签是使其工作的"魔法"
}
不幸的是,那个标签的"魔法"只对已导出的字段有效。如果我想从一个不可变的结构体进行Unmarshal和Marshall操作,我必须做类似以下的事情。
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)进行方法替换。但请注意,这些方法可能涉及底层操作,需要谨慎使用。


