关于go:Go框架深入理解iris中的mvc之原理篇

53次阅读

共计 5426 个字符,预计需要花费 14 分钟才能阅读完成。

一、mvc 的根本应用

在 iris 中,还封装了 mvc 包,该包能够让开发者疾速的搭建出基于 mvc(model-view-controller)分层的业务零碎。其根本应用如下:

package main

import (
    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/context"
    "github.com/kataras/iris/v12/mvc"
)

func main() {app := iris.New()

    // 基于 app.Party("/")分组 初始化 mvcApplication
    mvcApplication := mvc.New(app.Party("/"))

    // 注册 controller
    mvcApplication.Handle(new(testController))

    app.Listen(":8080")
}

//  定义 controller 处理器
type testController struct {Ctx *context.Context}

func (c *testController) GetHome() {c.Ctx.Writef(ctx.Method())
}

func (c *testController) Post() {c.Ctx.Writef(ctx.Method())
}

这样,就能搭建一个最简略的 controller 处理器了。而后运行该服务,在浏览器中输出 http://localhost:8080/home 就能拜访到 testControllerGetHome办法。

咱们晓得,在 iris 框架中,是须要注册路由的,行将门路对应到特定的 context.Handler 类型(函数类型)的处理器,当用户拜访对应的门路时,能力执行对应处理器上的函数体的逻辑的。那么,mvc 包 是如何做到门路到 controller 中函数的映射的呢?

二、mvc 的实现原理

通过下面应用基于 mvc 包 的示例,咱们能够理解到,mvc 包 的操作实际上是基于 mvc.Application 类型的实例进行的。在初始化 mvc.Application 实例的时候,传入参数是一个 app.Party 的对象,即一个路由组。因而,一个 mvc.Application 实例实质上就是一个路由分组。

而后,通过 mvc.Application.Handle 办法,将 testController 类型的对象进行注册。实际上是 controller依赖的对象(model、service等)注入到 controllerController 中的办法转换成路由 controller中执行的后果渲染到 view 的过程。

2.1 从 controller 到 ControllerActivator 的转换

在第一局部的代码示例中,当初始化了 mvc.Application 对象后,就是通过如下这行代码对 controller 中的办法进行转换的:

mvcApplication.Handle(new(testController))

代码就一行,很简略。但就是这个 Handle 函数将 testController 中的办法转换成了路由,使用户能够通过对应的门路拜访到 controller 中的办法。接下来咱们看看该 Handle 办法中都做了哪些事件。

点击进入源代码,直到 iris/mvc/mvc.go 文件中的 handle 办法,如下图示所示。

该办法显示,首先将 controller 包装成了一个 ControllerActivator 对象 c。其构造体如下:

各字段含意如下:

  • injector:该 controller 中依赖的字段类型。即该 controller 构造体中有哪些字段。这个咱们前面具体解释。
  • Value:该字段即在一开始 new 进去的 controller 的对象值。比方 new(testController)的值。
  • Typ:该字段是 controller 的具体类型。比方下面示例中的 testController 类型。
  • routes:controller 中每个函数名对应的具体的路由。

而后该对象 c 做了 3 件事件:执行对象 c 的 BeforeActivation 办法(如果 controller 中定义了该办法)、activate 办法和 AfterActivation 办法(如果定义了该办法)。咱们先跳过 BeforeActivation 和 AfterActivation 办法,重点看下 activate 办法。

2.2 ControllerActivator 的 activate 办法

c.activate办法的源码如下图所示,次要是 parseMethods 办法,依据名字也能猜到是要解析办法了。

咱们再进入 c.parseMethods 办法的源代码,如下:

这里的逻辑也很简略,先是获取该类型(即 controller 的构造体类型,例如示例中的 testController 类型)的办法个数,而后顺次遍历所有的办法,并对每个办法进行解析,也就是代码中的 c.parseMethod(m) 办法。

接下来进入到 c.parseMethod(m) 的代码逻辑中,看看该办法做了些什么。

这里看到,通过 parseMethod 解析进去了申请的办法名称 httpMethod(例如 GET、POST 等)、申请的门路 httpPath。而后再通过 c.Handle 函数将对应的申请办法和申请门路以及办法名注册成 iris 的惯例路由。

到这里咱们先总结一下 controller 转换的过程:

  • 首先,先将 controller 对象封装成 ControllerActivator 对象。该对象蕴含了 controller 类型的值以及具体的 controller 数据类型。
  • 其次,通过 reflect 包获取该 controller 类型的有哪些办法,并顺次遍历解析这些办法。
  • 而后,依据办法名称解析出规范的 HTTP 申请的办法以及申请门路,即代码中的 parseMethord 函数。
  • 最初,将该申请办法以及申请门路转换成 iris 惯例的路由,即代码中的 c.Handle 函数。

因为申请办法和申请门路是依据 controller 中的办法名称解析进去的,所以开发人员在给 controller 的办法的命名时,须要遵循以下规定

  • controller 中的办法必须采纳 驼峰式命名,且首字母必须大小。
  • parseMethod 会大写字母为宰割符,将办法名 宰割成多个单词。例如。GetHome 会宰割成 Get、Home 两个单词。办法名 HOME,则会被宰割成 H、O、M、E 四个单词。具体实现算法可查看源代码 methodLexer.reset 函数。
  • 办法名的第一个单词 必须是 http 申请的办法名。无效的申请办法为 GET、HEAD、POST、PUT、PATCH、DELETE、CONNECT、OPTIONS、TRACE。具体的实现可查看源码申请办法校验逻辑。
  • 从办法名的第二个单词开始,应用 "/" 符号将 各个单词连接成对应的申请门路 。所以办法名实际上是由 申请办法 + 申请门路 模式组成的。

