golang设计模式——代理模式

golang设计模式——代理模式接下来会通过golang实现静态代理,有Golang和java的差异性,我们无法比较方便的利用反射实现动态代理,但是我们可以利用。实现类似的效果,并且这样实现有两个比较大的好处,一个是有静态代码检查,我们在编译期间就可以及早发现问题,第二个是性能会更好。的注释,我们就会为这个struct生成一个proxy类,同时实现相同的接口,这个接口就是在注释中指定的接口。接来下我们会简单的实现这个需求,由于篇幅和时间的关系,我们会略过一些检查之类的代码,例如。@proxy接口名。…

大家好,欢迎来到IT知识分享网。

代理模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PS5Sb8Bu-1660137399344)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220810095505698.png)]

静态代理:

  1. 代理类实现和目标类相同的接口,每个类都单独编辑一个代理类。
  2. 我们需要在代理类中,将目标类中的所有方法都要重新实现,并且为每个方法都附加相似的代码逻辑。
  3. 如果要添加方法增强的类不止一个,我们需要对每个类都创建一个代理类。

动态代理:

  1. 不需要为每个目标类编辑代理类。
  2. 在程序运行时,系统会动态地创建代理类,然后用代理类替换掉原始类。
  3. 一般采用反射实现。

代理模式的优点:

  1. 代理模式能将代理对象与真实被调用目标对象分离。
  2. 在一定程度上降低了系统的耦合性,拓展性好。
  3. 可以起到保护目标对象的作用。
  4. 可以增强目标对象的功能。

代码实现

接下来会通过 golang 实现静态代理,有 Golang 和 java 的差异性,我们无法比较方便的利用反射实现动态代理,但是我们可以利用go generate实现类似的效果,并且这样实现有两个比较大的好处,一个是有静态代码检查,我们在编译期间就可以及早发现问题,第二个是性能会更好。

静态代理

代码

package proxy

import (
	"log"
	"time"
)

// IUser IUser
type IUser interface { 
   
	Login(username, password string) error
}

// User 用户
type User struct { 
   
}

// Login 用户登录
func (u *User) Login(username, password string) error { 
   
	// 不实现细节
	return nil
}

// UserProxy 代理类
type UserProxy struct { 
   
	user *User
}

// NewUserProxy NewUserProxy
func NewUserProxy(user *User) *UserProxy { 
   
	return &UserProxy{ 
   
		user: user,
	}
}

// Login 登录,和 user 实现相同的接口
func (p *UserProxy) Login(username, password string) error { 
   
	// before 这里可能会有一些统计的逻辑
	start := time.Now()

	// 这里是原有的业务逻辑
	if err := p.user.Login(username, password); err != nil { 
   
		return err
	}

	// after 这里可能也有一些监控统计的逻辑
	log.Printf("user login cost time: %s", time.Now().Sub(start))

	return nil
}

单元测试

package proxy

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func TestUserProxy_Login(t *testing.T) { 
   
	proxy := NewUserProxy(&User{ 
   })

	err := proxy.Login("test", "password")

	require.Nil(t, err)
}

Go Generate 实现 “动态代理”

注意: 在真实的项目中并不推荐这么做,因为有点得不偿失,本文只是在探讨一种可能性,并且可以复习一下 go 语法树先关的知识点
接下来我们先来看看需求。

需求

动态代理相比静态代理主要就是为了解决生产力,将我们从繁杂的重复劳动中解放出来,正好,在 Go 中 Generate 也是干这个活的
如下面的代码所示,我们的 generate 会读取 struct 上的注释,如果出现 @proxy 接口名 的注释,我们就会为这个 struct 生成一个 proxy 类,同时实现相同的接口,这个接口就是在注释中指定的接口

// User 用户
// @proxy IUser
type User struct { 
   
}

代码

接来下我们会简单的实现这个需求,由于篇幅和时间的关系,我们会略过一些检查之类的代码,例如 User 是否真正实现了 IUser 这种情况。
代码有点长,主要思路:

  • 读取文件, 获取文件的 ast 语法树
  • 通过 NewCommentMap 构建 node 和 comment 的关系
  • 通过 comment 是否包含 @proxy 接口名 的接口,判断该节点是否需要生成代理类
  • 通过 Lookup 方法找到接口
  • 循环获取接口的每个方法的,方法名、参数、返回值信息
  • 将方法信息,包名、需要代理类名传递给构建好的模板文件,生成代理类
  • 最后用 format 包的方法格式化源代码
package proxy

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"strings"
	"text/template"
)

