通常我们只需要一行代码即可启动 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 = nil
  err = 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 := <-ch
syslog.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 servers
  if 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 2025-06-24 15:50  lvan100  阅读(119)  评论(0)    收藏  举报