自动配置
简介
Hiboot 自动配置根据你导入的包自动配置应用程序。当你导入一个 starter 包时,Hiboot 会检测到它并配置必要的组件以供依赖注入使用。
这种方式借鉴了 Spring Boot 的自动配置机制,并适配了 Go 的包系统。
自动配置原理
- 在应用程序中导入 starter 包
- starter 的
init()函数注册其配置 - Hiboot 在启动时扫描已注册的配置
- 调用配置方法创建可注入的组件
- 组件可用于依赖注入
内置 Starter
Hiboot 提供了多个内置 starter:
| Starter | 包路径 | 描述 |
|---|---|---|
| Actuator | github.com/hidevopsio/hiboot/pkg/starter/actuator |
健康检查和指标 |
| Logging | github.com/hidevopsio/hiboot/pkg/starter/logging |
结构化日志 |
| JWT | github.com/hidevopsio/hiboot/pkg/starter/jwt |
JWT 认证 |
| Locale | github.com/hidevopsio/hiboot/pkg/starter/locale |
国际化 |
使用 Starter
package main
import (
"github.com/hidevopsio/hiboot/pkg/app"
"github.com/hidevopsio/hiboot/pkg/app/web"
"github.com/hidevopsio/hiboot/pkg/starter/actuator"
"github.com/hidevopsio/hiboot/pkg/starter/logging"
)
func main() {
web.NewApplication().
SetProperty(app.ProfilesInclude, actuator.Profile, logging.Profile).
Run()
}
创建自定义 Starter
一个 starter 通常包含:
- 配置结构体 - 嵌入
app.Configuration并定义工厂方法 - 属性结构体 - 保存来自 YAML 文件的配置值
- 服务结构体 - 实际要注入的组件
项目结构
starter/
└── myservice/
├── autoconfigure.go # 配置和注册
├── properties.go # 属性结构体
├── service.go # 服务实现
└── doc.go # 包文档
步骤 1:定义属性
属性保存可在 application.yml 中自定义的配置值:
package myservice
// Properties 保存 MyService 的配置
type Properties struct {
Enabled bool `json:"enabled" default:"true"`
Endpoint string `json:"endpoint" default:"http://localhost:8080"`
Timeout int `json:"timeout" default:"30"`
MaxRetry int `json:"max_retry" default:"3"`
}
步骤 2:定义服务
package myservice
import "fmt"
// Service 是 MyService 的接口
type Service interface {
DoSomething(data string) (string, error)
}
// serviceImpl 是实现
type serviceImpl struct {
endpoint string
timeout int
maxRetry int
}
func newService(endpoint string, timeout, maxRetry int) Service {
return &serviceImpl{
endpoint: endpoint,
timeout: timeout,
maxRetry: maxRetry,
}
}
func (s *serviceImpl) DoSomething(data string) (string, error) {
return fmt.Sprintf("已处理: %s", data), nil
}
步骤 3:创建配置
package myservice
import "github.com/hidevopsio/hiboot/pkg/app"
const (
// Profile 是此 starter 的配置文件名
Profile = "myservice"
)
// configuration 是自动配置结构体
type configuration struct {
app.Configuration
// Properties 将从 application.yml 填充
// 字段名应与 mapstructure 标签匹配
MyServiceProperties Properties `mapstructure:"myservice"`
}
func init() {
// 注册配置
app.Register(newConfiguration)
}
func newConfiguration() *configuration {
return &configuration{}
}
// Service 创建并返回 MyService 实例
// 此方法在 Hiboot 启动时调用
func (c *configuration) Service() Service {
return newService(
c.MyServiceProperties.Endpoint,
c.MyServiceProperties.Timeout,
c.MyServiceProperties.MaxRetry,
)
}
步骤 4:在 application.yml 中配置
app:
profiles:
include:
- myservice
myservice:
enabled: true
endpoint: "https://api.example.com"
timeout: 60
max_retry: 5
步骤 5:在应用中使用
package controller
import (
"github.com/hidevopsio/hiboot/pkg/app"
"github.com/hidevopsio/hiboot/pkg/at"
"github.com/hidevopsio/hiboot/pkg/model"
"myapp/starter/myservice"
)
type myController struct {
at.RestController
at.RequestMapping `value:"/api"`
service myservice.Service
}
func init() {
app.Register(newMyController)
}
func newMyController(service myservice.Service) *myController {
return &myController{
service: service,
}
}
func (c *myController) Post(request *DataRequest) (model.Response, error) {
result, err := c.service.DoSomething(request.Data)
response := new(model.BaseResponse)
response.SetData(result)
return response, err
}
高级配置
条件配置
使用结构体标签控制配置的加载时机:
type configuration struct {
app.Configuration `after:"databaseConfiguration"`
// 此配置在 databaseConfiguration 之后加载
}
可用标签:
| 标签 | 描述 |
|---|---|
after:"name" |
在指定配置之后加载 |
missing:"name" |
仅在指定配置不存在时加载 |
多个 Bean 方法
一个配置可以提供多个可注入的组件:
type configuration struct {
app.Configuration
Props Properties `mapstructure:"myservice"`
}
// 主服务
func (c *configuration) Service() Service {
return newService(c.Props)
}
// 外部 API 客户端
func (c *configuration) Client() *Client {
return newClient(c.Props.Endpoint)
}
// 带数据库连接的仓储
func (c *configuration) Repository(db *Database) Repository {
return newRepository(db)
}
在配置中注入依赖
配置方法可以有参数,这些参数会被注入:
// Repository 依赖于由其他 starter 提供的 Database
func (c *configuration) Repository(db *gorm.DB) Repository {
return &gormRepository{db: db}
}
// Service 依赖于 Repository
func (c *configuration) Service(repo Repository) Service {
return &serviceImpl{repository: repo}
}
最佳实践
- 使用接口:定义服务接口以提高可测试性
- 提供默认值:使用
default:""标签设置合理的默认值 - 文档化属性:清楚记录每个属性的作用
- 保持专注:每个 starter 应提供一个内聚的功能
- 优雅处理错误:在初始化期间返回有意义的错误