• golang设计模式——代理模式


    代理模式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    单元测试

    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    Go Generate 实现 “动态代理”

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

    需求

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

    // User 用户
    // @proxy IUser
    type User struct {
    }
    
    • 1
    • 2
    • 3
    • 4

    代码

    接来下我们会简单的实现这个需求,由于篇幅和时间的关系,我们会略过一些检查之类的代码,例如 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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172

    单元测试

    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    仿照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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    测试

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    参考我之前写的动态代理

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

  • 相关阅读:
    夏日炎炎 水域守护:北斗守护安全防线——为生命撑起智能保护伞
    groupnorm_backward反向公式推导
    技术对接36
    在vs code中创建一个名为 “django_env“ 的虚拟环境报错?!以下方法可以解决
    【苹果家庭推送】这是iPhone上SMS功能的严重安全漏洞
    Spring MVC组件之HandlerAdapter
    Docker 网络访问原理解密
    Spring 配置使用介绍
    数据库慢SQL排查及优化问题
    计算机毕设之基于Python+django+MySQL可视化的学习系统的设计与实现
  • 原文地址:https://blog.csdn.net/qq_53267860/article/details/126274651