golang设计模式——代理模式
代理模式
目录
- 代理模式
- 静态代理:
- 动态代理:
- 代理模式的优点:
- 代码实现
- 静态代理
- 代码
- 单元测试
- Go Generate 实现 “动态代理”
- 需求
- 代码
- 单元测试
- 仿照java的jdk动态代理实现go语言动态代理
- 测试
静态代理:
- 代理类实现和目标类相同的接口,每个类都单独编辑一个代理类。
- 我们需要在代理类中,将目标类中的所有方法都要重新实现,并且为每个方法都附加相似的代码逻辑。
- 如果要添加方法增强的类不止一个,我们需要对每个类都创建一个代理类。
动态代理:
- 不需要为每个目标类编辑代理类。
- 在程序运行时,系统会动态地创建代理类,然后用代理类替换掉原始类。
- 一般采用反射实现。
代理模式的优点:
- 代理模式能将代理对象与真实被调用目标对象分离。
- 在一定程度上降低了系统的耦合性,拓展性好。
- 可以起到保护目标对象的作用。
- 可以增强目标对象的功能。
代码实现
接下来会通过 golang 实现静态代理,有 Golang 和 java 的差异性,我们无法比较方便的利用反射实现动态代理,但是我们可以利用go generate实现类似的效果,并且这样实现有两个比较大的好处,一个是有静态代码检查,我们在编译期间就可以及早发现问题,第二个是性能会更好。
静态代理
代码
package proxyimport ("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 proxyimport ("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 proxyimport ("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 fsetf, 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)// 类型转换,注意: 这里没有对断言进行判断,可能会导致 panict := 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 []stringparamNames []string)for i, param := range fields.List {// 循环获取所有的参数名var names []stringfor _, 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 proxyimport ("testing""github.com/stretchr/testify/assert""github.com/stretchr/testify/require"
)func Test_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接口,类似多态
}//创建新的代理
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 proimport ("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
golang设计模式——代理模式相关推荐
- Python设计模式-代理模式
Python设计模式-代理模式 基于Python3.5.2,代码如下 #coding:utf-8info_struct = dict() info_struct["addr"] = ...
- Java设计模式(代理模式-模板方法模式-命令模式)
Java设计模式Ⅴ 1.代理模式 1.1 代理模式概述 1.2 静态代理 1.2.1 静态代理概述 1.2.2 代码理解 1.3 动态代理之JDK代理 1.3.1 动态代理之JDK代理概述 1.3.2 ...
- 设计模式——代理模式
设计模式--代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能 ...
- 23种设计模式----------代理模式(一)
代理模式也叫委托模式. 代理模式定义:对其他对象提供一种代理从而控制对这个对象的访问.就是,代理类 代理 被代理类,来执行被代理类里的方法. 一般情况下,代理模式化有三个角色. 1,抽象的主题类(或者 ...
- java设计模式代理模式_Java中的代理设计模式
java设计模式代理模式 代理对象或代理对象为另一个对象提供占位符,以控制对该对象的访问. 代理充当原始对象的轻量级版本或简化版本. 它支持与原始对象相同的操作,但可以将那些请求委托给原始对象以实现它 ...
- 第四章 Caché 设计模式 代理模式
文章目录 第四章 Caché 设计模式 代理模式 定义 类型 使用场景 优点 缺点 结构图 完整示例 抽象主题类 真实主题类 代理类 对象类 调用 思考 第四章 Caché 设计模式 代理模式 定义 ...
- Android常见设计模式——代理模式(Proxy Pattern)(二)
文章目录 1. 前言 2. 远程代理(Remote Proxy) 3. 后记 1. 前言 在上篇Android常见设计模式--代理模式(Proxy Pattern)中基本上知道了什么是代理模式,以及对 ...
- sheng的学习笔记-设计模式-代理模式
原理图: 代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问.这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介. 至少在以下集中情况下可以用 ...
- 设计模式-代理模式(Proxy Pattern)
设计模式-代理模式(Proxy Pattern) 文章目录 设计模式-代理模式(Proxy Pattern) 一.定义 二.概念解释 三.场景 四.实现 1.类图 2.代码实现 五.小结 六.动态代理 ...
最新文章
- 遗传算法求解几何问题
- js生日计算年龄_JS根据生日算年龄的方法
- Docker 更换下载镜像源
- 如何在mapinfo中查看link ID
- win7计算机用户名在哪找,win7 c盘里找不到users,用户里也没有C:#92;User...-win7电脑c盘USERS文件夹在哪...
- 查看-增强会话_会话式人工智能-关键技术和挑战-第2部分
- 三年级优秀书籍推荐_三年级课外推荐阅读书目
- ZooKeeper动态重新配置
- 20161212 输出1到n之间所有的奇(单)数(n30000) 。
- 层级菜单构造--groupingby
- 7-59 二元一次方程的解
- 面对互联网寒冬裁员潮,HR都在干什么?
- 使用 Fabric CA 生成 MSP
- 广州高清卫星地图 用百度卫星地图server下载 含标签、道路数据叠加 可商用
- 【量亿数据-level2】唐奇安趋势跟踪系统
- 苹果usb设置在哪里_努比亚红魔3红魔3S打开开发者选项和USB调试功能
- 2022年湖南省临床执业医师考试第三单元综合(二)
- 一文搞懂Kiss、Yagni原则
- SetFocus()函数
- 天时地利人和—一个传奇操作系统的诞生记