предисловие
За последние несколько недель я прочитал жизненный цикл 4 фреймворков go (iris, gin, echo, beego) до и после.В процессе чтения я был весьма впечатлен их реализацией в промежуточном программном обеспечении фреймворка, и я всегда чувствовал, что реализация не идеальна..
Зачем?
1.За использование взимается плата.При внедрении нового промежуточного программного обеспечения вам необходимо вручную добавить строку кода `ctx.Next()` в бизнес-код, чтобы выполнить следующее промежуточное программное обеспечение.
2.Есть барьеры для чтения кода, затрудняют трудные для людей, чтобы понять. Люди, которые чувствуют, что они хотят понимать реализацию в первый раз, в основном будут запутаться в течение этого кода.
3.Промежуточное ПО — это все типы анонимных функций, недостаточно объектно-ориентированных.
Почему я говорю это, как выше?
Потому что, проще говоря, промежуточное ПО этого фреймворка на самом деле представляет собой процесс цепных вызовов. Однако, когда я думаю о сценарии цепных вызовов, первой реакцией в моем уме часто является шаблон цепочки ответственности в шаблоне проектирования. С помощью модели цепочки ответственности мы можем внедрить новое промежуточное ПО, не беспокоясь о ручном добавлении нового промежуточного ПО в бизнес-код.Next()
Вручную вызывать следующий объект, во-вторых, логика кода проста и понятна.
Давайте сначала посмотрим на основную структуру этой статьи:
верхняя половина | вторая половина |
---|---|
Внедрение промежуточного программного обеспечения beego framework | Концепция схемы цепочки ответственности |
Внедрение промежуточного программного обеспечения iris framework | Промежуточное ПО платформы реализации шаблона цепочки ответственности |
Внедрение промежуточного программного обеспечения gin framework | - |
Реализация промежуточного программного обеспечения эхо-фреймворка | - |
Во-первых, давайте взглянем на реализацию промежуточного программного обеспечения основной платформы go, а затем сравним мои идеи дизайна промежуточного программного обеспечения. Содержание этой статьи слишком длинное. Если вас интересует только реализация промежуточного программного обеспечения фреймворка в режиме цепочки ответственности, вы можете сразу перейти к этому модулю.
Анализ реализации промежуточного программного обеспечения мейнстрима go framework
Внедрение промежуточного программного обеспечения beego framework
Прежде всего, давайте взглянем на реализацию промежуточного программного обеспечения платформы beego. Реализация промежуточного программного обеспечения платформы Beego самая разная (естественная структура MVC), поэтому давайте сначала посмотрим на beego. Все знают, что beego определяет ` в интерфейсе контроллера. () `, beego предоставляет базовую структуру контроллера, а затем фактический бизнес-контроллер синтезирует и повторно использует этот базовый контроллер, а затем мы можем переписать `Prepare()` с помощью этого метода предварительного выполнения. Цель промежуточного программного обеспечения может быть достигнута. . код показывает, как показано ниже:
// 控制器接口type ControllerInterface interface { // 省略... // 具体控制器需要实现的预执行方法 Prepare() // 省略...}
скопировать код
Но в дополнение к обычно используемому `Prepare()`, на самом деле в beego есть метод `RunWithMiddleWares`, который мы можем использовать в качестве места для регистрации промежуточного программного обеспечения перед запуском. Код выглядит следующим образом:
// 注册中间件
func RunWithMiddleWares(addr string, mws ...MiddleWare) {
// ...
BeeApp.Run(mws...)
}
// ...
app.Server.Handler = app.Handlers
for i := len(mws) - 1; i >= 0; i-- {
if mws[i] == nil {
continue
}
app.Server.Handler = mws[i](app.Server.Handler)
}
скопировать код
Внедрение промежуточного программного обеспечения iris framework
iris — очень стандартное промежуточное ПО фреймворка, давайте подытожим его конкретную реализацию.
Определите типы всего промежуточного ПО:
// 定义了一个Handler类型 匿名函数类型// 所有的中间件必须是Handler类型// 所以在这些框架里中间件其实就是注册的闭包type Handler func(Context)
скопировать код
Анонимная функция прописана в свойстве промежуточного ПО типа slice:
func (api *APIBuilder) Use(handlers ...context.Handler) { api.middleware = append(api.middleware, handlers...)}
скопировать код
http.ServeHTTP выполняет первое промежуточное ПО (context.Handler) с индексом 0:
// http.ServeHTTP 也就是每次请求都会调这个Dofunc Do(ctx Context, handlers Handlers) { if len(handlers) > 0 { // 把当前的请求的中间件都挂载到上下文里 ctx.SetHandlers(handlers) // 执行第一个注册的索引为0的中间件 handlers[0](ctx) }}
скопировать код
Первая анонимная функция (промежуточное ПО) вызовет явное выполнениеctx.Next()
Перейдите к следующему промежуточному программному обеспечению, чтобы сформировать процесс цепного вызова, я извлек часть кода одного из промежуточного программного обеспечения следующим образом, мы видим, что анонимная функция наконец выполняется.ctx.Next()
:
return func(ctx context.Context) { // ... ctx.Next()}
скопировать код
Давайте взглянемctx.Next()
Конкретная реализация:
// 别名var Next = DefaultNext// 其实最终ctx.Next()执行的这里func DefaultNext(ctx Context) { if ctx.IsStopped() { return } // 这里是获取当前要执行的中间件的索引 // 我们先往下看ctx.HandlerIndex(-1)的逻辑 记住这里传的-1 其次currentHandlerIndex的默认值是0 // 我们通过分析ctx.HandlerIndex(-1)的逻辑得到ctx.HandlerIndex(-1)返回的是0 而 n就是0+1=1了 if n, handlers := ctx.HandlerIndex(-1)+1, ctx.Handlers(); n < len(handlers) { // 所以这里的n就是下一个中间件的索引1 ctx.HandlerIndex(n) // 执行下一个索引1 // 以此类推 // 类似递归执行 handlers[n](ctx) }}func (ctx *context) Next() { Next(ctx)}func (ctx *context) HandlerIndex(n int) (currentIndex int) { // 因为上面传的-1 if n < 0 || n > len(ctx.handlers)-1 { // 所以代码走到这里 // 假设这里是第一个中间件执行调用了这 所以currentHandlerIndex还是0 // 所以返回0 return ctx.currentHandlerIndex } ctx.currentHandlerIndex = n return n}
скопировать код
Внедрение промежуточного программного обеспечения gin framework
Общая идея дизайна middleware у gin такая же, как и у iris, но детали конкретной реализации отличаются от iris.В общем то же место:
-
Фактический тип промежуточного программного обеспечения также является определенной анонимной функцией.
-
Носителем промежуточного ПО также является слайс
разница:
-
Используемый цикл for используется для определения того, было ли выполнено все промежуточное ПО, а радужная оболочка оценивается по тому, если.
Конкретный код выглядит следующим образом:
// 同样的匿名函数 注册到类型为slice的中间件属性里func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { group.Handlers = append(group.Handlers, middleware...) return group.returnObj()}func (c *Context) Next() { // c.index默认值是-1 // 下面代码可以看出来 // 所以第一次是-1+1=0 第一个中间件 c.index++ // 重点:注意这个for循环的c.index++ // 如果c.handlers[c.index](c)执行中间件的方法又调用了Next // for循环的c.index++是不会执行的 // 类似内部递归了 for s := int8(len(c.handlers)); c.index < s; c.index++ { c.handlers[c.index](c) }}func (c *Context) reset() { // ... // c.index默认值是-1 c.index = -1 // ...}// 找了一个中间件的代码func Recovery() HandlerFunc { return RecoveryWithWriter(DefaultErrorWriter)}func RecoveryWithWriter(out io.Writer) HandlerFunc { // 省略... return func(c *Context) { defer func() { // 省略... }() // 调用下一个中间件 c.Next() }}
скопировать код
Реализация промежуточного программного обеспечения эхо-фреймворка
Хотя общая идея реализации промежуточного программного обеспечения у echo такая же, как у iris и gin, это единственный из этих фреймворков, который составляет так называемую цепочку вызовов. Как сказать эту разницу? Давайте сначала вернемся к промежуточному программному обеспечению iris и gin:
После выполнения промежуточного программного обеспечения вызовите ctx.Next(), чтобы найти следующее промежуточное программное обеспечение, которое будет выполнено через глобальный индекс, и выполните
Поэтому промежуточное программное обеспечение iris и gin не формирует цепочку, а затем выполняет ее. Промежуточная реализация echo делает это, что на самом деле очень просто: Echo сначала внедряет следующую анонимную функцию, которая будет выполняться, в текущую анонимную функцию через цикл for, а затем, наконец, выполняет ее. Давайте посмотрим на следующий код:
// 第一次遍历返回的匿名函数类型// 相对于iris、gin的中间件又封装了一层// 1. 函数里面返回匿名函数// 2. 匿名函数里面又返回匿名函数// 3. 匿名函数里又调用注入的匿名函数// 看起来是不是很累MiddlewareFunc func(HandlerFunc) HandlerFunc// 实际中间件业务的 匿名函数类型HandlerFunc func(Context) error// 摘了一个中间件代码片段func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc { // 省略... // 函数里面返回匿名函数 return func(next echo.HandlerFunc) echo.HandlerFunc { // 匿名函数里面又返回匿名函数 return func(c echo.Context) error { // 省略... }() // 匿名函数里又调用注入的匿名函数 return next(c) } }}// 和iris、gin一样的中间件注册方式// 只是在实际中间件匿名函数上又包装了一层匿名函数// 方便循环层层注入中间件func (e *Echo) Use(middleware ...MiddlewareFunc) { e.middleware = append(e.middleware, middleware...)}// 每次http请求都会执行这里func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) { // 省略... // 找路由handler e.findRouter(r.Host).Find(r.Method, getPath(r), c) h = c.Handler() // 遍历中间件 通过匿名函数构成链式调用 h = applyMiddleware(h, e.middleware...) // 省略... if err := h(c); err != nil { // 省略... }}func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc { // 通过匿名函数 循环层层注入中间件 构成链式调用 for i := len(middleware) - 1; i >= 0; i-- { h = middleware[i](h) } // 最后返回第一个注册进来的中间件 return h
}
Выше мы ознакомились с реализацией посредников iris, gin, echo и beego framework и, наконец, приступили к теме этой статьи:
Улучшенная реализация промежуточного программного обеспечения фреймворка
Внедрение Framework Middleware в режиме цепочки ответственности
Часть концепции паттерна «Цепь ответственности»: ряд объектов обработки объединяется в цепочку, и передается дизайн обрабатываемых объектов. Мы позаимствовали этот дизайн.
Реализация режима цепочки ответственности очень проста: после того, как объект (Handler) выполнил (Run()) и завершил свое дело (Do()), он определяет, существует ли следующий объект (nextHandler), и если да, то выполняет следующий объект (nextHandler.Do()). Кроме того, наш обработчик также должен иметь метод-член, который устанавливает следующий объект. Таким образом, структура uml нашего обработчика выглядит следующим образом:
модельный член | тип члена | имея в виду | степень абстракции | Мультиплексирование |
---|---|---|---|---|
nextHandler | Свойства члена | следующий объект | конкретный без изменений | Унифицированное определение и повторное использование, например прямое наследование |
Do | метод члена | свое дело | Разные объекты имеют разные реализации | Требуется абстракция (является абстрактным методом) |
SetNext | метод члена | Установить метод следующего объекта | конкретный без изменений | Унифицированное определение и повторное использование, например, прямое наследование родительского класса |
Run | метод члена | Выполнить текущий и следующий объект | конкретный без изменений | Унифицированное определение и повторное использование, например, прямое наследование родительского класса |
Теоретически, в соответствии с описанным выше процессом моделирования, мы можем абстрагировать абстрактный класс, конкретный обработчик наследует этот абстрактный класс, а затем реализует конкретные абстрактные методы.Do
То есть нет необходимости вручную вызывать следующий объект в бизнес-коде (элегантно, низкая стоимость доступа). Однако, так как в go нет концепции наследования, она не может удовлетворить наши потребности, однако мы можем добиться этого максимально путем синтеза и повторного использования (если посмотреть на метод реализации, который может быть унаследован, вы можете увидеть мой php код для реализации https://github.com/TIGERB/easy-tips/blob/master/patterns/chainOfResponsibility/test.php), uml-диаграмма финальной версии Go для синтеза и повторного использования выглядит следующим образом:
-
Все бизнес-обработчики реализуют интерфейс обработчика.
-
Структура Next реализует конкретный
nextHandler
свойства члена,SetNext
метод члена,Run
метод члена -
Бизнес-обработчик реализует определенные
Do
Метод-член, повторное использование синтеза бизнес-обработчиков ДалееnextHandler
свойства члена,SetNext
метод члена,Run
метод члена
Итак, в конце концов, новый бизнес-обработчик, который мы хотим реализовать, должен: 1. Синтезировать и повторно использовать Далее 2. Реализовать конкретныеDo
, не очень просто и изящно. Затем мы демонстрируем простоту, ясность и элегантность этого на реальном коде.
package mainimport ( "fmt")// Context Contexttype Context struct {}// Handler 处理type Handler interface { // 自身的业务 Do(c *Context) error // 设置下一个对象 SetNext(h Handler) Handler // 执行 Run(c *Context)}// Next 抽象出来的 可被合成复用的结构体type Next struct { // 下一个对象 nextHandler Handler}// SetNext 实现好的 可被复用的SetNext反方// 返回值是下一个对象 方便写成链式代码优雅// 例如 nullHandler.SetNext(argumentsHandler).SetNext(signHandler).SetNext(frequentHandler)func (n *Next) SetNext(h Handler) Handler { n.nextHandler = h return h}// Run 执行func (n *Next) Run(c *Context) { // 由于go无继承的概念 这里无法执行当前handler的Do // n.Do(c) if n.nextHandler != nil { // 合成复用下的变种 // 执行下一个handler的Do (n.nextHandler).Do(c) // 执行下一个handler的Run (n.nextHandler).Run(c) }}// NullHandler 空Handler// 由于go无继承的概念 作为链式调用的第一个载体 设置实际的下一个对象type NullHandler struct { // 合成复用Next的`nextHandler`成员属性、`SetNext`成员方法、`Run`成员方法 Next}// Do 空Handler的Dofunc (h *NullHandler) Do(c *Context) error { // 空Handler 这里什么也不做 只是载体 do nothing... return nil}// SignHandler 校验请求签名的handlertype SignHandler struct { // 合成复用Next Next}// Do 校验请求签名逻辑func (h *SignHandler) Do(c *Context) error { fmt.Println("校验签名成功...") return nil}// ArgumentsHandler 校验参数的handlertype ArgumentsHandler struct { // 合成复用Next Next}// Do 校验参数的逻辑func (h *ArgumentsHandler) Do(c *Context) error { fmt.Println("校验参数成功...") return nil}// FrequentHandler 校验请求频率的hanldertype FrequentHandler struct { Next}// Do 校验请求频率逻辑func (h *FrequentHandler) Do(c *Context) error { fmt.Println("校验请求频率成功...") return nil}func main() { // 初始化空handler nullHandler := &NullHandler{} // 初始化参数handler argumentsHandler := &ArgumentsHandler{} // 初始化签名handler signHandler := &SignHandler{} // 初始化频率handler frequentHandler := &FrequentHandler{} // 链式调用 代码是不是很优雅 // 很明显的链 逻辑关系一览无余 nullHandler.SetNext(argumentsHandler).SetNext(signHandler).SetNext(frequentHandler) nullHandler.Run(&Context{})}
// 执行结果
// [Running] go run "/Users/tigerb/github/easy-tips/go/src/go-learn/main.go"
// 校验参数成功...
// 校验签名成功...
// 校验请求频率成功...
скопировать код
Далее давайте посмотрим, как использовать шаблон цепочки ответственности в качестве реализации промежуточного программного обеспечения платформы. Мы по-прежнему используем приведенный выше код для реализации хорошей структуры. Конкретный код выглядит следующим образом:
// 初始化一个框架中间件切片middlewares := make([]Handler, 0)// 创建一个空的handler作为下一个中间件的载体middlewares = append(middlewares, nullHandler)// 注册中间件middlewares = append(middlewares, argumentsHandler)// 注册中间件middlewares = append(middlewares, signHandler)// 注册中间件middlewares = append(middlewares, frequentHandler)// 遍历中间件切片for k, handler := range middlewares { // 第一个中间件跳过 if k == 0 { continue } // 上一个中间件 设置 下一个中间件对象 middlewares[k-1].SetNext(handler)}// 开启链式调用过程nullHandler.Run(&Context{})
// 执行结果
// [Running] go run "/Users/tigerb/github/easy-tips/go/src/go-learn/main.go"
// 校验参数成功...
// 校验签名成功...
// 校验请求频率成功...
скопировать код
Суммировать
промежуточное ПО фреймворка | преимущество | недостаточный |
---|---|---|
beego | В соответствии с привычкой PHP-разработчиков использовать фреймворки |
Концепция промежуточного программного обеспечения недостаточно заметна, и эта концепция недостаточно абстрактна и изолирована. |
iris | Цепные вызовы, разделение и высокая возможность повторного использования во время выполнения | Ручной Next(), реализация не элегантная, цепочка вызовов не формируется перед выполнением |
gin | То же, что ирис | То же, что и iris, в дополнение к использованию цикла for (рекурсивный код в цикле for трудно читать) |
echo | То же, что и радужка, сначала сформируйте цепочку, а затем выполните | То же, что ирис |
мой | Объектно-ориентированная, понятная и простая логика вызова цепочки, низкая стоимость, нет необходимости вручную вставлять Next() в бизнес-код, элегантный | Концепция отсутствия наследования в go требует отдельной реализации пустого бизнес-объекта, который ничего не делает, как начало и носитель цепочки |
Ссылки на серию статей Go Framework Analysis: