当前位置:金屋文档› golang设计模式——代理模式

golang设计模式——代理模式

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

代理模式

目录

  • 代理模式
      • 静态代理:
      • 动态代理:
      • 代理模式的优点:
    • 代码实现
      • 静态代理
        • 代码
        • 单元测试
      • Go Generate 实现 “动态代理”
        • 需求
        • 代码
        • 单元测试
      • 仿照java的jdk动态代理实现go语言动态代理
        • 测试

静态代理:

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

动态代理:

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

代理模式的优点:

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

代码实现

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

静态代理

代码

package proxyimport(	"log"	"time")// IUser IUsertype IUser interface{	Login(username, password string)error}// User 用户type User struct{}// Login 用户登录func(u *User)Login(username, password string)error{	// 不实现细节	returnnil}// UserProxy 代理类type UserProxy struct{	user *User}// NewUserProxy NewUserProxyfuncNewUserProxy(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))	returnnil}

单元测试

package proxyimport(	"testing"	"github.com/stretchr/testify/require")funcTestUserProxy_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 IUsertype User struct{}

代码

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

  • 读取文件, 获取文件的 ast 语法树
  • 通过 NewCommentMap 构建 node 和 comment 的关系
  • 通过 comment 是否包含 @proxy 接口名 的接口,判断该节点是否需要生成代理类
  • 通过 Lookup 方法找到接口
  • 循环获取接口的每个方法的,方法名、参数、返回值信息
  • 将方法信息,包名、需要代理类名传递给构建好的模板文件,生成代理类
  • 最后用 format 包的方法格式化源代码
package proxyimport(	"bytes"	"fmt"	"go/ast"	"go/format"	"go/parser"	"go/token"	"strings"	"text/template")funcgenerate(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	}	returnstring(src),nil}// getParamsOrResults 获取参数或者是返回值// 返回带类型的参数,以及不带类型的参数,以逗号间隔funcgetParamsOrResults(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)		}		iflen(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,",")}funcgetProxyInterfaceName(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 proxyimport(	"testing"	"github.com/stretchr/testify/assert"	"github.com/stretchr/testify/require")funcTest_generate(t *testing.T){	want :=`package proxytype 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 proimport("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接口,类似多态}//创建新的代理funcNewProxy(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){deferfunc(){//用来捕捉异常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 proimport("fmt""testing""time")funcTestName(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\", 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

相关文档
  • 代理

  • 模式

  • 设计模式

相关文档推荐: