写点什么

「Go 框架」深入理解 iris 中的 mvc 之原理篇

作者:Go学堂
  • 2023-02-17
    北京
  • 本文字数:4017 字

    阅读完需:约 13 分钟

「Go框架」深入理解iris中的mvc之原理篇

一、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/home 就能访问到 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 文档。

发布于: 刚刚阅读数: 5
用户头像

Go学堂

关注

微信公众号:Go学堂 2019-08-06 加入

专注Go编程知识、案例、常见错误及原理分析。意在通过阅读更多优秀的代码,提高编程技能。同名公众号「Go学堂」期待你的关注

评论

发布
暂无评论
「Go框架」深入理解iris中的mvc之原理篇_golang_Go学堂_InfoQ写作社区