go的函数如何比较?

发布于:2025-06-21 ⋅ 阅读:(18) ⋅ 点赞:(0)

背景&问题

目前手动在写一个框架, 遇到一个很有意思的事情, 特此记录&分享一下
先看如下代码

// AddMiddleware 增加中间件
func (app *App) AddMiddleware(middlewares ...gin.HandlerFunc) {
	app.Middlewares = append(app.Middlewares, middlewares...)
}
// RemoveMiddleware 移除中间件
func (app *App) RemoveMiddleware(middlewares ...gin.HandlerFunc) {
	// 用户可以指定不使用某个中间件
	// WARNING 感觉这段代码有风险, 因为go的函数不能比较,有可能地址一样,但是行为不同,比如闭包
	// 但是对于中间件,一般都是在初始化的时候就确定了,不会在运行时动态改变,所以认为是安全的
	// 如果针对函数一定要比较,可以封装一个标识符

	// 每次调用都会返回 比如 ResponseMiddleware()都会返回一个新的匿名函数实例, 因此下面做法就是失败,而且风险大
	for _, middleware := range middlewares {
		sf1 := reflect.ValueOf(middleware)
		for i := 0; i < len(app.Middlewares); i++ {
			sf2 := reflect.ValueOf(app.Middlewares[i])
			if sf1.Pointer() == sf2.Pointer() {
				app.Middlewares = slices.Delete(app.Middlewares, i, i+1)
				i-- // 调整索引
			}
		}
	}
}

想法很简单,就是实现一个增加和移除中间件的功能, 框架自身初始化时候会默认带上某些中间件(比如response标准化输出),但是呢万一用户又不想使用,因此控制权交给用户,让用户自主选择。

但是自己在测试时候发现,RemoveMiddleware即使用了反射一直不生效,因此排查代码,发现函数中使用匿名函数实现,这样每次调用ResponseMiddleware函数,拿到的函数地址肯定不一样

// ResponseMiddleware 中间件:自动封装响应
func ResponseMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
	   ...
    }
}

事已至此,就发现问题,接下来就要看如何改动,成本低,风险小,更通用

解决方案

1. 修改匿名函数为全局函数

此方案呢,评估改动最小,后续所有中间件都定义全局变量,RemoveMiddleware代码天然复用

// ResponseMiddleware 中间件:自动封装响应
func ResponseMiddleware() gin.HandlerFunc {
	return responseMiddleware
}

func responseMiddleware(c *gin.Context) {
	c.Next()
}

但是还是不想用这玩意, 本身函数指针比较就是一个危险的事情,只能保证地址一样,不能保证函数行为一样

2. 给函数新增标识符

Middleware接口定义如下,通过name去判断,函数是否一致


type Middleware interface {
    Handler() gin.HandlerFunc
    Identifier() string
}

网站公告

今日签到

点亮在社区的每一天
去签到