安装并调用
go get github.com/go-spring/go-spring/spring-boot
package main
import (
"github.com/sirupsen/logrus"
sb "github.com/go-spring/go-spring/spring-boot"
)
func main() {
logrus.Info("程序启动^_^")
sb.RunApplication("brun/")
}
上下文初始化
进入程序入口
// 传入配置路径
func RunApplication(configLocation ...string) {
app := newApplication(&defaultApplicationContext{
SpringContext: ctx,
}, configLocation...)
app.configReady = func() {
if ctx.Strict { // 设置是否使用严格模式,默认是严格模式
keys := []string{SPRING_STRICT, SpringStrict}
if strict := ctx.GetStringProperty(keys...); strict != "" {
ctx.Strict = strings.ToLower(strict) == "true"
}
}
}
BootStarter.Run(app)
}
1)创建一个application,传入appctx应用上下文及配置文件路径,如果路径为空默认找到(DefaultConfigLocation = "config/" // 默认的配置文件路径
)这个路径
func newApplication(appCtx ApplicationContext, cfgLocation ...string) *application {
if len(cfgLocation) == 0 { // 没有的话用默认的配置文件路径
cfgLocation = append(cfgLocation, DefaultConfigLocation)
}
return &application{
appCtx: appCtx,
cfgLocation: cfgLocation,
}
}
application的接口及struct如下
//
// AppRunner 应用执行器
//
type AppRunner interface {
Start() // 启动执行器
ShutDown() // 关闭执行器
}
// application SpringBoot 应用
type application struct {
appCtx ApplicationContext // 应用上下文
cfgLocation []string // 配置文件目录
configReady func() // 配置文件已就绪
}
2)这里对其中的另一个入参应用上下文appCtx ApplicationContext进行解释,其接口定义如下
// ApplicationContext Application 上下文
type ApplicationContext interface {
SpringCore.SpringContext
// SafeGoroutine 安全地启动一个 goroutine
SafeGoroutine(fn GoFunc)
// Wait 等待所有 goroutine 退出
Wait()
}
封装了一个SpringContext等,该SpringContext定义IoC容器接口,Bean 的注册规则:AutoWireBeans 开始后不允许注册新的 Bean(性能考虑)
type SpringContext interface {
// SpringContext 的工作过程分为三个阶段:
// 1) 加载 Properties 文件,
// 2) 注册 Bean 列表,
// 3) 自动绑定,又分为两个小阶段:
// 3.1) 解析 Bean,
// 3.2) 绑定 Bean。
// 属性值列表接口
Properties
// 上下文接口
context.Context
// GetProfile 返回运行环境
GetProfile() string
// SetProfile 设置运行环境
SetProfile(profile string)
// AllAccess 返回是否允许访问私有字段
AllAccess() bool
// SetAllAccess 设置是否允许访问私有字段
SetAllAccess(allAccess bool)
// SetEventNotify 设置 Context 事件通知函数
SetEventNotify(notify func(event ContextEvent))
// RegisterBean 注册单例 Bean,不指定名称,重复注册会 panic。
RegisterBean(bean interface{}) *BeanDefinition
// RegisterNameBean 注册单例 Bean,需指定名称,重复注册会 panic。
RegisterNameBean(name string, bean interface{}) *BeanDefinition
// RegisterBeanFn 注册单例构造函数 Bean,不指定名称,重复注册会 panic。
RegisterBeanFn(fn interface{}, tags ...string) *BeanDefinition
// RegisterNameBeanFn 注册单例构造函数 Bean,需指定名称,重复注册会 panic。
RegisterNameBeanFn(name string, fn interface{}, tags ...string) *BeanDefinition
// RegisterMethodBean 注册成员方法单例 Bean,不指定名称,重复注册会 panic。
// selector 可以是 *BeanDefinition,可以是 BeanId,还可以是 (Type)(nil) 变量。
// 必须给定方法名而不能通过遍历方法列表比较方法类型的方式获得函数名,因为不同方法的类型可能相同。
// 而且 interface 的方法类型不带 receiver 而成员方法的类型带有 receiver,两者类型不好匹配。
RegisterMethodBean(selector interface{}, method string, tags ...string) *BeanDefinition
// RegisterNameMethodBean 注册成员方法单例 Bean,需指定名称,重复注册会 panic。
// selector 可以是 *BeanDefinition,可以是 BeanId,还可以是 (Type)(nil) 变量。
// 必须给定方法名而不能通过遍历方法列表比较方法类型的方式获得函数名,因为不同方法的类型可能相同。
// 而且 interface 的方法类型不带 receiver 而成员方法的类型带有 receiver,两者类型不好匹配。
RegisterNameMethodBean(name string, selector interface{}, method string, tags ...string) *BeanDefinition
// @Incubate 注册成员方法单例 Bean,不指定名称,重复注册会 panic。
RegisterMethodBeanFn(method interface{}, tags ...string) *BeanDefinition
// @Incubate 注册成员方法单例 Bean,需指定名称,重复注册会 panic。
RegisterNameMethodBeanFn(name string, method interface{}, tags ...string) *BeanDefinition
// AutoWireBeans 完成自动绑定
AutoWireBeans(watchers ...WiringWatcher)
// WireBean 绑定外部的 Bean 源
WireBean(bean interface{}, watchers ...WiringWatcher)
// GetBean 根据类型获取单例 Bean,若多于 1 个则 panic;找到返回 true 否则返回 false。
// 什么情况下会多于 1 个?假设 StructA 实现了 InterfaceT,而且用户在注册时使用了
// StructA 的指针注册多个 Bean,如果在获取时使用 InterfaceT,则必然出现多于 1 个的情况。
GetBean(i interface{}, watchers ...WiringWatcher) bool
// GetBeanByName 根据名称和类型获取单例 Bean,若多于 1 个则 panic;找到返回 true 否则返回 false。
// 什么情况下会多于 1 个?假设 StructA 和 StructB 都实现了 InterfaceT,而且用户在注册时使用了相
// 同的名称分别注册了 StructA 和 StructB 的 Bean,这时候如果使用 InterfaceT 去获取,就会出现多于 1 个的情况。
GetBeanByName(beanId string, i interface{}, watchers ...WiringWatcher) bool
// FindBean 获取单例 Bean,若多于 1 个则 panic;找到返回 true 否则返回 false。
// selector 可以是 BeanId,还可以是 (Type)(nil) 变量,Type 为接口类型时带指针。
FindBean(selector interface{}) (*BeanDefinition, bool)
// FindBeanByName 根据名称和类型获取单例 Bean,若多于 1 个则 panic;找到返回 true 否则返回 false。
FindBeanByName(beanId string) (*BeanDefinition, bool)
// CollectBeans 收集数组或指针定义的所有符合条件的 Bean 对象,收集到返回 true,否则返回 false。
// 什么情况下可以使用此功能?假设 HandlerA 和 HandlerB 都实现了 HandlerT 接口,而且用户分别注册
// 了一个 HandlerA 和 HandlerB 对象,如果用户想要同时获取 HandlerA 和 HandlerB 对象,那么他可
// 以通过 []HandlerT 即数组的方式获取到所有 Bean。
CollectBeans(i interface{}, watchers ...WiringWatcher) bool
// GetBeanDefinitions 获取所有 Bean 的定义,一般仅供调试使用。
GetBeanDefinitions() []*BeanDefinition
// Close 关闭容器上下文,用于通知 Bean 销毁等。
Close()
// Run 立即执行一个一次性的任务
Run(fn interface{}, tags ...string) *Runner
// Config 注册一个配置函数
Config(fn interface{}, tags ...string) *Configer
// ConfigWithName 注册一个配置函数,name 的作用:区分,排重,排顺序。
ConfigWithName(name string, fn interface{}, tags ...string) *Configer
}
这里使用的defaultApplicationContext就是ApplicationContext接口、SpringContext接口、Properties接口等的实现,其结构体如下:
type defaultApplicationContext struct {
// 空白标识符导出
_ ApplicationContext `export:""`
// 匿名字段导出
SpringCore.SpringContext `export:""`
wg sync.WaitGroup
}
defaultApplicationContext实例中SpringCore.SpringContext的实例
&defaultApplicationContext{
SpringContext: ctx,
}
如下:
// ctx 全局的 SpringContext 变量
var ctx = SpringCore.NewDefaultSpringContext()
// NewDefaultSpringContext defaultSpringContext 的构造函数
func NewDefaultSpringContext() *defaultSpringContext {
ctx, cancel := context.WithCancel(context.Background())
return &defaultSpringContext{
Context: ctx,
Strict: true,
cancel: cancel,
Properties: NewDefaultProperties(),
methodBeans: make([]*BeanDefinition, 0),
beanMap: make(map[beanKey]*BeanDefinition),
configers: list.New(),
beanCacheByName: make(map[string][]*BeanDefinition),
}
}
type defaultSpringContext struct {
// 属性值列表接口
Properties
// 上下文接口
context.Context
cancel context.CancelFunc
profile string // 运行环境
autoWired bool // 已经开始自动绑定
allAccess bool // 允许注入私有字段
eventNotify func(event ContextEvent) // 事件通知函数
beanMap map[beanKey]*BeanDefinition // Bean 的集合
methodBeans []*BeanDefinition // 方法 Beans
configers *list.List // 配置方法集合
// Bean 的缓存,使用线程安全的 map 是考虑到运行时可能有
// 并发操作,另外 resolveBeans 的时候一步步地创建缓存。
beanCacheByType sync.Map
beanCacheByName map[string][]*BeanDefinition
Sort bool // 自动注入期间是否按照 BeanId 进行排序并依次进行注入
Strict bool // 严格模式,true 必须使用 Export() 导出接口
}
// 对配置文件的操作接口
type Properties interface {
// LoadProperties 加载属性配置文件,
// 支持 properties、yaml 和 toml 三种文件格式。
LoadProperties(filename string)
// ReadProperties 读取属性配置文件,
// 支持 properties、yaml 和 toml 三种文件格式。
ReadProperties(reader io.Reader, configType string)
// GetProperty 返回属性值,属性名称统一转成小写。
GetProperty(keys ...string) interface{}
// GetBoolProperty 返回布尔型属性值,属性名称统一转成小写。
GetBoolProperty(keys ...string) bool
// GetIntProperty 返回有符号整型属性值,属性名称统一转成小写。
GetIntProperty(keys ...string) int64
// GetUintProperty 返回无符号整型属性值,属性名称统一转成小写。
GetUintProperty(keys ...string) uint64
// GetFloatProperty 返回浮点型属性值,属性名称统一转成小写。
GetFloatProperty(keys ...string) float64
// GetStringProperty 返回字符串型属性值,属性名称统一转成小写。
GetStringProperty(keys ...string) string
// GetDurationProperty 返回 Duration 类型属性值,属性名称统一转成小写。
GetDurationProperty(keys ...string) time.Duration
// GetTimeProperty 返回 Time 类型的属性值,属性名称统一转成小写。
GetTimeProperty(keys ...string) time.Time
// GetDefaultProperty 返回属性值,如果没有找到则使用指定的默认值,属性名称统一转成小写。
GetDefaultProperty(key string, def interface{}) (interface{}, bool)
// SetProperty 设置属性值,属性名称统一转成小写。
SetProperty(key string, value interface{})
// GetPrefixProperties 返回指定前缀的属性值集合,属性名称统一转成小写。
GetPrefixProperties(prefix string) map[string]interface{}
// GetProperties 返回所有的属性值,属性名称统一转成小写。
GetProperties() map[string]interface{}
// BindProperty 根据类型获取属性值,属性名称统一转成小写。
BindProperty(key string, i interface{})
// BindPropertyIf 根据类型获取属性值,属性名称统一转成小写。
BindPropertyIf(key string, i interface{}, allAccess bool)
}
// BeanDefinition Bean 的详细定义
type BeanDefinition struct {
bean SpringBean // 源
name string // 名称
status beanStatus // 状态
file string // 注册点所在文件
line int // 注册点所在行数
cond *Conditional // 判断条件
primary bool // 主版本
dependsOn []interface{} // 非直接依赖
init *runnable // 初始化的回调
destroy *runnable // 销毁时的回调
exports map[reflect.Type]struct{} // 导出接口类型
autoExport bool // 自动导出接口
}
// SpringBean Bean 源接口
type SpringBean interface {
Bean() interface{} // 源
Type() reflect.Type // 类型
Value() reflect.Value // 值
TypeName() string // 原始类型的全限定名
beanClass() string // SpringBean 的实现类型
}
// beanStatus Bean 的状态值
type beanStatus int
const (
beanStatus_Default = beanStatus(0) // 默认状态
beanStatus_Resolving = beanStatus(1) // 正在决议
beanStatus_Resolved = beanStatus(2) // 已决议
beanStatus_Wiring = beanStatus(3) // 正在绑定
beanStatus_Wired = beanStatus(4) // 绑定完成
beanStatus_Deleted = beanStatus(5) // 已删除
)
可以看到使用了"context"包,这里对context包进行一下说明
Context接口如下:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Context接口的4个实现如下
Name | Deadline | Done | Err | Value | 继承 |
---|---|---|---|---|---|
empty | + | + | + | + | nil |
cancel | - | + | + | - | Context |
timer | + | - | - | - | canel |
value | - | - | - | + | Context |
Context常用函数
//创建一个Cancel context
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
//创建一个带有 deadline的Timer context
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
//创建一个带有超时的Timer context
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
//创建一个Value context
func WithValue(parent Context, key, val interface{}) Context
总的来说创建一个新的context就是在parant context中挂载一个 children context,也许传入的parent与新生成的ctx会挂载到同一个ctx下,也许会加入到parent contxt的children 队列中。empty context和 value context是不具备挂载children的能力的,而cancel context 和timer context 两种类型具备挂载chidren 的能力。
但问题来了,在创建cancel context时候需要传入一个parent 参数,那么这个parent从哪里来?这时候就需要 func Background() Context 这个函数,它返回一个作为起始点的context对象,而这个BackgroundCtx是一个empty context,这就是empty context的作用。
3)设置配置文件就绪函数属性值
app.configReady = func() {
if ctx.Strict { // 设置是否使用严格模式,默认是严格模式
keys := []string{SPRING_STRICT, SpringStrict}
if strict := ctx.GetStringProperty(keys...); strict != "" {
ctx.Strict = strings.ToLower(strict) == "true"
}
}
}
其中两个静态字符串定义如下:
SpringStrict = "spring.strict" // 严格模式,"true" 必须使用 Export() 导出接口
SPRING_STRICT = "SPRING_STRICT"
ctx.GetStringProperty方法如下,其只会返回成功的第一个参数key的value
// GetProperty 返回属性值,属性名称统一转成小写。
func (p *defaultProperties) GetProperty(keys ...string) interface{} {
for _, key := range keys {
if v, ok := p.properties[strings.ToLower(key)]; ok {
return v
}
}
return nil
}
该函数从ctx的属性中取spring.strict或SPRING_STRICT的值,来判断是否为true,并赋值给ctx.Strict
程序启动
BootStarter.Run(app)
func Run(runner AppRunner) {
exitChan = make(chan struct{})
// 响应控制台的 Ctrl+C 及 kill 命令。
go func() {
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
<-sig
SpringLogger.Info("got signal, program will exit")
Exit()
}()
runner.Start()
<-exitChan
runner.ShutDown()
}
这里创建了一个全局的var exitChan chan struct{}通道,起了一个协程监听os.Interrupt信号,打日志退出,下面为Exit()//关闭执行器的代码
// SafeCloseChan 安全地关闭一个管道
func SafeCloseChan(ch chan struct{}) {
select {
case <-ch:
// chan 已关闭,无需再次关闭。不做处理
default:
close(ch)
}
}
当通道关闭后,执行后续关闭操作。
这里我本地模拟了这个过程的操作
func main() {
fmt.Println("启动器开始")
ch := make(chan struct{}) // 创建ch
// 启动协程接收中断信号
go func() {
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
fmt.Println("启动协程等待关闭信号")
<-sig
// 关闭ch通道
fmt.Println("收到关闭信号,关闭启动器通道")
closeCh(ch)
}()
<-ch // 读取ch
fmt.Printf("启动器通道已经关闭,开始执行关闭程序")
}
func closeCh(ch chan struct{}) {
select {
case <-ch:
// chan 已关闭,无需再次关闭。
default:
close(ch)
}
}
日志如下:
启动器开始
启动协程等待关闭信号
^C收到关闭信号,关闭启动器通道
启动器通道已经关闭,开始执行关闭程序
调用的Start()具体实现方法如下:
// Start 启动 SpringBoot 应用
func (app *application) Start() {
// 准备上下文环境
app.prepare()
// 注册 ApplicationContext
app.appCtx.RegisterBean(app.appCtx)
// 依赖注入、属性绑定、Bean 初始化
app.appCtx.AutoWireBeans()
// 执行命令行启动器
var runners []CommandLineRunner
app.appCtx.CollectBeans(&runners)
for _, r := range runners {
r.Run(app.appCtx)
}
// 通知应用启动事件
var eventBeans []ApplicationEvent
app.appCtx.CollectBeans(&eventBeans)
for _, bean := range eventBeans {
bean.OnStartApplication(app.appCtx)
}
SpringLogger.Info("spring boot started")
}
准备上下文环境
// prepare 准备上下文环境
func (app *application) prepare() {
// 配置项加载顺序优先级,从高到低:
// 1.代码设置
// 2.命令行参数
// 3.系统环境变量
// 4.application-profile.properties
// 5.application.properties
// 6.内部默认配置
// 加载默认的应用配置文件,如 application.properties
appConfig := app.loadProfileConfig("")
// 加载系统环境变量
sysEnv := app.loadSystemEnv()
p := SpringCore.NewPriorityProperties(appConfig)
for key, value := range sysEnv.GetProperties() {
p.SetProperty(key, value)
}
// 加载命令行参数
cmdArgs := app.loadCmdArgs()
p = SpringCore.NewPriorityProperties(p)
for key, value := range cmdArgs.GetProperties() {
p.SetProperty(key, value)
}
// 加载特定环境的配置文件,如 application-test.properties
profile := app.appCtx.GetProfile()
if profile == "" {
keys := []string{SpringProfile, SPRING_PROFILE}
profile = p.GetStringProperty(keys...)
}
if profile != "" {
app.appCtx.SetProfile(profile)
profileConfig := app.loadProfileConfig(profile)
p.InsertBefore(profileConfig, appConfig)
}
// 拷贝用户使用代码设置的属性值
p = SpringCore.NewPriorityProperties(p)
for key, value := range app.appCtx.GetProperties() {
p.SetProperty(key, value)
}
// 将重组后的属性值写入 SpringContext 属性列表
for key, value := range p.GetProperties() {
app.appCtx.SetProperty(key, value)
}
// 设置是否允许注入私有字段
if ok := app.appCtx.AllAccess(); !ok {
keys := []string{SpringAccess, SPRING_ACCESS}
if access := app.appCtx.GetStringProperty(keys...); access != "" {
app.appCtx.SetAllAccess(strings.ToLower(access) == "all")
}
}
// 配置文件已就绪
if app.configReady != nil {
app.configReady()
}
}
从实现来看,主要是给application中的属性ApplicationContext设置一些属性,做一些准备工作。
可以看到代码中多次使用了这个函数
SpringCore.NewPriorityProperties(xxx)
它的返回值为priorityProperties类型
// NewPriorityProperties priorityProperties 的构造函数
func NewPriorityProperties(properties Properties) *priorityProperties {
return &priorityProperties{
curr: NewDefaultProperties(),
next: properties,
}
}
这个priorityProperties类型定义如下:
// priorityProperties 基于优先级的 Properties 版本
type priorityProperties struct {
curr Properties // 高优先级
next Properties // 低优先级
}
Properties在上面已经提到提供了一些列文件的操作方法。这个priorityProperties优先级属性类的意思是,有很多的Properties实例,把他们按照一定的顺序串在一起。高优先级在前面,低优先级在后面。使用代码p = SpringCore.NewPriorityProperties(p)
来排序。
这些方法
appConfig := app.loadProfileConfig("")// 加载默认的应用配置文件,如 application.properties
sysEnv := app.loadSystemEnv()// 加载系统环境变量
cmdArgs := app.loadCmdArgs()// 加载命令行参数
profile := app.appCtx.GetProfile()// 加载特定环境的配置文件,如 application-test.properties
// 拷贝用户使用代码设置的属性值
for key, value := range app.appCtx.GetProperties() {
p.SetProperty(key, value)
}
再将Properties实例链,重新歇写会application的ApplicationContext属性中
// 将重组后的属性值写入 SpringContext 属性列表
for key, value := range p.GetProperties() {
app.appCtx.SetProperty(key, value)
}
// GetProperties 返回所有的属性值,属性名称统一转成小写。
func (p *priorityProperties) GetProperties() map[string]interface{} {
properties := p.curr.GetProperties()
for key, val := range p.next.GetProperties() {
if _, ok := properties[key]; !ok {
properties[key] = val
}
}
return properties
}
// SetProperty 设置属性值,属性名称统一转成小写。
func (p *defaultProperties) SetProperty(key string, value interface{}) {
p.properties[strings.ToLower(key)] = value
}
最后面的属性值就会最后被赋值。
注册 ApplicationContext
// 注册 ApplicationContext
app.appCtx.RegisterBean(app.appCtx)
// RegisterBean 注册单例 Bean,不指定名称,重复注册会 panic。
func (ctx *defaultSpringContext) RegisterBean(bean interface{}) *BeanDefinition {
return ctx.RegisterNameBean("", bean)
}
// RegisterNameBean 注册单例 Bean,需要指定名称,重复注册会 panic。
func (ctx *defaultSpringContext) RegisterNameBean(name string, bean interface{}) *BeanDefinition {
bd := ToBeanDefinition(name, bean)
ctx.registerBeanDefinition(bd)
return bd
}
注册bean的时候,显示创建一个BeanDefinition,然后将它注册到ctx的beanMap中去。
我们先看创建BeanDefinition
// ToBeanDefinition 将 Bean 转换为 BeanDefinition 对象
func ToBeanDefinition(name string, i interface{}) *BeanDefinition {
return ValueToBeanDefinition(name, reflect.ValueOf(i))
}
// ValueToBeanDefinition 将 Value 转换为 BeanDefinition 对象
func ValueToBeanDefinition(name string, v reflect.Value) *BeanDefinition {
if !v.IsValid() || IsNil(v) {
panic(errors.New("bean can't be nil"))
}
bean := newObjectBean(v)
return newBeanDefinition(name, bean)
}
这里看到这里将传入的bean(I interface{})变成了一个reflect.ValueOf(i)向后传,这样通过reflect.ValueOf(i).Kind()可以用来判断类型,reflect.ValueOf(i).Type()可以用来获取类型,reflect.ValueOf(i)则存储了具体的值。向beanMap注册的时候可以完全保留住这些信息。
下面我测试了一个反射相关的例子:
func main() {
d:=make(map[string]interface{})
d["sdf"]="sdf"
a:=abc{
a: "sdf",
b: 1,
c: d,
}
b:=reflect.ValueOf(a)
fmt.Printf("值:%v\n",b)
fmt.Printf("kind:%v\n",b.Kind())
fmt.Printf("type:%v\n",b.Type())
fmt.Printf("pkgPath:%v\n",b.Type().PkgPath())
}
下面是它的输出:
值:{sdf 1 map[sdf:sdf]}
kind:struct
type:main.abc
pkgPath:main
Exiting.
可以看到,value、kind、type,其中的kind其实是一个类型,具体为uint类型
const (
valType = 1 // 值类型
refType = 2 // 引用类型
)
var kindType = []uint8{
0, // Invalid
valType, // Bool
valType, // Int
valType, // Int8
valType, // Int16
valType, // Int32
valType, // Int64
valType, // Uint
valType, // Uint8
valType, // Uint16
valType, // Uint32
valType, // Uint64
0, // Uintptr
valType, // Float32
valType, // Float64
valType, // Complex64
valType, // Complex128
valType, // Array
refType, // Chan
refType, // Func
refType, // Interface
refType, // Map
refType, // Ptr
refType, // Slice
valType, // String
valType, // Struct
0, // UnsafePointer
}
顺便复习一下uint表示整数,下面是各实现下的整数取值范围:
uint8(0 -> 255)
uint16(0 -> 65,535)
uint32(0 -> 4,294,967,295)
uint64(0 -> 18,446,744,073,709,551,615)
接下来我们看这段代码bean := newObjectBean(v)
// objectBean 原始 Bean 源
type objectBean struct {
rType reflect.Type // 类型
rValue reflect.Value // 值
typeName string // 原始类型的全限定名
}
// newObjectBean objectBean 的构造函数
func newObjectBean(v reflect.Value) *objectBean {
if t := v.Type(); !IsRefType(t.Kind()) {
panic(errors.New("bean must be ref type"))
} else {
return &objectBean{
rType: t,
typeName: TypeName(t), // 类型的全限定名
rValue: v,
}
}
}
// TypeName 返回原始类型的全限定名,golang 允许不同的路径下存在相同的包,故此有全限定名的需求。
// 形如 "github.com/go-spring/go-spring/spring-core/SpringCore.DefaultSpringContext"
func TypeName(t reflect.Type) string {
if t == nil {
panic(errors.New("type shouldn't be nil"))
}
// Map 的全限定名太复杂,不予处理,而且 Map 作为注入对象要三思而后行!
for {
if k := t.Kind(); k != reflect.Ptr && k != reflect.Slice {
break
} else {
t = t.Elem()
}
}
if pkgPath := t.PkgPath(); pkgPath != "" { // 内置类型的路径为空
return pkgPath + "/" + t.String()
} else {
return t.String()
}
}
因为go允许不同路径下存在相同的包,所以通过全路径名(pkgPath := t.PkgPath();pkgPath + "/" + t.String())进行区分。然后调用newBeanDefinition(name, bean)
// newBeanDefinition BeanDefinition 的构造函数
func newBeanDefinition(name string, bean SpringBean) *BeanDefinition {
var (
file string
line int
)
// 获取注册点信息
for i := 2; i < 10; i++ {
_, file0, line0, _ := runtime.Caller(i)
// 排除 spring-core 包下面所有的非 test 文件
if strings.Contains(file0, "/spring-core/") {
if !strings.HasSuffix(file0, "_test.go") {
continue
}
}
// 排除 spring-boot 包下面的 spring-boot-singlet.go 文件
if strings.Contains(file0, "/spring-boot/") {
if strings.HasSuffix(file0, "spring-boot-singlet.go") {
continue
}
}
file = file0
line = line0
break
}
if _, ok := bean.(*fakeMethodBean); !ok {
if name == "" { // 生成默认名称
name = bean.Type().String()
}
}
return &BeanDefinition{
bean: bean,
name: name,
status: beanStatus_Default,
file: file,
line: line,
cond: NewConditional(),
exports: make(map[reflect.Type]struct{}),
autoExport: true,
}
}
可以看到,BeanDefinition的属性name默认设置为objectBean的类型,bean为objectBean,file、line所在文件与所在行号等属性,为了获取文件和行号,这里使用了runtime.Caller(),这个一般在日志包中经常能看到用来获取文件和行号来定位报错位置。下面做一个实验来解释一下:
func floor0() {
for i := 0; i <= 5; i++ {
_, file, line, _ := runtime.Caller(i)
fmt.Printf("第%v层,file:%v,line:%v\n",i,file,line)
}
}
func floor1() {
floor0()
}
func floor2() {
floor1()
}
func floor3() {
floor2()
}
func floor4() {
floor3()
}
func main() {
floor4()
输出:
第0层,file:/Users/zhangmingzheng/workspace_personal/infra_cbb/brun/main.go,line:9
第1层,file:/Users/zhangmingzheng/workspace_personal/infra_cbb/brun/main.go,line:14
第2层,file:/Users/zhangmingzheng/workspace_personal/infra_cbb/brun/main.go,line:18
第3层,file:/Users/zhangmingzheng/workspace_personal/infra_cbb/brun/main.go,line:22
第4层,file:/Users/zhangmingzheng/workspace_personal/infra_cbb/brun/main.go,line:26
第5层,file:/Users/zhangmingzheng/workspace_personal/infra_cbb/brun/main.go,line:30
其中的skip参数,指的是相对当前函数从下往上(从高地址到低地址)的第几个堆栈帧,这里对堆栈帧做一下解释:
堆栈
可以说堆栈是一块连续的内存,在系统中运行的例程将它组织成“层状”结构。堆栈中的内存单元在函数的生命周期内可以使用,而当函数返回时,这些内存单元就会被释放(释放后其他函数就可以用了)。
接下来的几小节将展示堆栈是怎样组织的,并讲述各种决定堆栈的基本布局的调用约定。
堆栈帧
堆栈帧指的是在堆栈中为当前正在运行的函数分配的区域(或空间)。传入的参数、返回地址(当这个函数结束后必须跳转到该返回地址。译注:即主调函数的断点处)以及函数所用的内部存储单元(即函数存储在堆栈上的局部变量)都在堆栈帧中。
image.png
堆栈可以说是一个一个堆栈帧组成的,堆栈帧从栈底层到栈顶,分别是为了恢复上级函数保存的变量,当前函数的xxx、param3、param2、param1、返回值、EBP、ESP【设置堆栈帧的目的是:通过将一个指针存放在堆栈中参数区域和局部变量区域之间的那个单元,使得函数可以简便而快捷地访问这些参数和局部变量。这个指针通常存放在一个辅助寄存器中(通常是EBP),而腾出来ESP(ESP是主堆栈指针)来记录当前堆栈位置(即堆栈顶)】
回到源码
for i := 2; i < 10; i++ {
_, file0, line0, _ := runtime.Caller(i)
...
}
这里的skip传了2~10进行搜索,直到找到一个file,line为止,相对第二堆栈帧也就是ToBeanDefinition这个函数开始再一直往上找,里面排出框架包、_test结尾文件等
BeanDefinition已经定义好了,接下来,就是向上下文中写入注册Bean了ctx.registerBeanDefinition(bd)
// registerBeanDefinition 注册 BeanDefinition,重复注册会 panic。
func (ctx *defaultSpringContext) registerBeanDefinition(d *BeanDefinition) {
ctx.checkRegistration()
k := beanKey{d.name, d.Type()}
if _, ok := ctx.beanMap[k]; ok {
panic(fmt.Errorf("duplicate registration, bean: \"%s\"", d.BeanId()))
}
ctx.beanMap[k] = d
}
首先先检测一下,是否允许注入ctx.checkRegistration()
,通过上下文中的ctx.autoWired变量来判断
// checkRegistration 检查注册是否已被冻结
func (ctx *defaultSpringContext) checkRegistration() {
if ctx.autoWired {
panic(errors.New("bean registration have been frozen"))
}
}
若允许的话,将keyk := beanKey{d.name, d.Type()}
,valued *BeanDefinition
写入上下文中的beanMapctx.beanMap[k] = d
,最后整个注册函数返回一个 *BeanDefinition就完成了app.ctx的注册。
依赖注入、属性绑定、Bean 初始化
app.appCtx.AutoWireBeans()
// AutoWireBeans 完成自动绑定
func (ctx *defaultSpringContext) AutoWireBeans(watchers ...WiringWatcher) {
// 不再接受 Bean 注册,因为性能的原因使用了缓存,并且在 AutoWireBeans 的过程中
// 逐步建立起这个缓存,而随着缓存的建立,绑定的速度会越来越快,从而减少性能的损失。
if ctx.autoWired {
panic(errors.New("ctx.AutoWireBeans() already called"))
}
// 注册所有的 Method Bean
ctx.registerMethodBeans()
ctx.autoWired = true
if ctx.eventNotify != nil {
ctx.eventNotify(ContextEvent_ResolveStart)
}
// 对 config 函数进行决议
for e := ctx.configers.Front(); e != nil; {
next := e.Next()
configer := e.Value.(*Configer)
if ok := configer.Matches(ctx); !ok {
ctx.configers.Remove(e)
}
e = next
}
// 对 config 函数进行排序
ctx.configers = sortConfigers(ctx.configers)
// 首先决议 Bean 是否能够注册,否则会删除其注册信息
for _, bd := range ctx.beanMap {
ctx.resolveBean(bd)
}
if ctx.eventNotify != nil {
ctx.eventNotify(ContextEvent_ResolveEnd)
}
w := newDefaultBeanAssembly(ctx, watchers)
if ctx.eventNotify != nil {
ctx.eventNotify(ContextEvent_AutoWireStart)
}
defer func() { // 捕获自动注入过程中的异常,打印错误日志然后重新抛出
if err := recover(); err != nil {
SpringLogger.Errorf("%v ↩\n%s", err, w.wiringStack.path())
panic(err)
}
}()
// 执行配置函数,过程中会自动完成部分注入
for e := ctx.configers.Front(); e != nil; e = e.Next() {
configer := e.Value.(*Configer)
if err := configer.run(ctx); err != nil {
panic(err)
}
}
if ctx.Sort { // 自动注入期间是否排序注入
beanKeyMap := map[string]beanKey{}
for key, val := range ctx.beanMap {
beanKeyMap[val.BeanId()] = key
}
beanIds := make([]string, 0)
for s := range beanKeyMap {
beanIds = append(beanIds, s)
}
sort.Strings(beanIds)
for _, beanId := range beanIds {
key := beanKeyMap[beanId]
bd := ctx.beanMap[key]
w.wireBeanDefinition(bd, false)
}
} else {
for _, bd := range ctx.beanMap {
w.wireBeanDefinition(bd, false)
}
}
if ctx.eventNotify != nil {
ctx.eventNotify(ContextEvent_AutoWireEnd)
}
}
这里通过一个上下文的属性进行控制ctx.autoWired,注册完所有的bean后,设置为true
if ctx.autoWired {
panic(errors.New("ctx.AutoWireBeans() already called"))
}
// 注册所有的 Method Bean
...
ctx.autoWired = true
进入ctx.registerMethodBeans()
// registerMethodBeans 注册方法 Bean
func (ctx *defaultSpringContext) registerMethodBeans() {
var (
selector string
filter func(*BeanDefinition) bool
)
for _, bd := range ctx.methodBeans {
bean := bd.bean.(*fakeMethodBean)
result := make([]*BeanDefinition, 0)
switch e := bean.selector.(type) {
case *BeanDefinition:
selector = e.BeanId()
result = append(result, e)
case string:
selector = e
typeName, beanName, _ := ParseBeanId(e)
filter = func(b *BeanDefinition) bool {
return b.Match(typeName, beanName)
}
case reflect.Type:
selector = e.String()
filter = func(b *BeanDefinition) bool {
return b.Type() == e
}
default:
t := reflect.TypeOf(e)
// 如果是接口类型需要解除外层指针
if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Interface {
t = t.Elem()
}
selector = t.String()
filter = func(b *BeanDefinition) bool {
return b.Type() == t
}
}
if filter != nil {
for _, b := range ctx.beanMap {
if filter(b) {
result = append(result, b)
}
}
}
if l := len(result); l == 0 {
panic(fmt.Errorf("can't find parent bean: \"%s\"", selector))
} else if l > 1 {
panic(fmt.Errorf("found %d parent bean: \"%s\"", l, selector))
}
bd.bean = newMethodBean(result[0], bean.method, bean.tags...)
if bd.name == "" { // 使用默认名称
bd.name = bd.bean.Type().String()
}
ctx.registerBeanDefinition(bd)
}
}
附录
swagger使用
代码:
func init() {
SpringBoot.RegisterBean(new(AppController)).Init(func(c *AppController) {
r := SpringBoot.Route("/app")
{
lib.Doc(r.GET("/list", c.List)).WithID("我是id").
WithSummary("我是summary").
WithDescription("我是desc").
WithExternalDocs("我是扩展doc","http://www.baidu.com").
AddParam(spec.QueryParam("hahaId").Typed("string","").WithDescription("要输入hahaId")).
AddParam(spec.QueryParam("huhuId").Typed("string","").WithDescription("要输入huhuId")).
Deprecate().
RemoveParam("huhuId","query").
//Undeprecate()
BindParam(lib.H{},"我是个bind H").
WithSchemes("我是个schemes")
}
})
}
效果:
image.png
网友评论