前阵子本来打算重构博客,但是后来用 WordPress 的官方 Docker 镜像替代了之前自己配的 PHP 环境,问题就解决了。(猜测可能是之前的 PHP 环境没开 Opcache)现在网站的访问速度飞快,我其实也就没必要再浪费时间造轮子了。
又一个刚开的坑就这样匆匆结束了呀。🤣
重构时我使用的是 Macaron 框架,因为在他上面我看到了很多不可思议的亮点。Macaron 完美的解决了我之前用 Beego 时遇到的种种痛点,这让我很是兴奋。虽然这个站没能写完,但是我体验了 Macaron 里的种种“黑魔法”。遂打算写一篇文章来分享介绍一下。
什么是依赖注入?
首先要隆重介绍的就是依赖注入,这是一个在 Java 开发中经常用到的概念,又称控制反转。它让每一块代码只负责实现自己的功能,其他的交给“其他人”去做,当需要什么变量时,就和这个“其他人”说一声,他就会给你提供。
举个更形象的例子,依赖注入使得函数的入参不再是固定的:
func Foo(a string){
// Do something
}
func Bar(a string, b int){
// Do something
}
FoostringstringBarstringintstringint
这其实就是所谓的“控制反转”,被调用者不再受制于调用者的入参约定,而是调用者来设法满足被调用者。
再简单点说:使用依赖注入可以实现调用任意入参的函数,它会自己找数据注入填充。
依赖注入的 Go 语言实现
inject
injectInjector
type Injector interface {
Applicator // 这个接口用来灌入到一个结构体
Invoker // 这个接口用来实际调用的,所以可以实现非反射的实际调用
TypeMapper // 这个接口是真正的容器
SetParent(Injector) // 表示这个结构是递归的
}
TypeMapper
TypeMapper
type TypeMapper interface {
// 直接设置一个对象,TypeOf 是 key,value 是这个对象
Map(interface{}) TypeMapper
// 将一个对象注入到一个接口中,TypeOf 是接口,value 是对象,因为接口直接 TypeOf 获取只能拿到接口类型
MapTo(interface{}, interface{}) TypeMapper
// 手动设置 key 和 value
Set(reflect.Type, reflect.Value) TypeMapper
// 从容器中获取某个类型的注入对象
Get(reflect.Type) reflect.Value
}
injectinjector
type injector struct {
values map[reflect.Type]reflect.Value
parent Injector
}
values
Invoker
Invoke
func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
t := reflect.TypeOf(f)
var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func
for i := 0; i < t.NumIn(); i++ {
argType := t.In(i)
val := inj.Get(argType)
if !val.IsValid() {
return nil, fmt.Errorf("Value not found for type %v", argType)
}
in[i] = val
}
return reflect.ValueOf(f).Call(in), nil
}
invokevaluesGet()比如当我们的函数入参有两个 string 时,通过依赖注入获取到的 string 的值将会是同一个。这时我们需要自己额外声明一个类型来解决。
func Foo(firstName string, lastName string){
// Do something
}
type mystring string
func Bar(firstName string, lastName mystring)
👆这样就把两个参数通过类型给区分开来了。
injectFastInvoker
type FastInvoker interface {
// Invoke attempts to call the ordinary functions. If f is a function
// with the appropriate signature, f.Invoke([]interface{}) is a Call that calls f.
// Returns a slice of reflect.Value representing the returned values of the function.
// Returns an error if the injection fails.
Invoke([]interface{}) ([]reflect.Value, error)
}
injectInvokeFastInvokerInvoke
Applicator
Applicatorinjectvalues
一个简单的 Demo
type noString string
no := noString("10000") // 为了区分两个 string,定义了新的类型
name := "E99p1ant"
age := 20
inj := inject.New()
inj.Map(no)
inj.Map(name)
inj.Map(age)
inj.Invoke(func(age int) {
fmt.Printf("Your age is %d\n", age)
})
inj.Invoke(func(no noString, name string) {
fmt.Printf("Hi! %s - %s\n", no, name)
})
Context
以上介绍完了依赖注入,我们可以来看下 Macaron 的框架的 Context。
type Context struct {
inject.Injector
handlers []Handler
...
Contextinject.Injectorinterface{}
type MyContext struct {
*macaron.Context
Name string
}
func (c *MyContext) Greet() {
c.Write([]byte("Hello " + c.Name))
}
func main() {
m := macaron.Classic()
m.Use(func(ctx *macaron.Context) {
c := &MyContext{
Context: ctx,
Name: "",
}
ctx.Map(c)
})
m.Get("/", func(c *MyContext) {
c.Greet()
})
m.Run()
}
*macaron.ContextNameGreet()*macaron.Contextrun()
func (c *Context) run() {
for c.index <= len(c.handlers) {
vals, err := c.Invoke(c.handler())
if err != nil {
panic(err)
}
c.index += 1
...
*macaron.ContextInjectorInvoke*MyContextisLogin
具体的实践可以参考 gogs 。
binding
另一个使用依赖注入实现的“黑魔法”是 binding 包,他是用来验证和绑定传入的表单的。
因为有了依赖注入,我们可以直接将绑定表单的结构体作为参数传进 Handler 中:
func CategoryPost(c *context.Context, f form.NewCategory) {
if !c.HasError() {
err := db.Categories.New(&db.Category{
Name: f.Name,
})
if err != nil {
c.RenderWithErr(err.Error(), "admin/category/category", f)
return
}
}
c.Data["CategoryList"] = db.Categories.All()
c.Success("admin/category/category")
}
是不是很神奇!以往需要从 Context 中取的变量,现在可以单独作为函数的参数!
而在声明路由的时候:
r.Post("/category", binding.Bind(form.NewCategory{}), admin.CategoryPost)
binding.BindbindJsonvalidateAndMap
func validateAndMap(obj reflect.Value, ctx *macaron.Context, errors Errors, ifacePtr ...interface{}) {
_, _ = ctx.Invoke(Validate(obj.Interface()))
errors = append(errors, getErrors(ctx)...)
ctx.Map(errors)
ctx.Map(obj.Elem().Interface())
if len(ifacePtr) > 0 {
ctx.MapTo(obj.Elem().Interface(), ifacePtr[0])
}
}
可以看到在这里,绑定好数据的新结构体被注入进 Context 中。
之后再从 Context 中取得。
总结
以上就是我最近用到的 Macaron 使用依赖注入实现的“黑科技”。感觉 Macaron 才是一个 Go Web 框架该有的样子。虽然他在一定程度上使用了过多的反射,但却提高了开发的效率。
文章中如有描述不准确的地方,还请小伙伴们指出。