例如在 testController 中有如下 GetMyHome 办法,那么,对应的申请门路是 /my/home。申请http://localhost:8080/my/home 就会执行 testController 的 GetMyHome 办法的逻辑。

  • 在办法名中能够通过 By能够在路由中减少 动静参数

例如在 testController 中有如下办法 GetByHome(username string),则对应的申请门路会被转换成 /{param1:string}/home。申请 http://localhost:8080/yufuzi/… 就能拜访到 testController 中的 GetByHome 函数的逻辑,并且在该办法中 username 的变量值就是门路中的yufizi。如下:

  • 若 By 在办法名的两头地位,一个 By 只对应一个动静参数;若 By 在办法名的最初,则办法中的所有输出参数都被解析成门路中的动静参数。

示例一 :By 在办法名两头地位
GetByHome 办法名中有两个参数,那么,拜访该门路http://localhost:8080/yufuzi/home 时就会报 panic。如下:

func (c *testController) GetByHome(username string, param2 int) {c.Ctx.Writef(c.Ctx.Method() + "" + username +" Home")
    c.Ctx.Writef("param2:" + param2) // 这里 param2 是对应类型的默认值:0
}

示例二:By 在办法名最初地位
GetHomeBy 办法名中有两个参数,则会转换成对应的门路 /home/{param1:string}/{param2:int}。如下:

func (c *testController) GetHomeBy(username string, param2 int) {c.Ctx.Writef(c.Ctx.Method() + "" + username +" Home")
    c.Ctx.Writef("param2:" + param2)
}

以上是对 controller 中办法的解析以及命名时须要恪守的规定。接下来,咱们持续看如何将 controller 的办法转换成具体的路由申请处理器。

2.3 ControllerActivator 的 Handle 办法 — 将 controller 中的办法转换成申请处理器

咱们晓得 iris 的规范路由处理器是 type Handler func(*Context) 类型 的这一个函数。那在 mvc 中,是如何将 controller 中的办法转换成这样规范的申请处理器类型的呢?

这个转换次要在 ControllerActivator.parseMethod 办法的第二局部的性能:ControllerActivator.Handle。进入该函数的源代码,直到 ControllerActivator.handleMany 函数,如下:

在 handleMany 函数中次要做了两件事:

  • 将函数转换成申请处理器。即 c.handlerOf 函数做的事件
  • 将申请解决注册成规范的路由。即 c.app.Router.HandleMany 函数做的事件。

这里咱们次要看 c.handlerOf 是如何将函数转换申请处理器的。至于注册成规范路由,大家能够参考这篇 iris 路由相干的文章:深刻了解 iris 路由的底层实现原理。

2.4 将 controller 的函数转换成规范的申请处理器类型

解决该性能的是逻辑在 handlerOf 函数中。上面是 handlerOf 的源代码,咱们看到次要做了两件事:一是 attachInjector 函数;一个是 injector 中的 MethodHandler 函数。

理论将 controller 的函数转换成申请处理器的是在 injector 的 MethodHandler 中进行的,即返回的 handler 对象。而 injector 就是在第一步的 c.attachInjector 函数中结构进去的。

那么,c.injector 是什么呢?上面咱们看下其对应的构造体,如下图:

能够看进去,injector 实际上是对 controller 具体类型及其对象的形容。还是以 testController 为例,在 Struct.ptrType 就是代表 testController 这种数据类型;Struct.ptrValue 代表的是 testController 对象值;bindings 代表的是在 testController 中有哪些字段值。比方 testController 中要是定义如下:

type testController struct {model *userModel}

那么,testController对象就须要绑定 userModel 类型的值。在实例化 testController 时,须要将一个具体的 userModel 类型的值赋值给 model 变量,这样在 testController 的办法中就能援用该变量从数据源中读取数据。而这种依赖就是保留在 bingdings 中的。

咱们次要看第二局部,c.injector.MethodHandler将函数转换成路由处理器类型。点击进入源码,直到 /iris/hero/handler.go 文件的 makeHandler 函数,如下所示(注:这里为了突出最终的返回值,省略了一些代码)。

首先,makeHandler返回值就是一个 context.Handler 类型的函数。在函数体内有 3 局部:获取 controller 函数的输出参数值、通过 v.Call 函数调用 controller 理论的函数体、通过 dispatchFuncResult 散发函数的返回值。

到此,就把 controller 中的函数转换成了规范的路由处理器类型context.Handler。并且,只有 controller 中的办法名的第一个单词是 HTTP 规范的申请办法(GET、HEAD、POST、PUT、PATCH、DELETE、CONNECT、OPTIONS、TRACE)时,才会将该办法主动注册成对应的路由。

在理解了 mvc 的实现原理后,下一篇咱们解说 mvc 的高级应用,敬请期待。

特地举荐:一个专一 go 我的项目实战、我的项目中踩坑教训及避坑指南、各种好玩的 go 工具的公众号,「Go 学堂」,专一实用性,十分值得大家关注。点击下方公众号卡片,间接关注。关注送《100 个 go 常见的谬误》pdf 文档。

正文完
 0