共计 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
就能拜访到 testController
的GetHome
办法。
咱们晓得,在 iris 框架中,是须要注册路由的,行将门路对应到特定的 context.Handler 类型
(函数类型)的处理器,当用户拜访对应的门路时,能力执行对应处理器上的函数体的逻辑的。那么,mvc 包
是如何做到门路到 controller
中函数的映射的呢?
二、mvc 的实现原理
通过下面应用基于 mvc 包
的示例,咱们能够理解到,mvc 包
的操作实际上是基于 mvc.Application
类型的实例进行的。在初始化 mvc.Application 实例的时候,传入参数是一个 app.Party 的对象,即一个路由组。因而,一个 mvc.Application
实例实质上就是一个路由分组。
而后,通过 mvc.Application.Handle
办法,将 testController 类型的对象进行注册。实际上是 将controller
依赖的对象(model、service
等)注入到 controller
中、将 Controller
中的办法转换成路由 、 将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 文档。