Go能实现AOP吗?
hello~大家好,我是小楼,今天分享的话题是Go是否能实现AOP?
背景
写Java的同学来写Go就特别喜欢将两者进行对比,就经常看到技术群里讨论,比如Go能不能实现Java那样的AOP啊?Go写个事务好麻烦啊,有没有Spring那样的@Transactional注解啊?
遇到这样的问题我通常会回复:没有、实现不了、再见。
直到看了《Go语言底层原理剖析》这本书,开始了一轮认真地探索。
Java是如何实现AOP的
AOP概念第一次是在若干年前学Java时看的一本书《Spring实战》中看到的,它指的是一种面向切面编程的思想。注意它只是一种思想,具体怎么实现,你看着办。
AOP能在你代码的前后织入代码,这就能做很多有意思的事情了,比如统一的日志打印、监控埋点,事务的开关,缓存等等。
可以分享一个我当年学习AOP时的笔记片段:
在Java中的实现方式可以是JDK动态代理
和字节码增强技术
。
JDK动态代理是在运行时动态地生成了一个代理类,JVM通过加载这个代理类再实例化来实现AOP的能力。
字节码增强技术可以多唠叨两句,当年学Java时第一章就说Java的特点是「一次编译,到处运行」。
但当我们真正在工作中这个特性用处大吗?好像并不大,生产中都使用了同一种服务器,只编译了一次,也都只在这个系统运行。做到一次编译,到处运行的技术底座是JVM,JVM可以加载字节码并运行,这个字节码是平台无关的一种二进制中间码。
似乎这个设定带来了一些其他的好处。在JVM加载字节码时,字节码有一次被修改的机会,但这个字节码的修改比较复杂,好在有现成的库可用,如ASM、Javassist等。
至于像ASM这样的库是如何修改字节码的,我还真就去问了Alibaba Dragonwell的一位朋友,他回答ASM是基于Java字节码规范所做的「硬改」,但做了一些抽象,总体来说还是比较枯燥的。
由于这不是本文重点,所以只是提一下,如果想更详细地了解可自行网上搜索。
Go能否实现AOP?
之前用「扁鹊三连」的方式回复Go不能实现AOP的基础其实就是我对Java实现AOP的思考,因为Go没有虚拟机一说,也没有中间码,直接源码编译为可执行文件,可执行文件基本没法修改,所以做不了。
但真就如此吗?我搜索了一番。
运行时拦截
还真就在Github找到了一个能实现类似AOP功能的库gohook
(当然也有类似的其他库):
看这个项目的介绍:
运行时动态地hook Go的方法,也就是可以在方法前插入一些逻辑。它是怎么做到的?
通过反射找到方法的地址(指针),然后插入一段代码,执行完后再执行原方法。听起来很牛X,但它下面有个Notes:
使用有一些限制,更重要的是没有完全测试,不建议生产使用。这种不可靠的方式也就不尝试了。
AST修改源码
这种方式就是我在看《Go语言底层原理剖析》第一章看到的,其实我之前的文章也有写过关于AST的,《Cobar源码分析之AST》。
AST即抽象语法树,可以认为所有的高级编程语言都可以抽象为一种语法树,即对代码进行结构化的抽象,这种抽象可以让我们更加简单地分析甚至操作源码。
Go在编译时大概分为词法与语法分析、类型检查、通用 SSA 生成和最后的机器代码生成这几个阶段。
其中词法与语法分析之后,生成一个AST树,在Go中我们能调用Go提供的API很轻易地生成AST:
fset := token.NewFileSet()
// 这里file就是一个AST对象
file, err := parser.ParseFile(fset, "aop.go", nil, parser.ParseComments)
比如这里我的aop.go文件是这样的:
package main
import "fmt"
func main() {
fmt.Println(execute("roshi"))
}
func execute(name string) string {
return name
}
想看生成的AST长什么样,可调用下面的方法:
ast.Print(fset, file)
由于篇幅太长,我截个图感受下即可:
当然也有一些开源的可视化工具,但我觉得大可不必,想看的话Debug看下file
的结构。
至于Go AST结构的介绍,也不是本文的重点,而且AST中的类型很多很多,我建议如果你想看的话直接Debug来看,对照源码比较清晰。
我们这里就实现一个简单的,在execute方法执行之前添加一条打印before
的语句,接上述代码:
const before = "fmt.Println(\"before\")"
...
exprInsert, err := parser.ParseExpr(before)
if err != nil {
panic(err)
}
decls := make([]ast.Decl, 0, len(file.Decls))
for _, decl := range file.Decls {
fd, ok := decl.(*ast.FuncDecl)
if ok {
if fd.Name.Name == "execute" {
stats := make([]ast.Stmt, 0, len(fd.Body.List)+1)
stats = append(stats, &ast.ExprStmt{
X: exprInsert,
})
stats = append(stats, fd.Body.List...)
fd.Body.List = stats
decls = append(decls, fd)
continue
} else {
decls = append(decls, decl)
}
} else {
decls = append(decls, decl)
}
}
file.Decls = decls
这里AST就被我们修改了,虽然我们是写死了针对execute方法,但总归是迈出了第一步。
再把AST转换为源码输出,Go也提供了API:
var cfg printer.Config
var buf bytes.Buffer
cfg.Fprint(&buf, fset, file)
fmt.Printf(buf.String())
输出效果如下:
看到这里,我猜你应该有和我相同的想法,这玩意是不是可以用来格式化代码?
没错,Go自带的格式化代码工具gofmt的原理就是如此。
当我们写完代码时,可以执行gofmt对代码进行格式化:
gofmt test.go
这相比于其他语言方便很多,终于有个官方的代码格式了,甚至你可以在IDEA中安装一个file watchers插件,监听文件变更,当文件有变化时自动执行 gofmt 来格式化代码。
看到这里你可能觉得太简单了,我查了下资料,AST中还能拿到注释,这就厉害了,我们可以把注释当注解来玩,比如我加了 // before:
的注释,自动把这个注释后的代码添加到方法之前去。
// before:fmt.Println("before...")
func executeComment(name string) string {
return name
}
修改AST代码如下,为了篇幅,省略了打印代码:
cmap := ast.NewCommentMap(fset, file, file.Comments)
for _, decl := range file.Decls {
fd, ok := decl.(*ast.FuncDecl)
if ok {
if cs, ok := cmap[fd]; ok {
for _, cg := range cs {
for _, c := range cg.List {
if strings.HasPrefix(c.Text, "// before:") {
txt := strings.TrimPrefix(c.Text, "// before:")
ei, err := parser.ParseExpr(txt)
if err == nil {
stats := make([]ast.Stmt, 0, len(fd.Body.List)+1)
stats = append(stats, &ast.ExprStmt{
X: ei,
})
stats = append(stats, fd.Body.List...)
fd.Body.List = stats
decls = append(decls, fd)
continue
}
}
}
}
} else {
decls = append(decls, decl)
}
} else {
decls = append(decls, decl)
}
}
file.Decls = decls
跑一下看看:
虽然又是硬编码,但这不重要,又不是不能用~
但你发现,这样实现AOP有个缺点,必须在编译期对代码进行一次重新生成,理论上来说,所有高级编程语言都可以这么操作。
但这不是说毫无用处,比如这篇文章《每个 gopher 都需要了解的 Go AST》就给了我们一个实际的案例:
最后
写到最后,我又在思考另一个问题,为什么Go的使用者没有AOP的需求呢?反倒是写Java的同学会想到AOP。
我觉得可能还是Go太年轻了,Java之所以要用AOP,很大的原因是代码已经堆积如山,没法修改,历史包袱沉重,最小代价实现需求是首选,所以会选择AOP这种技术。
反观Go还年轻,大多数项目属于造轮子期间,需要AOP的地方早就在代码中提前埋伏好了。我相信随着发展,一定也会出现一个生产可用Go AOP框架。
至于现在问我,Go能否实现AOP,我还是回答:没有、实现不了、再见。
对了,本文的完整测试代码这里可以看到:
https://github.com/lkxiaolou/all-in-one/tree/master/go-in-one/samples/tree
感谢大家,如果有点收获,点个在看
、赞
、关注
吧,我们下期再见。
搜索关注微信公众号"捉虫大师",后端技术分享,架构设计、性能优化、源码阅读、问题排查、踩坑实践。
Go能实现AOP吗?的更多相关文章
- 基于spring注解AOP的异常处理
一.前言 项目刚刚开发的时候,并没有做好充足的准备.开发到一定程度的时候才会想到还有一些问题没有解决.就比如今天我要说的一个问题:异常的处理.写程序的时候一般都会通过try...catch...fin ...
- Spring基于AOP的事务管理
Spring基于AOP的事务管理 事务 事务是一系列动作,这一系列动作综合在一起组成一个完整的工作单元,如果有任何一个动作执行失败,那么事务 ...
- 学习AOP之透过Spring的Ioc理解Advisor
花了几天时间来学习Spring,突然明白一个问题,就是看书不能让人理解Spring,一方面要结合使用场景,另一方面要阅读源代码,这种方式理解起来事半功倍.那看书有什么用呢?主要还是扩展视野,毕竟书是别 ...
- 学习AOP之深入一点Spring Aop
上一篇<学习AOP之认识一下SpringAOP>中大体的了解了代理.动态代理及SpringAop的知识.因为写的篇幅长了点所以还是再写一篇吧.接下来开始深入一点Spring aop的一些实 ...
- 学习AOP之认识一下Spring AOP
心碎之事 要说知道AOP这个词倒是很久很久以前了,但是直到今天我也不敢说非常的理解它,其中的各种概念即抽象又太拗口. 在几次面试中都被问及AOP,但是真的没有答上来,或者都在面上,这给面试官的感觉就是 ...
- .Net中的AOP系列之构建一个汽车租赁应用
返回<.Net中的AOP>系列学习总目录 本篇目录 开始一个新项目 没有AOP的生活 变更的代价 使用AOP重构 本系列的源码本人已托管于Coding上:点击查看. 本系列的实验环境:VS ...
- .NET里简易实现AOP
.NET里简易实现AOP 前言 在MVC的过滤器章节中对于过滤器的使用就是AOP的一个实现了吧,时常在工作学习中遇到AOP对于它的运用可以说是很熟练了,就是没想过如果自己来实现的话是怎么实现的,性子比 ...
- 在.Net中实现自己的简易AOP
RealProxy基本代理类 RealProxy类提供代理的基本功能.这个类中有一个GetTransparentProxy方法,此方法返回当前代理实例的透明代理.这是我们AOP实现的主要依赖. 新建一 ...
- 使用Java原生代理实现AOP
### 本文由博主柒.原创,转载请注明出处 ### 完整源码下载地址 [https://github.com/MatrixSeven/JavaAOP](https://github.com/Matri ...
- 【开源】.Net Aop(静态织入)框架 BSF.Aop
BSF.Aop .Net 免费开源,静态Aop织入(直接修改IL中间语言)框架,类似PostSharp(收费): 实现前后Aop切面和INotifyPropertyChanged注入方式. 开源地址: ...
随机推荐
- ES6-ES12部分简单知识点总结,希望对大家有用~
ES6-ES12简单知识点总结 1.ES6相关知识点 1.1.对象字面量的增强 ES6中对对象字面量的写法进行了增强,主要包含以下三个方面的增强: 属性的简写:当给对象设置属性时,如果希望变量名和属性 ...
- Basler acA1300-200uc相机使用教程
https://www.baslerweb.com/cn/products/cameras/area-scan-cameras/ace/aca1300-200uc/ 开发文档 https://zh.d ...
- 使用docker配置laravel5.5环境
简介 简述一下我用docker来配置laravel环境 软件镜像 php:7.2-rc-fpm-alpine mysql:5.6 nginx:1.12.2-alpine 配置环境 Ubuntu:16. ...
- CyclicBarrier和CountDownLatch区别
这两天写多线程时,用到了CyclicBarrier,下意识的认为CyclicBarrier和CountDownLatch作用很像,就翻阅资料查了一下,说一下他们的区别吧 CyclicBarrier和C ...
- zookeeper的通知机制
一.znode Zookeeper维护一个类似文件系统的数据结构.简单来说,有点类似windows中注册表的结构,有名称,有树节点,有Key(键)/Value(值)对的关系,可以看做一个树形结构的数据 ...
- phpstorm chrome 添加xdebug扩展
转:https://blog.csdn.net/ltcm_sakura/article/details/102967859 一.Xdebug helper:php调试插件 http://chromec ...
- 使用 JDBC 操作数据库时,如何提升读取数据的性能?如 何提升更新数据的性能?
要提升读取数据的性能,可以指定通过结果集(ResultSet)对象的 setFetchSize() 方法指定每次抓取的记录数(典型的空间换时间策略):要提升更新数据的性能 可以使用 PreparedS ...
- 什么是 AQS ?
AQS 是 AbustactQueuedSynchronizer 的简称,它是一个 Java 提高的底层同步工具类,用一个 int 类型的变量表示同步状态,并提供了一系列的 CAS 操作来管理这个同步 ...
- 用TLS/SSL保证EMQ的网络传输安全
作为基于现代密码学公钥算法的安全协议,TLS/SSL能在计算机通讯网络上保证传输安全,EMQ的MQTT broker支持TLS,也可以用这种方式来确保传输安全. 参考官网:https://www.em ...
- 机器学习之近邻算法模型(KNN)
1..导引 如何进行电影分类 众所周知,电影可以按照题材分类,然而题材本身是如何定义的?由谁来判定某部电影属于哪 个题材?也就是说同一题材的电影具有哪些公共特征?这些都是在进行电影分类时必须要考虑的问 ...