func generate(file string) (string, error) { 
   
	fset := token.NewFileSet() // positions are relative to fset
	f, err := parser.ParseFile(fset, file, nil, parser.ParseComments)
	if err != nil { 
   
		return "", err
	}

	// 获取代理需要的数据
	data := proxyData{ 
   
		Package: f.Name.Name,
	}

	// 构建注释和 node 的关系
	cmap := ast.NewCommentMap(fset, f, f.Comments)
	for node, group := range cmap { 
   
		// 从注释 @proxy 接口名,获取接口名称
		name := getProxyInterfaceName(group)
		if name == "" { 
   
			continue
		}

		// 获取代理的类名
		data.ProxyStructName = node.(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Name.Name

		// 从文件中查找接口
		obj := f.Scope.Lookup(name)

		// 类型转换,注意: 这里没有对断言进行判断,可能会导致 panic
		t := obj.Decl.(*ast.TypeSpec).Type.(*ast.InterfaceType)

		for _, field := range t.Methods.List { 
   
			fc := field.Type.(*ast.FuncType)

			// 代理的方法
			method := &proxyMethod{ 
   
				Name: field.Names[0].Name,
			}

			// 获取方法的参数和返回值
			method.Params, method.ParamNames = getParamsOrResults(fc.Params)
			method.Results, method.ResultNames = getParamsOrResults(fc.Results)

			data.Methods = append(data.Methods, method)
		}
	}

	// 生成文件
	tpl, err := template.New("").Parse(proxyTpl)
	if err != nil { 
   
		return "", err
	}

	buf := &bytes.Buffer{ 
   }
	if err := tpl.Execute(buf, data); err != nil { 
   
		return "", err
	}

	// 使用 go fmt 对生成的代码进行格式化
	src, err := format.Source(buf.Bytes())
	if err != nil { 
   
		return "", err
	}

	return string(src), nil
}

// getParamsOrResults 获取参数或者是返回值
// 返回带类型的参数,以及不带类型的参数,以逗号间隔
func getParamsOrResults(fields *ast.FieldList) (string, string) { 
   
	var (
		params     []string
		paramNames []string
	)

	for i, param := range fields.List { 
   
		// 循环获取所有的参数名
		var names []string
		for _, name := range param.Names { 
   
			names = append(names, name.Name)
		}

		if len(names) == 0 { 
   
			names = append(names, fmt.Sprintf("r%d", i))
		}

		paramNames = append(paramNames, names...)

		// 参数名加参数类型组成完整的参数
		param := fmt.Sprintf("%s %s",
			strings.Join(names, ","),
			param.Type.(*ast.Ident).Name,
		)
		params = append(params, strings.TrimSpace(param))
	}

	return strings.Join(params, ","), strings.Join(paramNames, ",")
}

func getProxyInterfaceName(groups []*ast.CommentGroup) string { 
   
	for _, commentGroup := range groups { 
   
		for _, comment := range commentGroup.List { 
   
			if strings.Contains(comment.Text, "@proxy") { 
   
				interfaceName := strings.TrimLeft(comment.Text, "// @proxy ")
				return strings.TrimSpace(interfaceName)
			}
		}
	}
	return ""
}

// 生成代理类的文件模板
const proxyTpl = ` package { 
   {.Package}} type { 
   { .ProxyStructName }}Proxy struct { child *{ 
   { .ProxyStructName }} } func New{ 
   { .ProxyStructName }}Proxy(child *{ 
   { .ProxyStructName }}) *{ 
   { .ProxyStructName }}Proxy { return &{ 
   { .ProxyStructName }}Proxy{child: child} } { 
   { range .Methods }} func (p *{ 
   {$.ProxyStructName}}Proxy) { 
   { .Name }} ({ 
   { .Params }}) ({ 
   { .Results }}) { // before 这里可能会有一些统计的逻辑 start := time.Now() { 
   { .ResultNames }} = p.child.{ 
   { .Name }}({ 
   { .ParamNames }}) // after 这里可能也有一些监控统计的逻辑 log.Printf("user login cost time: %s", time.Now().Sub(start)) return { 
   { .ResultNames }} } { 
   { end }} `

type proxyData struct { 
   
	// 包名
	Package string
	// 需要代理的类名
	ProxyStructName string
	// 需要代理的方法
	Methods []*proxyMethod
}

// proxyMethod 代理的方法
type proxyMethod struct { 
   
	// 方法名
	Name string
	// 参数,含参数类型
	Params string
	// 参数名
	ParamNames string
	// 返回值
	Results string
	// 返回值名
	ResultNames string
}

单元测试

package proxy

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func Test_generate(t *testing.T) { 
   
	want := `package proxy type UserProxy struct { child *User } func NewUserProxy(child *User) *UserProxy { return &UserProxy{child: child} } func (p *UserProxy) Login(username, password string) (r0 error) { // before 这里可能会有一些统计的逻辑 start := time.Now() r0 = p.child.Login(username, password) // after 这里可能也有一些监控统计的逻辑 log.Printf("user login cost time: %s", time.Now().Sub(start)) return r0 } `
	got, err := generate("./static_proxy.go")
	require.Nil(t, err)
	assert.Equal(t, want, got)
}

仿照java的jdk动态代理实现go语言动态代理

package pro

import (
   "errors"
   "fmt"
   "reflect"
)

//提供动态调用方法接口
type InvocationHandler interface { 
   
   Invoke(proxy *Proxy, method *Method, args []interface{ 
   }) ([]interface{ 
   }, error)
}

//代理,用来总管代理类的生成
type Proxy struct { 
   
   target  interface{ 
   }        //目标类,后面的类型和java的Object一样
   methods map[string]*Method //map用来装载待增强的不同的方法
   handle  InvocationHandler  //用来暴露统一invoke接口,类似多态
}

//创建新的代理
func NewProxy(target interface{ 
   }, h InvocationHandler) *Proxy { 
   
   typ := reflect.TypeOf(target)          //用来显示目标类动态的真实类型
   value := reflect.ValueOf(target)       //获取目标类的值
   methods := make(map[string]*Method, 0) //初始化目标类的方法map
   //将目标类的方法逐个装载
   for i := 0; i < value.NumMethod(); i++ { 
   
      method := value.Method(i)
      methods[typ.Method(i).Name] = &Method{ 
   value: method}
   }
   return &Proxy{ 
   target: target, methods: methods, handle: h}
}

//代理调用代理方法
func (p *Proxy) InvokeMethod(name string, args ...interface{ 
   }) ([]interface{ 
   }, error) { 
   
   return p.handle.Invoke(p, p.methods[name], args)
}

//用来承载目标类的方法定位和调用
type Method struct { 
   
   value reflect.Value //用来装载方法实例
}

//这里相当于调用原方法,在该方法外可以做方法增强,需要调用者自己实现!!!
func (m *Method) Invoke(args ...interface{ 
   }) (res []interface{ 
   }, err error) { 
   
   defer func() { 
   
      //用来捕捉异常
      if p := recover(); p != nil { 
   
         err = errors.New(fmt.Sprintf("%s", p))
      }
   }()

   //处理参数
   params := make([]reflect.Value, 0)
   if args != nil { 
   
      for i := 0; i < len(args); i++ { 
   
         params = append(params, reflect.ValueOf(args[i]))
      }
   }

   //调用方法
   call := m.value.Call(params)

   //接收返回值
   res = make([]interface{ 
   }, 0)
   if call != nil && len(call) > 0 { 
   
      for i := 0; i < len(call); i++ { 
   
         res = append(res, call[i].Interface())
      }
   }
   return
}

测试

package pro

import (
   "fmt"
   "testing"
   "time"
)

func TestName(t *testing.T) { 
   
   //这里对活动时长做统计
   people := &People{ 
   }   //创建目标类
   h := new(PeopleProxy) //创建接口实现类
   proxy := NewProxy(people, h)
   //调用方法
   ret, err := proxy.InvokeMethod("Work", "敲代码", "学习")
   if err != nil { 
   
      fmt.Println(err)
   }

   fmt.Println(ret)
}

//目标类
type People struct { 
   
}

func (p *People) Work(content string, next string) string { 
   
   fmt.Println("活动内容是:" + content + ",接下来需要做:" + next)
   return "all right"
}

//用户需要自己实现的增强内容,需要实现InvocationHandler接口
type PeopleProxy struct { 
   
}

//在这里做方法增强
func (p *PeopleProxy) Invoke(proxy *Proxy, method *Method, args []interface{ 
   }) ([]interface{ 
   }, error) { 
   
   start := time.Now()
   defer fmt.Printf("耗时:%v\n", time.Since(start))
   fmt.Println("before method")
   invoke, err := method.Invoke(args...)
   fmt.Println("after method")
   return invoke, err
}

参考我之前写的动态代理

https://blog.csdn.net/qq_53267860/article/details/126164229?spm=1001.2014.3001.5501

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/20805.html

(0)
上一篇 2024-02-05 09:00
下一篇 2024-02-05 18:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信