Go语言(Golang)1.16+版本支持。
Go语言从1.16版本(2021年2月16日发布)起,在语言层面原生支持静态资源嵌入,实现了某些动态语言的特性(动态变更执行代码和数据I/O)。
这为软件的版本发布和运维部署提供了很大便利,这是一个很重要的特性(因为我们再也不用搞一个可执行程序的同时附加一大堆静态资源文件了,现在只需要一个程序就可以开一个性能强大的又有优秀的UI界面的服务,比如consul)。以前我们都是借助市面上的一些第三方工具实现的(之前绝大多数第三方工具的做法是把外部的静态资源翻译成用Go代码读取成字节串然后使用),现在Go语言自身具备这样的特性(通过embed标准库),这是一个非常好的消息。
本仓库即为这些代码的使用做了一个简单易懂的示例。
查看main.go就能知道,本程序启动的时候,使用了一个最简单的动态控制加载资源的方式useOs := len(os.Args) > 1 && os.Args[1] == "live"
- 保持
static/index.html
文件内容中的第9行的h2文本为embed mode
- 编译输出一个可执行文件并执行它,不带任何参数,此时会加载编译时已经内置进去的static目录中的内容
- 在浏览器中访问 http://localhost:9100 即可访问index.html,此时你会在页面上看到
embed mode
- 修改
index.html
,将embed mode
改为live mode
,重新运行编译好的程序,带上live参数,此时你会发现网页内容已经变更为live mode
了 - 再重新运行可执行程序,不带任何参数,你会在页面上得到
embed mode
。
结论:静态资源在编译好的Go程序中有一份,在static目录中也有一份,我可以通过命令行参数动态的控制到底加载和读取哪一个。
其实也很简单:
- 将index.html内容改为
embed mode
,编译完之后再改回live mode
,此时,embed mode
的index.html已经在可执行程序中了 - 再执行可执行程序时:
- 有
live
命令行参数,加载磁盘文件,于是看到live mode
- 没有
live
命令行参数,加载内嵌到程序中的文件,于是看到embed mode
- 有
- 这是Go 1.16中的一个很重要的功能,基于此,我觉得以下场景会十分有用:
- 编译时自动打进去一个version.txt,集成产品版本,以前的时候是使用
-ldflags "-X 'package_name/global.AppVersion=0.0.1'"
的方式,现在这种,更优雅了 - 在一些特定场所,程序需要输出自己的源码(好像是Quine),那么就更方便了
- 集成一个LICENSE到软件中,此时的LICENSE是固化在软件实体中的,它能够实时地跟着软件版本走,对于一些商业化和版权相关的领域,很有用
- 先将一段代码编码输出到外部资源中,然后再在程序中动态加载和解码这段代码并执行它,简单说:A程序将一段代码编码导出到swap.gob,B程序编译时将swap.gob集成进来然后在程序运行时执行swap.gob中的代码。这使得Go语言多多少少有了点动态脚本语言的特性,它能够实现B程序不必做出修改就改变程序代码的行为(扩展插件思想?),所以,这是一个令人兴奋的特性。
- 集成一个文件的内容到一个go程序变量,成为这个变量的值,这对于要实现开发期动态发布期静态的需求,实现很方便了
- 直觉是这个
embed
特性应该还有其他用途,以后想到了再补充
- 编译时自动打进去一个version.txt,集成产品版本,以前的时候是使用
总得来说,我觉得这一次这个embed特性是十分有意义的,代码可以动态交换(它控制软件流程和逻辑),文件也可以动态交换(它为软件提供数据),所以,代码逻辑可以动态替换,数据也可以动态替换的Go语言,未来必将更加大放异彩。
main.go
文件中的第11行//go:embed static
看着是个注释,但是不可缺少,Go编译器是依据它来决定是否要将static目录编译进可执行的二进制程序中的。- 据我个人理解,这种将静态资源集成到可执行的二进制程序中的做法,有以下特点:
- 对于一些不太想公开的资源,在一定程度上可以做到加密(虽然想想办法也能解开,但是增加了解开的门槛)
- 正常情况下,一个外部的静态资源要加载进来,操作虽然烦琐,但是可以封装成函数,到处使用,也不算太麻烦,所以它更多的考虑是性能问题,毕竟,封装的函数每次执行时都会触发一次磁盘I/O
- 所以,静态资源编译进可执行程序,可以大幅提升这些资源加载和使用的效率,因为程序运行时就直接加载到内存中了(在进程的虚拟地址空间的低位的只读代码和数据区段)
Linux平台上,进程的虚拟地址空间从低位到高位依次是:只读的代码和数据(从可执行文件中加载进来的)、读写数据(也是从可执行文件中加载进来的)、运行时堆空间、共享库的内存映射区域、栈、内核虚拟内存区域,再往高位上去,用户进程就不可见了。32位的Windows平台和Linux不相同,但略有相似,64位的Windows差别非常大,分了好几个范围,每个范围又有自己固定的用途和独特的管理运作机制。
- 静态资源编译进二进制程序中,对于程序发布有利,但是也要考虑清楚使用场景,避免反复修改编译,否则就得不偿失了