进阶 www.iris-go.com 2026-10-20 02:39:14 · 18 阅读
第4章 依赖注入
# 依赖注入(Dependency Injection)
Iris 通过请求处理器和基于返回值的服务端响应,对依赖注入提供了一流的支持。
使用 Iris 可以获得真正安全的绑定。它的速度极快,性能接近原生处理器,因为我们在服务器上线之前就预先分配好了必要的信息!
依赖可以是函数,也可以是静态值。函数依赖还可以接收先前注册的依赖作为输入参数。
示例代码:
```go
func printFromTo(from, to string) string { return "message" }
// [...]
app.ConfigureContainer(func (api *iris.APIContainer){
api.Get("/{from}/{to}", printFromTo)
})
```
如上所示,`iris.Context` 这个输入参数完全是可选的。Iris 足够智能,会自动帮你绑定它,无需任何额外操作。
## 概述
一个路由最常见的处理场景是:
- 接收一个或多个路径参数和请求数据(payload)
- 返回一个响应(payload),如 JSON、XML 等
在上述场景中,新版 Iris 依赖注入功能比前代快了约 33.2%,进一步缩小了原生处理器与带依赖的处理器之间的性能差距。正是出于安全性和性能的考虑,我们推出了新的 `Party.ConfigureContainer(builder ...func(*iris.APIContainer)) *APIContainer` 方法,它会返回诸如 `Handle(method, relativePath string, handlersFn ...interface{}) *Route` 和 `RegisterDependency` 这样的方法。
看看使用 Iris 后,你的代码可以有多简洁:
```go
package main
import "github.com/kataras/iris/v12"
type (
testInput struct {
Email string `json:"email"`
}
testOutput struct {
ID int `json:"id"`
Name string `json:"name"`
}
)
func handler(id int, in testInput) testOutput {
return testOutput{
ID: id,
Name: in.Email,
}
}
func main() {
app := iris.New()
app.ConfigureContainer(func(api *iris.APIContainer) {
api.Post("/{id:int}", handler)
})
app.Listen(":5000", iris.WithOptimizations)
}
```
你的眼睛没骗你。你看得很仔细,这里既没有 `ctx.ReadJSON(&v)`,也没有 `ctx.JSON(send)`,更没有错误处理。虽然如此简洁让人很安心,但如果你确实需要这些功能,你依然可以完全掌控它们,包括来自依赖的错误。以下是 `Party.ConfigureContainers` 新增字段和方法的快速列表:
```go
// Container 持有该 Party 特有的依赖注入容器。
// 用它可将函数或结构体(控制器)手动转换为 Handler。
Container *hero.Container
// OnError 为该 Party 的 DI Hero Container 及其 handler(或 controller)添加错误处理器。
// "errorHandler" 负责处理 Party 的 hero handler 在依赖注入过程中或 handler 自身执行时
// 可能产生并返回的任何错误。
OnError(errorHandler func(iris.Context, error))
// RegisterDependency 添加一个依赖。
// 该值可以是单个结构体值或一个函数。
// 遵循以下规则:
// * {structValue}
// * func(accepts ) 返回 或 (, error)
// * func(accepts iris.Context) 返回 或 (, error)
//
// 一个 Dependency 可以接受之前已注册的依赖,并返回一个新依赖或更新后的同一依赖。
// * func(accepts1 , accepts2 ) 返回 或 (, error) 或 error
// * func(acceptsPathParameter1 string, id uint64) 返回 或 (, error)
//
// 用法示例:
//
// - RegisterDependency(loggerService{prefix: "dev"})
// - RegisterDependency(func(ctx iris.Context) User {...})
// - RegisterDependency(func(User) OtherResponse {...})
RegisterDependency(dependency interface{})
// UseResultHandler 向 Container 添加一个结果处理器。
// 结果处理器可用于注入请求 handler 返回的结构体值,或替换默认的渲染器。
UseResultHandler(handler func(next iris.ResultHandler) iris.ResultHandler)
type ResultHandler func(ctx iris.Context, v interface{}) error
// Use 与普通 Party 的 "Use" 类似,但它接受动态函数作为 "handlersFn" 输入。
Use(handlersFn ...interface{})
// Done 与普通 Party 的 "Done" 类似,但它接受动态函数作为 "handlersFn" 输入。
Done(handlersFn ...interface{})
```
4.1 依赖注入与处理器函数
`Handle` 方法的行为与普通 Party 的 `Handle` 一致,但它接受一个或多个 `handlersFn` 函数。每个函数都可以接收与 Party 已注册的容器 `Dependencies` 相匹配的任意输入参数,以及任意输出结果。这些结果可以是自定义结构体 ``、`string`、`[]byte`、`int`、`error`、上述类型的组合,甚至是 `hero.Result`(`hero.View` 或 `hero.Response`)等。
通常情况下,Hero 的处理器甚至不需要接收 `Context`。为此,如果开发者没有手动调用,`handlersFn` 会自动调用 `ctx.Next()`。若要停止执行且不再继续调用下一个 `handlersFn`,终端开发者应输出一个错误并返回 `iris.ErrStopExecution`。
```go
Handle(method, relativePath string, handlersFn ...interface{}) *Route
// Get 注册一个 GET 路由,等同于 `Handle("GET", relativePath, handlersFn....)`。
Get(relativePath string, handlersFn ...interface{}) *Route
// 依此类推……
```
4.2 内置依赖列表
以下是所有可用的内置依赖列表:Request、Response 和 Path Parameters。
步骤 1. 声明 Go 类型用于客户端请求体和服务端响应
```go
type (
request struct {
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
}
response struct {
ID uint64 `json:"id"`
Message string `json:"message"`
}
)
```
步骤 2. 创建路由处理器
路径参数和请求体会被自动绑定:
- `id uint64` 绑定到 `"id:uint64"`
- `input request` 绑定到客户端请求数据(如 JSON)
```go
func updateUser(id uint64, input request) response {
return response{
ID: id,
Message: "User updated successfully",
}
}
```
步骤 3. 按组配置容器并注册路由
```go
app.Party("/user").ConfigureContainer(container)
func container(api *iris.APIContainer) {
api.Put("/{id:uint64}", updateUser)
}
```
步骤 4. 模拟客户端请求,发送数据到服务器并显示响应
```bash
curl --request PUT -d '{"firstanme":"John","lastname":"Doe"}' http://localhost:8080/user/42
```
```javascript
{
"id": 42,
"message": "User updated successfully"
}
```
# 自定义 Preflight
在继续下一节「注册依赖」之前,你可能会想了解如何在响应发送给客户端之前,通过 iris.Context 对其进行自定义。
服务器会在把响应发送给客户端之前,自动执行函数返回值结构体上的 Preflight(iris.Context) error 方法。
举个例子:假设你想根据 handler 内部的自定义逻辑返回不同的 HTTP 状态码,并且想在响应发送给客户端之前修改响应体的内容。这时你的响应类型可以定义一个如下所示的 Preflight 方法。
gotype response struct {
ID uint64 `json:"id,omitempty"`
Message string `json:"message"`
Code int `json:"code"`
Timestamp int64 `json:"timestamp,omitempty"`
}
func (r *response) Preflight(ctx iris.Context) error {
if r.ID > 0 {
r.Timestamp = time.Now().Unix()
}
if r.Code > 0 {
ctx.StatusCode(r.Code)
}
return nil
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这样,所有返回 *response 值的 handler 都会自动调用 response.Preflight 方法。 gofunc deleteUser(db *sql.DB, id uint64) *response { // [...custom logic] return &response{ Message: "User has been marked for deletion", Code: iris.StatusAccepted, } }1
2
3
4
5
6
7
8
注册路由并发起请求后,你会看到如下输出:timestamp 字段已被填充,客户端收到的 HTTP 状态码为 202(Status Accepted)。 javascript{ "message": "User has been marked for deletion", "code": 202, "timestamp": 1583313026 }1
2
3
4
5
# 注册依赖 1. 导入与数据库交互所需的包。go-sqlite3 是 SQLite 的数据库驱动。 goimport "database/sql" import _ "github.com/mattn/go-sqlite3"1
2
2. 配置容器以注册你的依赖。Handler 需要一个 *sql.DB 实例。 golocalDB, _ := sql.Open("sqlite3", "./foo.db") api.RegisterDependency(localDB)1
2
3. 注册创建用户的路由。 goapi.Post("/{id:uint64}", createUser)1
4. 创建用户处理器(Handler)。该 Handler 接收数据库及客户端请求数据(如 JSON、Protobuf、Form、URL Query 等),并返回响应。 ```go func createUser(db *sql.DB, user request) *response { // [使用 db 的自定义逻辑] userID, err := db.CreateUser(user) if err != nil { return &response{ Message: err.Error(), Code: iris.StatusInternalServerError, } } return &response{ ID: userID, Message: "User created", Code: iris.StatusCreated, } } ``` 5. 模拟客户端创建用户。 ```bash # JSON curl --request POST -d '{"firstname":"John","lastname":"Doe"}' \ --header 'Content-Type: application/json' \ http://localhost:8080/user ``` ```bash # Form (multipart) curl --request POST 'http://localhost:8080/users' \ --header 'Content-Type: multipart/form-data' \ --form 'firstname=John' \ --form 'lastname=Doe' ``` ```bash # Form (URL-encoded) curl --request POST 'http://localhost:8080/users' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'firstname=John' \ --data-urlencode 'lastname=Doe' ``` ```bash # URL Query curl --request POST 'http://localhost:8080/users?firstname=John&lastname=Doe' ``` 响应: ```javascript { "id": 42, "message": "User created", "code": 201, "timestamp": 1583313026 } ``` 返回值 查看所有输出值及可能结果的列表。 ```go type response struct { ID uint64 `json:"id,omitempty"` Message string `json:"message"` Code int `json:"code"` Timestamp int64 `json:"timestamp,omitempty"` } func (r *response) Preflight(ctx iris.Context) error { if r.ID > 0 { r.Timestamp = time.Now().Unix() } ctx.StatusCode(r.Code) return nil } func deleteUser(db *sql.DB, id uint64) *response { // [...自定义逻辑] return &response{ Message: "User has been marked for deletion", Code: iris.StatusAccepted, } }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这样,所有返回 *response 值的 handler 都会自动调用 response.Preflight 方法。 gofunc deleteUser(db *sql.DB, id uint64) *response { // [...custom logic] return &response{ Message: "User has been marked for deletion", Code: iris.StatusAccepted, } }1
2
3
4
5
6
7
8
注册路由并发起请求后,你会看到如下输出:timestamp 字段已被填充,客户端收到的 HTTP 状态码为 202(Status Accepted)。 javascript{ "message": "User has been marked for deletion", "code": 202, "timestamp": 1583313026 }1
2
3
4
5
# 注册依赖 1. 导入与数据库交互所需的包。go-sqlite3 是 SQLite 的数据库驱动。 goimport "database/sql" import _ "github.com/mattn/go-sqlite3"1
2
2. 配置容器以注册你的依赖。Handler 需要一个 *sql.DB 实例。 golocalDB, _ := sql.Open("sqlite3", "./foo.db") api.RegisterDependency(localDB)1
2
3. 注册创建用户的路由。 goapi.Post("/{id:uint64}", createUser)1
4. 创建用户处理器(Handler)。该 Handler 接收数据库及客户端请求数据(如 JSON、Protobuf、Form、URL Query 等),并返回响应。 ```go func createUser(db *sql.DB, user request) *response { // [使用 db 的自定义逻辑] userID, err := db.CreateUser(user) if err != nil { return &response{ Message: err.Error(), Code: iris.StatusInternalServerError, } } return &response{ ID: userID, Message: "User created", Code: iris.StatusCreated, } } ``` 5. 模拟客户端创建用户。 ```bash # JSON curl --request POST -d '{"firstname":"John","lastname":"Doe"}' \ --header 'Content-Type: application/json' \ http://localhost:8080/user ``` ```bash # Form (multipart) curl --request POST 'http://localhost:8080/users' \ --header 'Content-Type: multipart/form-data' \ --form 'firstname=John' \ --form 'lastname=Doe' ``` ```bash # Form (URL-encoded) curl --request POST 'http://localhost:8080/users' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'firstname=John' \ --data-urlencode 'lastname=Doe' ``` ```bash # URL Query curl --request POST 'http://localhost:8080/users?firstname=John&lastname=Doe' ``` 响应: ```javascript { "id": 42, "message": "User created", "code": 201, "timestamp": 1583313026 } ``` 返回值 查看所有输出值及可能结果的列表。 ```go type response struct { ID uint64 `json:"id,omitempty"` Message string `json:"message"` Code int `json:"code"` Timestamp int64 `json:"timestamp,omitempty"` } func (r *response) Preflight(ctx iris.Context) error { if r.ID > 0 { r.Timestamp = time.Now().Unix() } ctx.StatusCode(r.Code) return nil } func deleteUser(db *sql.DB, id uint64) *response { // [...自定义逻辑] return &response{ Message: "User has been marked for deletion", Code: iris.StatusAccepted, } }