Glacier 是一款 Go 语言的,支持依赖注入的,模块化的应用开发框架,它以 go-ioc 依赖注入容器核心,为 Go 应用开发解决了依赖传递和模块化的问题。
创建一个新的项目,使用下面的命令安装 Glacier 开发框架
go get github.com/mylxsw/glacier
为了简化应用的创建过程,我们一般可以通过 starter 模板来创建应用
import "github.com/mylxsw/glacier/starter/app" ... // 方法一:快捷启动应用 app.MustStart("1.0", 3, func(app *app.App) error { // 这里完成应用的初始化 // ... return nil }) // 方法二: 分步骤启动应用 ins := app.Create("1.0", 3) // 应用初始化 // ... app.MustRun(ins)
示例:
app.MustStart("1.0", 3, func(ins *app.App) error { ins.AddStringFlag("listen", ":8080", "http listen address") ins.Provider(web.Provider( listener.FlagContext("listen"), web.SetRouteHandlerOption(func(cc infra.Resolver, router web.Router, mw web.RequestMiddleware) { router.Get("/", func(ctx web.Context) web.Response { return ctx.JSON(web.M{}) }) }), )) return nil })
Glacier 框架充分利用了 go-ioc 提供的依赖注入能力,为应用提供了功能强大的依赖注入特性。
在使用依赖注入特性时,首先要理解以下两个接口的作用
infra.Binder
该接口用于对象创建实例方法的绑定,简单说就是向 go-ioc
容器注册对象的创建方法infra.Resolver
该接口用于对象的实例化,获取对象实例无论是 Binder
还是 Resolver
,都会有一个 interface{}
类型的参数,它的类型为符合一定规则的函数,后面在 Binder
和 Resolver
部分将会详细说明。
infra.Binder
是一个对象定义接口,用于将实例的创建方法绑定到依赖注入容器,提供了以下常用方法
Prototype(initialize interface{}) error
原型绑定,每次访问绑定的实例都会基于 initialize
函数重新创建新的实例Singleton(initialize interface{}) error
单例绑定,每次访问绑定的实例都是同一个,只会在第一次访问的时候创建初始实例BindValue(key string, value interface{}) error
将一个具体的值绑定到 key
Prototype
和 Singleton
方法参数 initialize interface{}
支持以下两种形式
形式1:func(依赖参数列表...) (绑定类型定义, error)
// 这里使用单例方法定义了数据库连接对象的创建方法 binder.Singleton(func(conf *Config) (*sql.DB, error) { return sql.Open("mysql", conf.MySQLURI) }) binder.Singleton(func(c infra.FlagContext) *Config { ... return &Config{ Listen: c.String("listen"), MySQLURI: c.String("mysql_uri"), APIToken: c.String("api_token"), ... } })
形式2:func(注入参数列表...) 绑定类型定义
binder.Singleton(func() UserRepo { return &userRepoImpl{} }) binder.Singleton(func(db *sql.DB) UserRepo { // 这里我们创建的 userRepoImpl 对象,依赖 sql.DB 对象,只需要在函数 // 参数中,将依赖列举出来,容器会自动完成这些对象的创建 return &userRepoImpl{db: db} })
infra.Resolver
是对象实例化接口,通过依赖注入的方式获取实例,提供了以下常用方法
Resolve(callback interface{}) error
执行 callback 函数,自动为 callback 函数提供所需参数Call(callback interface{}) ([]interface{}, error)
执行 callback 函数,自动为 callback 函数提供所需参数,支持返回值,返回参数为 Call
的第一个数组参数AutoWire(object interface{}) error
自动对结构体对象进行依赖注入,object 必须是结构体对象的指针。自动注入字段(公开和私有均支持)需要添加 autowire
tag,支持以下两种
Get(key interface{}) (interface{}, error)
直接通过 key 来查找对应的对象实例// Resolve resolver.Resolve(func(db *sql.DB) {...}) err := resolver.Resolve(func(db *sql.DB) error {...}) // Call resolver.Call(func(userRepo UserRepo) {...}) // Call 带有返回值 // 这里的 err 是依赖注入过程中的错误,比如依赖对象创建失败 // results 是一个类型为 []interface{} 的数组,数组中按次序包含了 callback 函数的返回值,以下面的代码为例,其中 // results[0] - string // results[1] - error results, err := resolver.Call(func(userRepo UserRepo) (string, error) {...}) // 由于每个返回值都是 interface{} 类型,因此在使用时需要执行类型断言,将其转换为具体的类型再使用 returnValue := results[0].(string) returnErr := results[1].(error) // AutoWire // 假设我们有一个 UserRepo,创建该结构体时需要数据库的连接实例 type UserRepo struct { db *sql.DB `autowire:"@"` } userRepo := UserRepo{} resolver.AutoWire(&userRepo) // 现在 userRepo 中的 db 参数已经自动被设置为了数据库连接对象,可以继续执行后续的操作了
在 Glacier 应用开发框架中,Provider 是应用模块化的核心,每个独立的功能模块通过 Provider 完成实例初始化,每个 Provider 都需要实现 infra.Provider
接口。 在每个功能模块中,我们通常会创建一个名为 provider.go 的文件,在该文件中创建一个 provider 实现
type Provider struct{} func (Provider) Register(binder infra.Binder) { ... // 这里可以使用 binder 向 IOC 容器注册当前模块中的实例创建方法 }
Provider 接口只有一个必须实现的方法 Register(binder infra.Binder)
,该方法用于注册当前模块的对象到 IOC 容器中,实现依赖注入的支持。
例如,我们实现一个基于数据库的用户管理模块 repo
,该模块包含两个方法
package repo type UserRepo struct { db *sql.DB } func (repo *UserRepo) Login(username, password string) (*User, error) {...} func (repo *UserRepo) GetUser(username string) (*User, error) {...}
为了使该模块能够正常工作,我们需要在创建 UserRepo
时,提供 db
参数,在 Glacier 中,我们可以这样实现
package repo type Provider struct {} func (Provider) Register(binder infra.Binder) { binder.Singleton(func(db *sql.DB) *UserRepo { return &UserRepo {db: db} }) }
在我们的应用创建时,使用 ins.Provider
方法注册该模块
ins := app.Default("1.0") ... ins.MustSingleton(func() (*sql.DB, error) { return sql.Open("mysql", "user:pwd@tcp(ip:3306)/dbname") }) // 在这里加载模块的 Provider ins.Provider(repo.Provider{}) ... app.MustRun(ins)
在我们使用 Provider 时,默认只需要实现一个接口方法 Register(binder infra.Binder)
即可,该方法用于将模块的实例创建方法注册到 Glacier 框架的 IOC 容器中。
在 Glaicer 中,还提供了一个 ProviderBoot
接口,该接口包含一个 Boot(resolver Resolver)
方法,实现该方法的模块,可以在 Glacier 框架启动过程中执行一些模块自有的业务逻辑,该方法在所有的模块全部加载完毕后执行(所有的模块的 Register
方法都已经执行完毕),因此,系统中所有的对象都是可用的。
Boot(resolver Resolver)
方法中适合执行一些在应用启动过程中所必须完成的一次性任务,任务应该尽快完成,以避免影响应用的启动。
type Provider struct{} func (Provider) Register(binder infra.Binder) { binder.MustSingleton(func(conf *configs.Config) *grpc.Server { return ... }) } func (Provider) Boot(resolver infra.Resolver) { resolver.MustResolve(func(serv *grpc.Server) { protocol.RegisterMessageServer(serv, NewEventService()) protocol.RegisterHeartbeatServer(serv, NewHeartbeatService()) }) }
模块 Provider 的 Boot
方法是阻塞执行的,通常用于执行一些在应用启动时需要执行的一些初始化任务,在一个应用中,所有的 Provider 的 Boot
方法是串行执行的。
而 DaemonProvider
接口则为模块提供了异步执行的能力,模块的 Daemon(ctx context.Context, resolver infra.Resolver)
方法是异步执行的,我们可以在这里执行创建 web 服务器等操作。
func (Provider) Daemon(_ context.Context, app infra.Resolver) { app.MustResolve(func( serv *grpc.Server, conf *configs.Config, gf graceful.Graceful, ) { listener, err := net.Listen("tcp", conf.GRPCListen) ... gf.AddShutdownHandler(serv.GracefulStop) ... if err := serv.Serve(listener); err != nil { log.Errorf("GRPC Server has been stopped: %v", err) } }) }
ProviderAggregate 接口为应用提供了一种能够聚合其它模块 Provider 的能力,在 Aggregate() []Provider
方法中,我们可以定义多个我们当前模块所依赖的其它模块,在 Glacier 框架启动过程中,会优先加载这里定义的依赖模块,然后再加载我们的当前模块。
我们可以通过 ProviderAggregate
来创建我们自己的模块, Aggregates() []infra.Provider
方法中返回依赖的子模块,框架会先初始化子模块,然后再初始化当前模块。
// 创建自定义模块,初始化了 Glacier 框架内置的 Web 框架 type Provider struct{} func (Provider) Aggregates() []infra.Provider { return []infra.Provider{ // 加载了 web 模块,为应用提供 web 开发支持 web.Provider( listener.FlagContext("listen"), // 从命令行参数 listen 获取监听端口 web.SetRouteHandlerOption(s.routes), // 设置路由规则 web.SetExceptionHandlerOption(func(ctx web.Context, err interface{}) web.Response { log.Errorf("error: %v, call stack: %s", err, debug.Stack()) return nil }), // Web 异常处理 ), } } func (Provider) routes(cc infra.Resolver, router web.Router, mw web.RequestMiddleware) { router.Controllers( "/api", // 这里添加控制器 controller.NewWelcomeController(cc), controller.NewUserController(cc), ) } func (Provider) Register(app infra.Binder) {}
在 Glacier 框架中,Service 代表了一个后台模块,Service 会在框架生命周期中持续运行。要实现一个 Service,需要实现 infra.Service
接口,该接口只包含一个方法
Start() error
用于启动 Service除了 Start
方法之外,还支持以下控制方法,不过它们都是可选的
Init(resolver Resolver) error
用于 Service 的初始化,注入依赖等Stop()
触发 Service 的停止运行Reload()
触发 Service 的重新加载以下为一个示例
type DemoService struct { resolver infra.Resolver stopped chan interface{} } // Init 可选方法,用于在 Service 启动之前初始化一些参数 func (s *DemoService) Init(resolver infra.Resolver) error { s.resolver = resolver s.stopped = make(chan interface{}) return nil } // Start 用于 Service 的启动 func (s *DemoService) Start() error { for { select { case <-s.stopped: return nil default: ... // 业务代码 } } } // Stop 和 Reload 都是可选方法 func (s *DemoService) Stop() { s.stopped <- struct{}{} } func (s *DemoService) Reload() { ... }
在我们的应用创建时,使用 app.Service
方法注册 Service
ins := app.Create("1.0") ... ins.Service(&service.DemoService{}) ... app.MustRun(ins)
Provider 和 Service 支持按需加载,要使用此功能,只需要让 Provider 和 Service 实现 ShouldLoad(...) bool 方法。ShouldLoad
方法用于控制 Provider 和 Service 是否加载,支持以下几种形式
func (Provider) ShouldLoad(...依赖) bool
func (Provider) ShouldLoad(...依赖) (bool, error)
示例
type Provider struct{} func (Provider) Register(binder infra.Binder) {...} // 只有当 config.AuthType == ldap 的时候才会加载当前 Provider func (Provider) ShouldLoad(config *config.Config) bool { return str.InIgnoreCase(config.AuthType, []string{"ldap"}) }
注意:
ShouldLoad
方法在执行时,Provider
并没有完成Register
方法的执行,因此,在ShouldLoad
方法的参数列表中,只能使用在应用创建时全局注入的对象实例。ins := app.Create("1.0") ... ins.Singleton(func(c infra.FlagContext) *config.Config { return ... }) ... app.MustRun(ins)
实现 infra.Priority
接口的 **Provider **、 **Service **,会按照 Priority()
方法的返回值大小依次加载,值越大,加载顺序越靠后,默认的优先级为 1000
。
type Provider struct {} func (Provider) Register(binder infra.Binder) {...} func (Provider) Priority() int { return 10 }
Glacier 是一个应用框架,为了方便 Web 开发,也内置了一个灵活的 Web 应用开发框架。
Glaicer Web 在 Glacier 框架中是一个内置的 DaemonProvider,与其它的模块并无不同。我们通过 web.Provider(builder infra.ListenerBuilder, options ...Option) infra.DaemonProvider
方法创建 Web 模块。
参数 builder
用于创建 Web 服务的 listener(用于告知 Web 框架如何监听端口),在 Glaicer 中,有以下几种方式来创建 listener:
listener.Default(listenAddr string) infra.ListenerBuilder
该构建器使用固定的 listenAddr 来创建 listenerlistener.FlagContext(flagName string) infra.ListenerBuilder
该构建器根据命令行选项 flagName 来获取要监听的地址,以此来创建 listenerlistener.Exist(listener net.Listener) infra.ListenerBuilder
该构建器使用应存在的 listener 来创建参数 options
用于配置 web 服务的行为,包含以下几种常用的配置
web.SetRouteHandlerOption(h RouteHandler) Option
设置路由注册函数,在该函数中注册 API 路由规则web.SetExceptionHandlerOption(h ExceptionHandler) Option
设置请求异常处理器web.SetIgnoreLastSlashOption(ignore bool) Option
设置路由规则忽略最后的 /
,默认是不忽略的web.SetMuxRouteHandlerOption(h MuxRouteHandler) Option
设置底层的 gorilla Mux 对象,用于对底层的 Gorilla 框架进行直接控制web.SetHttpWriteTimeoutOption(t time.Duration) Option
设置 HTTP 写超时时间web.SetHttpReadTimeoutOption(t time.Duration) Option
设置 HTTP 读超时时间web.SetHttpIdleTimeoutOption(t time.Duration) Option
设置 HTTP 空闲超时时间web.SetMultipartFormMaxMemoryOption(max int64)
设置表单解析能够使用的最大内存web.SetTempFileOption(tempDir, tempFilePattern string) Option
设置临时文件存储规则web.SetInitHandlerOption(h InitHandler) Option
初始化阶段,web 应用对象还没有创建,在这里可以更新 web 配置web.SetListenerHandlerOption(h ListenerHandler) Option
服务初始化阶段,web 服务对象已经创建,此时不能再更新 web 配置了最简单的使用 Web 模块的方式是直接创建 Provider,
// Password 该结构体时 /complex 接口的返回值定义 type Password struct { Password string `json:"password"` } // Glacier 框架初始化 ins := app.Default("1.0") ... // 添加命令行参数 listen,指定默认监听端口 :8080 ins.AddStringFlag("listen", ":8080", "http listen address") ... ins.Provider(web.Provider( // 使用命令行 flag 的 listener builder listener.FlagContext("listen"), // 设置路由规则 web.SetRouteHandlerOption(func(resolver infra.Resolver, r web.Router, mw web.RequestMiddleware) { ... r.Get("/simple", func(ctx web.Context, gen *password.Generator) web.Response { ... return ctx.JSON(web.M{"password": pass}) }) r.Get("/complex", func(ctx web.Context, gen *password.Generator) Password {...}) }), )) app.MustRun(ins)
更好的方式是使用模块化,编写一个独立的 Provider
type Provider struct{} // Aggregates 实现 infra.ProviderAggregate 接口 func (Provider) Aggregates() []infra.Provider { return []infra.Provider{ web.Provider( confListenerBuilder{}, web.SetRouteHandlerOption(routes), web.SetMuxRouteHandlerOption(muxRoutes), web.SetExceptionHandlerOption(exceptionHandler), ), } } // Register 实现 infra.Provider 接口 func (Provider) Register(binder infra.Binder) {} // exceptionHandler 异常处理器 func exceptionHandler(ctx web.Context, err interface{}) web.Response { return ctx.JSONWithCode(web.M{"error": fmt.Sprintf("%v", err)}, http.StatusInternalServerError) } // routes 注册路由规则 func routes(resolver infra.Resolver, router web.Router, mw web.RequestMiddleware) { mws := make([]web.HandlerDecorator, 0) // 添加 web 中间件 mws = append(mws, mw.AccessLog(log.Module("api")), mw.CORS("*"), ) // 注册控制器,所有的控制器 API 都以 `/api` 作为接口前缀 router.WithMiddleware(mws...).Controllers( "/api", controller.NewServerController(resolver), controller.NewClientController(resolver), ) } func muxRoutes(resolver infra.Resolver, router *mux.Router) { resolver.MustResolve(func() { // 添加 prometheus metrics 支持 router.PathPrefix("/metrics").Handler(promhttp.Handler()) // 添加健康检查接口支持 router.PathPrefix("/health").Handler(HealthCheck{}) }) } // 创建自定义的 listener 构建器,从配置对象中读取 listen 地址 type confListenerBuilder struct{} func (l confListenerBuilder) Build(resolver infra.Resolver) (net.Listener, error) { return listener.Default(resolver.MustGet((*config.Server)(nil)).(*config.Server).HTTPListen).Build(resolver) }
控制器必须实现 web.Controller
接口,该接口只有一个方法
Register(router Router)
用于注册当前控制器的路由规则type UserController struct {...} // NewUserController 控制器创建方法,返回 web.Controller 接口 func NewUserController() web.Controller { return &UserController{...} } // Register 注册当前控制器关联的路由规则 func (ctl UserController) Register(router web.Router) { router.Group("/users/", func(router web.Router) { router.Get("/", u.Users).Name("users:all") router.Post("/", u.Add) router.Post("/{id}/", u.Update) router.Get("/{id}/", u.User).Name("users:one") router.Delete("/{id}/", u.Delete).Name("users:delete") }) router.Group("/users-helper/", func(router web.Router) { router.Get("/names/", u.UserNames) }) } // 读取 JSON 请求参数,直接返回实例,会以 json 的形式返回给客户端 func (ctl UserController) Add(ctx web.Context, userRepo repository.UserRepo) (*repository.User, error) { var userForm *UserForm if err := ctx.Unmarshal(&userForm); err != nil { return nil, web.WrapJSONError(fmt.Errorf("invalid request: %v", err), http.StatusUnprocessableEntity) } ctx.Validate(userForm, true) ... return ... } // 直接返回错误,如果 error 不为空,则返回错误给客户端 func (ctl UserController) Delete(ctx web.Context, userRepo repository.UserRepo) error { userID := ctx.PathVar("id") ... return userRepo.DeleteID(userID) } // 返回 web.Response,可以使用多种格式返回,如 ctx.Nil, ctx.API, ctx.JSON, ctx.JSONWithCode, ctx.JSONError, ctx.YAML, ctx.Raw, ctx.HTML, ctx.HTMLWithCode, ctx.Error 等 func (u UserController) Users(ctx web.Context, userRepo repository.UserRepo, roleRepo repository.RoleRepo) web.Response { page := ctx.IntInput("page", 1) perPage := ctx.IntInput("per_page", 10) ... return ctx.JSON(web.M{ "users": users, "next": next, "search": web.M{ "name": name, "phone": phone, "email": email, }, }) }
使用 web.Router
实例的 Controllers
方法注册控制器。
// routes 注册路由规则 func routes(resolver infra.Resolver, router web.Router, mw web.RequestMiddleware) { mws := make([]web.HandlerDecorator, 0) // 添加 web 中间件 mws = append(mws, mw.AccessLog(log.Module("api")), mw.CORS("*"), ) // 注册控制器,所有的控制器 API 都以 `/api` 作为接口前缀 router.WithMiddleware(mws...).Controllers( "/api", controller.NewUserController(), ) }
Glacier 框架提供了一个简单的事件管理模块,可以用于发布和监听应用运行中的事件,进行响应的业务处理。
通过 event.Provider(handler func(resolver infra.Resolver, listener Listener), options ...Option) infra.Provider
来初始化事件管理器。
ins.Provider(event.Provider( func(cc infra.Resolver, listener event.Listener) { listener.Listen(func(event CronEvent) { log.Debug("a new cron task executed") // 执行监听到定时任务执行事件后要触发的操作 }) }, // 设置事件管理器选项 event.SetStoreOption(func(cc infra.Resolver) event.Store { // 设置使用默认的内存事件存储 return event.NewMemoryEventStore(true, 100) }), ))
发布事件时,使用 Glacier 框架的依赖注入能力,获取 event.Publisher
接口实现
ins.Async(func(publisher event.Publisher) { for i := 0; i < 10; i++ { publisher.Publish(CronEvent{GoroutineID: uint64(i)}) } })
Glacier 内置了基于内存的事件存储后端,说有事件的监听器都是同步执行的。
// 设置事件管理器选项 event.SetStoreOption(func(cc infra.Resolver) event.Store { // 设置使用默认的内存事件存储 return event.NewMemoryEventStore(true, 100) })
使用内存作为事件存储后端时,当应用异常退出的时候,可能会存在事件的丢失,你可以使用这个基于 Redis 的事件存储后端 redis-event-store 来获得事件的持久化支持。
Glacier 提供了内置的定时任务支持,使用 scheduler.Provider
来实现。
type Provider struct{} func (Provider) Register(binder infra.Binder) {...} func (Provider) Aggregates() []infra.Provider { return []infra.Provider{ // 加载 scheduler 定时任务模块 scheduler.Provider( func(resolver infra.Resolver, creator scheduler.JobCreator) { // 添加一个名为 test-job 的任务,每隔 10s 执行一次 _ = cr.Add("test-job", "@every 10s", TestJob) // 添加一个名称为 test-timeout-job 的任务,每隔 5s 执行一次 // 通过 AddAndRunOnServerReady 添加的任务会在服务启动时先执行一次 _ = creator.AddAndRunOnServerReady( "test-timeout-job", "@every 5s", // 使用 scheduler.WithoutOverlap 包装的函数,当前一次调度还没有执行完毕,本次调度的时间已到,本次调度将会被取消 scheduler.WithoutOverlap(TestTimeoutJob).SkipCallback(func() { ... // 当前一个任务还没有执行完毕时,当前任务会被跳过,跳过时会触发该函数的执行 }), ) }, ), } }
scheduler.Provider
支持分布式锁,通过 SetLockManagerOption
选项可以指定分布式锁的实现,以满足任务在一组服务器中只会被触发一次的逻辑。
scheduler.Provider( func(resolver infra.Resolver, creator scheduler.JobCreator) {...}, // 设置分布式锁 scheduler.SetLockManagerOption(func(resolver infra.Resolver) scheduler.LockManagerBuilder { // get redis instance redisClient := resolver.MustGet(&redis.Client{}).(*redis.Client) return func(name string) scheduler.LockManager { // create redis lock return redisLock.New(redisClient, name, 10*time.Minute) } }), )
注意:Glacier 框架没有内置分布式锁的实现,在 mylxsw/distribute-locks 实现了一个简单的基于 Redis 的分布式锁实现,可以参考使用。
在 Glacier 中,默认使用 asteria 作为日志框架,asteria 是一款功能强大、灵活的结构化日志框架,支持多种日志输出格式以及输出方式,支持为日志信息添加上下文信息。
最简单的方式是通过 log.SetDefaultLogger(logger infra.Logger)
方法为 Glacier 框架设置默认的日志处理器,
// import "github.com/mylxsw/glacier/log" // 默认设置,使用 asteria 日志框架 // import asteria "github.com/mylxsw/asteria/log" log.SetDefaultLogger(asteria.Module("glacier")) // 使用标准库中的日志包,Glacier 对标准库日志包进行了简单封装 log.SetDefaultLogger(log.StdLogger())
当然,如果使用了 starter 模板项目创建的应用,也可以使用 WithLogger(logger infra.Logger)
方法来设置日志处理器。
ins := app.Default("1.0") ... // 设置使用标准库日志包,不输出 DEBUG 日志 ins.WithLogger(log.StdLogger(log.DEBUG)) ...
除了默认的 asteria
日志库以及 Glacier 自带的 StdLogger
之外,还可以使用其它第三方的日志包,只需要简单的封装,实现 infra.Logger
接口即可。
type Logger interface { Debug(v ...interface{}) Debugf(format string, v ...interface{}) Info(v ...interface{}) Infof(format string, v ...interface{}) Error(v ...interface{}) Errorf(format string, v ...interface{}) Warning(v ...interface{}) Warningf(format string, v ...interface{}) // Critical 关键性错误,遇到该日志输出时,应用直接退出 Critical(v ...interface{}) // Criticalf 关键性错误,遇到该日志输出时,应用直接退出 Criticalf(format string, v ...interface{}) }
Eloquent ORM 是为 Go 开发的一款数据库 ORM 框架,它的设计灵感来源于著名的 PHP 开发框架 Laravel,支持 MySQL 等数据库。
项目地址为 mylxsw/eloquent,可以配合 Glacier 框架使用。
Glacier 支持平滑退出,当我们按下键盘的 Ctrl+C
时(接收到 SIGINT, SIGTERM, Interrupt 等信号), Glacier 将会接收到关闭的信号,然后触发应用的关闭行为。默认情况下,我们的应用会立即退出,我们可以通过 starter 模板创建的应用上启用平滑支持选项 WithShutdownTimeoutFlagSupport(timeout time.Duration)
来设置默认的平滑退出时间
ins := app.Create("1.0") ins.WithShutdownTimeoutFlagSupport(5 * time.Second) ... // Provider 中获取 `gf.Graceful` 实例,注册关闭时的处理函数 resolver.MustResolve(func(gf graceful.Graceful) { gf.AddShutdownHandler(func() { ... }) })
S3 (Simple Storage Service) Bucket Bucket全局唯一,一个account默认可创建100个bucket,一般用DNS名字命名 AWS region 创建Bucket的时候需要选择region,region在物理和逻辑上完全独立,除非用户手动从一个region复制数据到另一个region 可以选择离端点用户地理位置近的region创建bucket,也可以选择离主
Glaciers are slowly moving masses of ice that have accumulated on land in areas where more snowfalls during a year than melts. Snow falls as hexagonal crystals, but once on the ground, snow is soon tr
12.3 依赖注入 JSR-352主要基于Spring Batch的编程模型。因此,尽管没有明确需要正式的依赖注入实现, 但还是推荐使用 DI 的方式。Spring Batch支持 JSR-352 中定义的三种加载组件方式: 实现专用加载器 - Spring Batch 构建于 Spring 之上, 支持在 JSR-352 批处理作业中使用依赖注入. 归档文件加载器 - JSR-352 定义了一个
在不讨论原因的情况下,假设有人想要一个老式的Play Framework web服务,并且不想使用依赖注入,也不想依赖Google的Guice。在玩法2.8.x中还可能吗? 有没有一个简单的方法可以让你不去这里就呆在老学校里? 我承认,但不完全理解https://www.playframework.com/documentation/2.4.x/migration24。我认为我的问题与2.7中删除
主要内容:inject 实践,inject 原理分析在介绍 inject 之前我们先来简单介绍一下“依赖注入”和“控制反转”这两个概念。 正常情况下,对函数或方法的调用是我们的主动直接行为,在调用某个函数之前我们需要清楚地知道被调函数的名称是什么,参数有哪些类型等等。 所谓的控制反转就是将这种主动行为变成间接的行为,我们不用直接调用函数或对象,而是借助框架代码进行间接的调用和初始化,这种行为称作“控制反转”,库和框架能很好的解释控制反转的概念。 依
在React中,想做依赖注入(Dependency Injection)其实相当简单。请看下面这个例子: // Title.jsx export default function Title(props) { return <h1>{ props.title }</h1>; } // Header.jsx import Title from './Title.jsx'; export defa
依赖注入 Dependency Injection is a strong mechanism, which helps us easily manage dependencies of our classes. It is very popular pattern in strongly typed languages like C# and Java. 依赖注入是一个很强大的机制,该机制可以帮
简介 Hyperf 默认采用 hyperf/di 作为框架的依赖注入管理容器,尽管从设计上我们允许您更换其它的依赖注入管理容器,但我们强烈不建议您更换该组件。 hyperf/di 是一个强大的用于管理类的依赖关系并完成自动注入的组件,与传统依赖注入容器的区别在于更符合长生命周期的应用使用、提供了 注解及注解注入 的支持、提供了无比强大的 AOP 面向切面编程 能力,这些能力及易用性作为 Hyper
出自维基百科 Wikipedia: 依赖注入是一种允许我们从硬编码的依赖中解耦出来,从而在运行时或者编译时能够修改的软件设计模式。 这句解释让依赖注入的概念听起来比它实际要复杂很多。依赖注入通过构造注入,函数调用或者属性的设置来提供组件的依赖关系。就是这么简单。