自动配置

简介

Hiboot 自动配置根据你导入的包自动配置应用程序。当你导入一个 starter 包时,Hiboot 会检测到它并配置必要的组件以供依赖注入使用。

这种方式借鉴了 Spring Boot 的自动配置机制,并适配了 Go 的包系统。

自动配置原理

  1. 在应用程序中导入 starter 包
  2. starter 的 init() 函数注册其配置
  3. Hiboot 在启动时扫描已注册的配置
  4. 调用配置方法创建可注入的组件
  5. 组件可用于依赖注入

内置 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}
}

最佳实践

  1. 使用接口:定义服务接口以提高可测试性
  2. 提供默认值:使用 default:"" 标签设置合理的默认值
  3. 文档化属性:清楚记录每个属性的作用
  4. 保持专注:每个 starter 应提供一个内聚的功能
  5. 优雅处理错误:在初始化期间返回有意义的错误

下一步