Go语言可以通过go/build包里定义的tags和命名约定来让Go的包可以运行不同的代码。
在源代码里添加标注,通常称之为编译标签(build tag)。编译标签采用靠近源代码文件顶部用注释的方式添加。go build在构建一个包的时候会读取这个包里的每个源文件并且分析编译便签,这些标签决定了这个源文件是否参与本次编译。
注:一个源文件可以有多个编译标签,多个编译标签之间是逻辑“与”的关系,一个编译标签可以包括由空格分割的多个标签,这些标签是逻辑“或”的关系。例子:
// +build linux darwin
// +build 386
这个将限制此源文件只能在 linux/386或者darwin/386平台下编译
下边以json包的适配为例,介绍标签编译:
Golang提供的标准json解析库——encoding/json,在开发高性能、高并发的网络服务时会产生性能问题。替代的方案是使用高性能的json解析库,比如json-iterator和easyjson。在正式引用高性能的json解析库(以json-iterator为例)通常的做法是小范围的进行测试,此时就会出现两个库并存的时候,解决方案是使用标签编译选择运行的解析库
现在我们需要两个库并存,所以我们先得统一这两个库的用法(参考适配器模式),这里我们使用一个自定义的json
包来适配encoding/json
和json-iterator
json/json.go
// +build !jsoniter
//必须留一行空格,否则会报重定义的错误
package json
import (
"encoding/json"
"fmt"
)
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
fmt.Println("Use [encoding/json] package")
return json.MarshalIndent(v,prefix,indent)
}
json/jsoniter.go
// +build jsoniter
package json
import (
"fmt"
"github.com/json-iterator/go"
)
var (
json = jsoniter.ConfigCompatibleWithStandardLibrary
)
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
fmt.Println("Use [jsoniter] package")
return json.MarshalIndent(v,prefix,indent)
}
目录结构如下:
json
├── json.go
└── jsoniter.go
注: 上述例子中如果编译标签 和包的声明 之间没有空行隔开,编译标签会被当做包声明的注释而不是编译标签,切记空行必须要有。
main函数
此处引用的json包下的两个go文件中都有MarshalIndent函数定义,并且签名一致,但它们又是使用不同的json解析库实现,此处相当于做了统一的适配包装,所以调用可以统一。
package main
import (
"fmt"
"pracitice/json_test/json"
)
type user struct {
Name string
Age int
}
func main() {
u := user{"xiyan", 7}
b, err := json.MarshalIndent(u, "", "")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(b))
}
}
直接运行go run main.go结果:
Use [encoding/json] package
{
"Name": "xiyan",
"Age": 7
}
使用标签编译运行go run -tags=jsoniter main.go
Use [jsoniter] package
{"Name":"xiyan","Age":7}
标签编译的关键在于 -tags=jsoniter
, -tags
这个标志,是Go语言为我们提供的条件编译方式之一。如果我们不是运行,而是编译构建的话,改为go build -tags=jsoniter
即可生成调用了对应包的可执行文件。
// +build !jsoniter
表示,tags不是jsoniter的时候编译这个Go文件。
// +build jsoniter
表示,tags是jsoniter的时候编译这个Go文件。
这两行是Go语言条件编译的关键。+build可以理解标签编译tags的声明关键字,后面跟着tags的条件。
使用这种方案比编译标签要简单,go/build可以在不读取源文件的情况下就可以决定哪些文件不需要参加编译。文件命名约定可以在go/build包里找到详细的说明,简单来说如果你的源文件包含后缀:_$GOOS.go,那么这个源文件只会在这个平台下编译, _$GOARCH.go也是如此。 这两个后缀可以结合在一起使用,顺序只能为:_$GOOS_$GOARCH.go
例子如下:
mypkg_freebsd_arm.go // only builds on freebsd/arm systems
mypkg_plan9.go // only builds on plan9
源文件不能只提供条件编译后缀,还必须有文件名,_linux.go、_freebsd_386.go
这两个源文件在所有的平台下都会被忽略掉,因为go/build将会忽略所有以下划线或者点开头的源文件 。