通常我们只需要一行代码即可启动 Go-Spring 应用,即
gs.Run()
这个看似简单的 gs.Run() 背后做了哪些事情呢?本文咱们跟随代码一点点深入分析,探究 Go-Spring 启动与停止的全过程。
gs.Run()
首先来看 gs.Run() 这个入口函数的代码实现。
type AppStarter struct{}// Web enables or disables the built-in web server.func Web(enable bool) *AppStarter {EnableSimpleHttpServer(enable)
return &AppStarter{}}// Run runs the app and waits for an interrupt signal to exit.func (s *AppStarter) Run() {
var err error
defer func() {
if err != nil {syslog.Errorf("app run failed: %s", err.Error())}}()
printBanner()
if err = B.(*gs_app.BootImpl).Run(); err != nil {return}B = nilerr = gs_app.GS.Run()}// Run runs the app and waits for an interrupt signal to exit.func Run() {new(AppStarter).Run()}
从这段代码可以看出, gs.Run() 里面做了下面四件事:
- 打印 Banner 图案,Banner 图案在现代编程框架里面非常流行,Go-Spring 也不能落后。
- 运行
bootstrap阶段,即引导阶段,通常在微服务环境下用于拉取远程配置、为 application 阶段做准备等。 - 运行
application阶段,即正式阶段,程序提供的所有核心能力都应该在这个阶段注册和执行。 - 程序结束处理,如果程序结束的过程中出现了错误,那么框架会打印一条错误日志,便于排查问题。通常来说,这就够了。
打印 Banner 图案和程序结束处理没什么好说的,咱们重点看 bootstrap 和 application 两个关键阶段。
bootstrap 阶段
首先是 bootstrap 阶段,它的 Run() 方法代码如下。
// Run executes the application's boot process.func (b *BootImpl) Run() error {
if !b.flag {
return nil}b.c.Object(b)var p conf.Properties// Refresh the boot configuration.
{
var err error
if p, err = b.p.Refresh(); err != nil {
return err}}// Refresh the container.
if err := b.c.Refresh(p); err != nil {
return err}// Execute all registered runners.
for _, r := range b.Runners {
if err := r.Run(); err != nil {
return err}}b.c.Close()return nil}
从代码中可以看出,bootstrap 阶段主要包含以下四个动作:
- 为 bootstrap 阶段加载配置文件、环境变量等,
- 刷新 ioc 容器,执行属性绑定、bean 创建、依赖注入等,
- 执行 ioc 容器中的
Runner对象,执行远程配置文件拉取、为 application 阶段做准备等, - 关闭 ioc 容器,销毁 ioc 容器中的资源。
这里解释一下,bootstrap 阶段和 application 阶段使用各自的 ioc 容器,因为通常来说 bootstrap 阶段执行完成之后就没有用了,可以及时销毁和回收资源,使用各自的 ioc 容器可以保证互相不受影响。翻回头看 gs.Run() 的代码,可以看到 B 也被回收了,那么至少在 go-spring 看来,bootstrap 阶段执行完就完了,就不要继续占用内存了。
application阶段
接下来的 application 阶段是 Go-Spring 程序最最核心的阶段,它的 Run() 代码如下,
// Run starts the application and listens for termination signals// (e.g., SIGINT, SIGTERM). Upon receiving a signal, it initiates// a graceful shutdown.func (app *App) Run() error {app.C.Object(app)if err := app.Start(); err != nil {
return err}// listens for OS termination signals
go func() {ch := make(chan os.Signal, 1)signal.Notify(ch, os.Interrupt, syscall.SIGTERM)sig := <-chsyslog.Infof("Received signal: %v", sig)app.ShutDown()}()// waits for the shutdown signal<-app.ctx.Done()app.Stop()return nil}
从代码中可以看出, application 阶段主要分为三步:
- 启动应用
- 监听退出信号
- 停止应用
1. 启动应用
代码如下:
// Start initializes and starts the application. It performs configuration// loading, IoC container refreshing, dependency injection, and runs// runners, jobs and servers.func (app *App) Start() error {
var p conf.Properties// loads the layered app properties
{
var err error
if p, err = app.P.Refresh(); err != nil {
return err}}// refreshes the container
if err := app.C.Refresh(p); err != nil {
return err}// runs all runners
for _, r := range app.Runners {
if err := r.Run(); err != nil {
return err}}// runs all jobs
if app.EnableJobs {
for _, job := range app.Jobs {goutil.GoFunc(func() {
defer func() {
if r := recover(); r != nil {
app.ShutDown()
panic(r)}}()
if err := job.Run(app.ctx); err != nil {
syslog.Errorf("job run error: %s", err.Error())app.ShutDown()
}})
}}// starts all serversif app.EnableServers {sig := NewReadySignal()for _, svr := range app.Servers {sig.Add()app.wg.Add(1)goutil.GoFunc(func() {defer app.wg.Done()defer func() {if r := recover(); r != nil {sig.Intercept()app.ShutDown()panic(r)}}()err := svr.ListenAndServe(sig)if err != nil && !errors.Is(err, http.ErrServerClosed) {syslog.Errorf("server serve error: %s", err.Error())sig.Intercept()app.ShutDown()}})}sig.Wait()if sig.Intercepted() {return nil}syslog.Infof("ready to serve requests")sig.Close()}return nil}
通过代码可以看到,application 启动过程中主要做了下面几件事:
- 加载配置文件、环境变量等,为 ioc 容器准备配置项,
- 刷新 ioc 容器,执行属性绑定、bean 创建、依赖注入等,
- 执行 ioc 容器中的
Runner对象,用于依赖校验、初始化数据等, - 启动 ioc 容器中的
Job对象,用来启动随应用绑定的后台任务, - 启动 ioc 容器中的
Server对象,用来启动 http、grpc、thrift 等 server, - 等待所有 server 都已准备好的信号,这一步用来同步应用对外提供服务的状态。
监听退出信号
采用的是 go 常规方案,就不介绍了。
停止应用
代码如下:
// Stop gracefully shuts down the application, ensuring all servers and// resources are properly closed.func (app *App) Stop() {ctx, cancel := context.WithTimeout(context.Background(), app.ShutDownTimeout)
defer cancel()waitChan := make(chan struct{})goutil.GoFunc(func() {
for _, svr := range app.Servers {goutil.GoFunc(func() {
if err := svr.Shutdown(ctx); err != nil {syslog.Errorf("shutdown server failed: %s", err.Error())}})}app.wg.Wait()app.C.Close()waitChan <- struct{}{}})select {case <-waitChan:syslog.Infof("shutdown complete")case <-ctx.Done():syslog.Infof("shutdown timeout")}}
通过代码可以看到,application 停止过程中主要做了以下两件事:
- 设置并等待一个超时,如果停止过程超过了超时时间,即使资源没有释放也会退出。
- 通知所有的 server 进行优雅停机,然后等待所有的 server 退出。
这里没有等待 Job 退出,是因为通常 Job 的逻辑是一个死循环,而且在程序退出的时候 Job 内部很可能在 sleep,这种情况下除了等待 sleep 结束,没有其他办法可以提前退出。但这并不是说 Job 就不能优雅退出了,Go-Spring 为 Job 优雅退出提供了可取消的 context.Context 对象,以及全局退出标志,用起来也相当方便。
总结
Go-Spring 应用在运行时分为两个主要阶段:
bootstrap阶段:用于加载远程配置、初始化基础环境等预处理工作,执行完毕后资源及时销毁。application阶段:核心应用逻辑阶段,涵盖配置加载、IoC 初始化、任务调度、服务启动与监听等。
两个阶段的通用步骤包括:
- 配置加载
- 容器刷新
- Runner 执行
- 容器关闭
而 application 阶段还包括:
- Job 启动与运行
- Server 启动、监听与关闭
对大多数开发者而言,只需关注 application 阶段即可;只有在微服务环境下譬如需要远程配置、动态初始化等功能时,才需要深入理解 bootstrap 阶段。
点击查看 Go-Spring 应用执行全流程图:

----------------------------------------------------
欢迎关注 Go-Spring 项目!
GitHub 地址:https://github.com/go-spring/spring-core
欢迎扫码关注微信公众号 GoSpring实战,获取更多实战分享。

欢迎扫码加入 Go-Spring 讨论群,与开发者们共同探讨技术,交流经验。

您的支持对 Go-Spring 非常重要!欢迎点赞、在看、分享,助力 Go-Spring 的成长与发展!
posted on
浙公网安备 33010602011771号