虽然 Ruby 有几个 AOP 库,但是这些 AOP 库最大的缺点就是使用繁琐,而且调试也不方便。 如果 Ruby 可以 native 的支持 AOP 的话就好了,就像 AspectJ 那种。
我不知道 AOP 是什么,以前讨论过几次这个话题,看有没有帮助
http://ruby-china.org/topics/6681 http://ruby-china.org/topics/4676
#1 楼 @Rei 还真的没有什么帮助 很多人把 AOP 想的太简单了我觉得 Ruby 实现 AOP 其实没有这么简单,AOP 本身非常灵活,Ruby 本身也非常灵活,把一个灵活的功能加入一个灵活的语言,这个难度不知道比加入到象 Java 这种死板语言中大多少倍。
AOP 的解释我觉得问号课堂里面已经讲的很清楚了 你可以看看 http://mba.shengwushibie.com/itbook/BookChapter.asp?id=34730
#3 楼 @Rei 说起来很简单 比如在一个复杂的系统中,我们有大量的重要方法现在需要纪录日志,比如纪录 stack,纪录传入参数,返回值等等。 传统的方法也就是最傻的办法是在这些方法前后增加 log 语句,但是这样做修改的规模可能非常大,并且有可能搜索不完全,比如那些要被动态 eval 那些字符串代码里可能也包含了这些重要方法,你用 grep 基本搜索不到,除此以外代码也不美观,也增加了耦合度(毕竟日志不是必需的逻辑,不该存在于重要的系统中)。直接在方法里面增加日志也不是很好的办法,虽然修改量很小,耦合度却增加了。 AOP 的解决方法是申请对某个类中的某些方法(可以用正则表达式匹配)进行代码注入,也就是在这个方法被执行前后增加所需的代码。 比如 UserShoppingSystem 和 EnterpriseShoppingSystem 里的 buy_products 和 sell_products 是重要方法,需要被纪录日志
class UserShoppingSystem
def buy_products(...)
...
end
def sell_products(...)
...
end
end
class EnterpriseShoppingSystem
def buy_products(...)
...
end
def sell_products(...)
...
end
end
你当然可以遍历搜索代码,找到调用这些方法的地方来为其增加 log 语句,当然缺陷很明显,之前已经说了。 我现在用 AOP 代码(当然是我假象的)来解决这个问题
aop /ShoppingSystem$/./_products$/
around(method_name, params)
log("#{method_name} #{params}")
return_val = yield
log("return: #{return_val}")
end
end
只要上述代码可以被执行到(无论任何地方,没有执行先后次序的要求),以 ShoppingSystem 结尾的类中所有方法名以_products 结尾的方法,被调用前后都会先执行到相应的 AOP 代码。
实现 AOP 的最大难度在于 Ruby 是可以动态添加和修改方法的,而且手段极多。Java 没有类似问题,所以简单。由于 AOP 并不是针对单一方法进行注入的,而是可以使用正则表达式对方法和参数进行匹配的。一个方法可能在 AOP 被申明后添加进去,如果仅仅只是添加在这个类本身,那还好点,有个 method_added 的 callback 可以用,但如果是添加在 Singleton 里呢,添加在 module 里然后 include 进去呢,在父类里添加呢?这些都不是容易实现的。 除此以外,由于 AOP 功能实在太强大,如果不注意有可能会产生非常奇怪的行为,引入 bug,因此需要支持良好的跟踪和调试。这些都不是一般的解决方案就能够解决的。
AOP 要解决的问题,在 Ruby 中不存在...
如果真想用 AOP 处理问题,你可以用 aspectJ 编译 java 代码然后用 JRuby 引入...
如果对普通业务代码和其它 AOP 代码不做区分,通通都要 AOP,并且一定要让被植入逻辑的目标代码对此一无所知,这个难度并不小,但这对 java 也是一样,因为这更象是对 AOP 代码进行 AOP,然而 java 工程师们并没有为此纠结,因为这个问题本身也许是个伪命题。
#11 楼 @iBachue 你没理解我的意思,java 工程师们想出了 AOP 技术来解决一些通用问题,但是他们没有在多个 AOP 组件之间建立更进一步的 AOP over AOP 的技术结构,而只是简单的规定——”AOP 功能代码要保持自我完备和功能最小化”,这样,开发 AOP 功能仅仅针对“普通代码”。
ruby 也是一样,类似 AOP 的功能模块重点是服务“普通代码”,至于用元编程技术“植入”或者各种技巧增强的代码,可以简单忽略,这样你就不用纠结了。
当然,实际情况可能比这个复杂,因为有 DSL 技术存在,基于 ruby 的方案可能是多个 DSL 从底层到高层“堆叠”的过程,那么你的“AOP”代码就要明确自己关注的是哪个层次,仅为那个层次的语言编写横切逻辑即可
#8 楼 @iBachue AOP 说白了就是代码生成工具,玩代码生成是玩不过 Ruby 元编程的。
你觉得系统一定要大型么?大型 Ruby 系统就一定要有这么多重复打日志的调用么?打日志就一定要 AOP 么?我整过自动给 java 方法加统一格式日志的方法,尝试过用 AOP, 但当时就是大型 Java 系统,包依赖很变态各种 alibaba logger 和 log4j 和 slf4j 混在一起,还得看配置按情况插不同的代码,最后还是自己写了 annotation processor 各种改语法树才弄好...
这种问题归根就是:1. 编程环境没有足够强的 debug 能力,只好用大量手动日志来分析系统状况,2. 编程环境没有足够强的动态能力,只好依赖代码生成工具和容器,甚至各种半吊子的解释器引擎。
p.s. 如果用 aspectJ 我猜一半人的 eclipse 搞不定,如果用 spring 的动态 aspect 运行又很慢... p.s. 最悲惨的 debug 方式莫过于看日志了,最耗资源吃力不讨好的数据分析也莫过于日志分析了...
用 set_trace_func
实现追踪日志应该可以满足你在 #4 楼 描述的需求,而且添加切点的逻辑不局限于 AOP 的定义。
http://www.ruby-doc.org/core-1.9.3/Kernel.html#method-i-set_trace_func
@iBachue 我是从 Java 转到 Ruby 的,我最大的感受是 Java 总是在过度设计,很多时候和很多地方都在为并不存在也并不需要的可扩展性、可通用性之类的需求生造出许多漂亮的设计来,尽管这个设计非常难用、非常晦涩,但它的确非常完美!
Java 的哲学是用 80% 的精力思考未来和解决未来的问题,被解决的问题根据我的经验,99% 的情况下一千年都不会出现,但对于 Java 程序员来说,明知公元 2012 年—公元 3012 年之间的某个时候会出现这个问题而不去解决它,那绝对是不可饶恕的,不解决它饭都吃不下去。然后,Java 程序员会在百忙之中抽出 20% 的宝贵时间来解决当下的问题,如有可能,当年我还想把这个比例压缩到 10%,你知道的,未来的事情已经够让我头大,现在这些小事儿最好不要来烦我!
当我用 Ruby 后,才发觉,1000 年太久,我非常怀疑我的应用能不能够活过 1000 天,如果 1000 天后我的程序还有问题让我去解决,天啊,那不是太 TMD 幸福了吗?!
这就是,我理解的 Java 哲学 和 Ruby 世界。
@iBachue 用 meta programming 实现的 AOP,听说有个 aqua 你可以试试
class A
def a
puts "a"
end
end
module Logger
def log(method_name)
m = self.instance_method(method_name)
undef_method method_name
define_method method_name.to_s do
puts "start"
m.bind(self).call
puts "end"
end
end
end
A.extend Logger
A.log :a
A.new.a
#5 楼 @luikore AOP 要解决的问题不仅在 Ruby 中不存在,在 Java 中也不存在。显然语言自身的问题和 AOP 要解决的问题没任何关系啊。
Ruby 和 Java 都只是一门语言而已,而且从 OO 的角度看,都只能算是很拙劣的 Smalltalk 模仿。即便分别对于某一些问题,他们都还算是不错的。
AOP 作为一种范式,是用来解决一类问题的。但,现实是,太多人介绍 AOP 的时候,就喜欢拿 logging 当例子,特别是和 Java 扯上关系之后,花了很大篇幅在讲如何在 Java 里实现 AOP 这种范式。于是,很多人都把精力花在了怎么实现上,而忽视了去理解为什么这种范式适合解决某一类问题。
基本上是和设计模式类似的情况。比如 Visitor Pattern,即便某个语言支持了 Pattern Match,所谓 Visitor Pattern 就是用一下 Pattern Match 而已。但重点不是 Pattern Match 如何优越,而是你用 Visitor Pattern 把问题解决了。
总结一下就是重点是怎么用来解决问题,而不是怎么实现。哪怕你用一个很残废的实现把问题解决了,也比整天研究怎么实现地更好一点但完全不考虑能用来怎么解决问题好。这只是为某一种解决问题的思路起个名字而已,没有人天生就能想到所有思路,起个恰当的名字,大家都在术语上统一一下,这样碰到问题的时候一说就能明白了,而在具体问题里够用的实现就是好实现,没必要深究啊,除非你被坑到了
#26 楼 @nouse https://github.com/baccigalupi/aqua 这个??A Ruby Object Database?
@iBachue 我刚开始从 java 转 ruby 时,也老在 ruby 里面找 java 的对应概念,多态啦,aop 呀。。 这些都是在 java 那样的强类型,静态编译语言中诞生的概念,对 ruby 不是说没用,但没那么适用。 像多态,duck type,天生就是多态。 只要你传进去的 object 能 response 某个方法调用,那方法就会被调用,不用 care 它是三角形还是圆形。 所以接口啊,抽象类啊这些东西,ruby 世界也没有了。
#31 楼 @feitian124 Ruby 是动态语言,Java 是静态语言,所以在涉及到类型的地方二者自然天差地远,但是这和 AOP 没什么关系啊。
@iBachue 所以我的意思是: 多态,接口 ruby 世界 90% 不需要,所以也确实没有。 aop 在 ruby 世界估计也 80% 不需要,所以也没几个人 care。。 所以参照你的标题,我已经回答了你的问题了,怎么没关系?
凑凑热闹,前不久我也问过 aop 的问题。我估计是 ruby 的应用领域可能有关系,所以 aop 在 ruby 很少人关注。
据说目前银行还是不用 ruby 的。当初 spring 就是用 aop 解决了事务处理问题。
aop 一般大型应用才比较有用。
#33 楼 @feitian124 二者不能类比,多态,接口是类型相关的问题,我说了 Ruby 和 Java 在这块地方有本质差异,所以 Java 的理论不适用于 Ruby。AOP 不是类型相关的问题,因此这不能证明 Ruby 不需要 AOP。
是不是 Java 阵营转过来的同学习惯把问题 java 化,然后用 java 化的方法解决呢?既然是用 ruby,那就 多多考虑一下 ruby style 的方法。
Java 并不鼓励 AOP,Java 里的只是 Proxy,在 EJB 里用得很谨慎很规整,大量使用 AOP 的是 Spring,简直为了制造噱头滥用,每次动 Spring 的那堆配置文件的时候都好像在擦一堆粘粘乎乎的东西,太恶心了,那东西做时间长了审美观必然扭曲
我想 Ruby2.0 的Module#prepend
是个很简单优雅的 AOP 方案
http://dev.af83.com/2012/10/19/ruby-2-0-module-prepend.html
rails 的一个 module 就提供这个功能。
class Person
def getName
puts "andyshi"
end
end
class Person
def getName_new
puts "before"
getName_old
puts "after"
end
alias_method :getName_old, :getName
alias_method :getName, :getName_new
end
#50 楼 @woaigithub 就是 alias_method_chain 嘛,这个只是实现了 AOP 的部分功能而已。另一部分我在 4 楼已经描述了,这里才是真正的难点
我认为所谓的 AOP 要解决的问题在具体场景下还是可以用各种其他方法和模式来解决的 比如惯用例子 - 日志,在 rails 中用 Active Record Observer 不可以解决吗(其实我不是写 rails 的,我就是随便搜了下文档) 我是写 php 的,实际上我在 Yii 中的解决方案是使用了全局的事件系统,给某类任务(比如动态、日志、积分、活跃度)做一个单独的 eventHandler 类来处理 acticerecord 抛出的事件,这样同样实现了分离关注点
相反,在这种场景使用 AOP 是一种简单粗暴的做法,就像一提钓鱼岛就要扔原子弹(PHP 也有 AOP 扩展啊,反正我是不可能用)
#55 楼 @chuck911 我针对的是 Ruby,Rails 已经把 Web 能碰到的大部分问题都解决了,active record 的事件可以用 observer,action 的事件可以用 filter,其它基本上没机会用此类 callback,自然不需要 AOP 了。所以我提 AOP 是针对 Ruby 的,比较 General 的情况。顺便说下,其实 Rails 这套框架本身也有很多可以应用 AOP 的地方,但是由于 Ruby 不提供(可能也难以实现),于是 Rails 做了另外一套库,比如前面提到的 alias_method_chain 和 Callback,然后在自己项目中大规模使用。但是 Rails 由于是一个独立项目,可以自己指定自己的规则,而真正接近实际生产环境的开发,就不是这么容易的了,没每一个模块都可能由不同的人在不同的地点,不同的时间段内为了不同的目的而开发的。想要完全实现加日志这个例子,运气好的时候多花点人力时间还能勉强做到,运气不好的时候就不可实现了(比如那部分代码你没有修改权限之类的,或是政策上不允许出现此类依赖之类的)。
#58 楼 @sevk AOP 本来就是个炒作的产物,某些 Java 框架的作者为了推销他们的产品炒作各种概念,仿佛他们的概念先进到你不用他们的东西就寸步难行一样,实际上很早之前的 EJB 里用的 Proxy 机制已经很简单明了,某些人为了 Without EJB 搞了另外一套更复杂的东西,再加上 JCP 委员会里的人拼命维持现状拒绝改进,把这种炒作当成繁荣,於是塑造了这么个空前的泡沫繁荣,这种做法加重了软件项目的负担,而且依赖上之後就是强耦合,你拿不掉的,这是软件项目的大忌,为了达到他们的商业目的不惜引诱你犯这种大忌,所以这种做法应该抵制
meta programming 里已经包含了 AOP 的所有概念,远比 AOP 优雅,没必要去迎合
假如是多个方法该怎么办?比如有若干个 controller
,每个 controller
里面都有各自的方法,需要在每个方法调用前后都打印 before after ..