心得:Spring AOP和Decorator模式

最近这个段时间都是忙于学习 SpringFramework,那东东好样的,只是刚刚学完Struts,Hibernate,有来了这个,呵呵,,感觉挺累的。
“你怎么把这两个不相干的东西撤到一起?” ,确实是这样! 无论从他们的设计,
实现,使用等方面他们是风马牛不相及, 但本人认为从理解的方面看他们有点类似。况且学习要从对比中学呀!这对初学者会有帮助于理解。
重要声明:本人都是用自己的语言写的,有专业术语不对还请大家指出。: - )
就说设计模式 Decorator,也就是装饰模式,这还不好理解? 在原有的东西上进行装饰一下便是装饰了。既然是装饰,你要有主体呀(被修饰的东西),还要有装饰品。但是不管你装饰来装饰去,他的本质是不变的。 就象人带上了面具,但他还是人。墙上打上了油漆,但它还是墙呀。你也许觉得这是废话。 但理解这点很重要(这是跟策略模式Strategy的区别),本人觉得这个是理解好 Decorator 模式很重要的一点。在开发中你经常要增强一个类的功能,或者是撤销一个类的某些功能,但是一个类给众多的功能进行装饰了以后,也许原来的类已经面目全非了,你往往会感到茫然,其实,你主要抓住他的主体,脑子里面时刻要知道你现在所作的工作就是为这个主体进行打扮的工作。
研究过Jive的都知道, 里面的 Filter 就是用了Decorator 设计模式,那么在这个设计里面,它的主体是什么? ForumMessage ,无疑是 ForumMessage 。装饰品当然是Filter 了,所以我们在作这件事情的时候始终是以ForumMessage为核心的,Filter 进行装饰的时候,你千万不要忘记你现在是为ForumMessage而工作!有关于jive研究的文章,网上一大堆,现在看看一个比较简单的 Decorator:



//比如有一个抽象构件:
public interface Component {
void operation();
}
//一个具体构件:
public class ConcreteComponent implements Component {
public void operation() {
//Write your code here
}
}
//一个抽象装饰:
public class Decorator implements Component {
private Component component;
//引用
… …. …. …
//商业方法,委派给构件
public void operation() {
component.operation();
}
}
//一个具体装饰:
public class ConcreteDecorator extends Decorator {
/**
* 商业方法
*/

public void operation(){
//一些具体的装饰的代码
......
super.operation();
// 主体
//一些具体的装饰的代码
.......
}
}

......
我们主要看看,在ConcreteDecorator 中的operation() 方法,他的父类是委托到component组建来完成的,其实它就是主体, 一些装饰代码都会在这个方法(主体)执行前进行“预处理”或是执行后进行“收尾”。
可不是,从上面很容易就看出了,抽象装饰中的operation() 方法没有在具体的装饰类中进行“全盘”覆盖,因为他用了super.operation(); 这样的语句。可见装饰模式中不管怎么样, 你都要找到类似这个的方法。说明他的主体还在。(这是跟策略模式Strategy的区别)
说了怎么多,不知道大家有没有感觉到ConcreteDecorator类中的operation()方法跟Spring AOP 中的 InvocationHandler 接口的invoke()方法有点相类是呢?
我们来看看要实现 InvocationHandler 时候要重写的invoke()方法:



public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
…………………….
result = method.invoke(originalObject, args);
……………………………..
return result;
}

InvocationHandler.invoke 方法将在被代理类的方法被调用之前触发。通过这个方法中,我们可以在被代理类方法调用的前后进行一些处理,如上面代码中所示,InvocationHandler.invoke方法的参数中传递了当前被调用的方法(Method),以及被调用方法的参数。同时,我们可以通过Method.invoke方法调用被代理类的原始方法实现。这样,我们就可以在被代理类的方法调用前后大做文章。(说明:引用了Spring 开发指南中的一段)
在回到装饰模式,其方法operation() 在执行前和执行后,也可以对它大做文章。如此看来, Spring AOP 和 Decorator,从理解层面上来看,确实有点相象,注意:只是理解层面, 而他们的实现完全两马事!
其实主要是大家怎么看待这个问题,按照本人的理解,Spring AOP 也可以说是一种广义的装饰,但它又不是装饰模式。它同样也是对某个方法加上了限制,比如insert() 的时候,你要对它做执行前开启事务和执行后提交或回滚这样的“装饰”。 又比如你也可以对某个人操作资源后做log这样的装饰工作。等等,,
他们的相同之处就这么点。
AOP 也不是什么新的东西,当然如果你的AOP是容器(Jboss 4.0)实现的话, 那么按照上面说的,你的一些方法就会被容器所“装饰”。
如此看来,更多的时候可以理解为 Spring AOP 和一些AOP 容器是在系统级的,容器级的“装饰”。 而装饰模式则是业务级的装饰。 这点其实从客户端也很容易理解。
如果是AOP ,程序员可以放心的写你的代码(即使有点笨也不怕,有AOP护着呢)。 因为系统将在系统运行时候对这段代码进行“装饰”。也就是说,这些装饰不会出现在客户端的代码中,,,而,Decorator模式呢?呵呵,这个时候程序员要有点头脑了,他们要自己组装一些装饰类,按照某一种机制对主体进行装饰。也就是说,这些修饰类要出现在客户端的代码中。看代码就知道了 :--)


InputStreamReader input = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(input);
....//主体是System.in

哦 , 够经典的例子!

写得不错!

IOC,service locator, template等模式也有相似之处,都是为了解除耦合。

decerator, aop的相似处在于seperation of concern.

>
> IOC,service locator,
> template等模式也有相似之处,都是为了解除耦合。

可道其详?

> decerator, aop的相似处在于seperation of concern.
我觉得decerator没有seperation of concern , 恰好相反,decerator
是反映在客户端得,是客户端得必要得逻辑, 而AOP不是!

写得不错,其实,我认为,Decorator和AOP并不是毫不相关的,目前很多人也在争论,可以使用Decorator实现的,为什么要用AOP实现?

其实,我个人体验,AOP其实是Decorator+Proxy,我在这个帖子了其实也谈到这个问题:
http://www.jdon.com/artichect/patternsInPractice.htm

Decorator也可以和AOP相联系,这是我的小小的aop framework中的例子http://joyaop.sourceforge.net/tutorial.html#_Toc85540197。普通的AOP interceptor一般是通用的(可以由pointcut随便指定目标),而这里的decorator式的interceptor则是针对特定接口的。当然这里借用rickard oberg的abstract schema AOP的许多概念,所以decorator是抽象类。但是AOP框架也可以很容易的把传统的OO decorator加到interceptor链中,这样decorator就对客户透明了(奇怪为什么没人去实现)。

Spring AOP 是使用动态Proxy 实现,而作出来的产品有貌像Decorator
....
嘿嘿,最近发现非洲有个人酷像张国容, 我说基因不一样,但基因组成
的产品就貌似张国容了。

Decorator和用什么方式去实现AOP没有太大关系。Spring现在也有用CGLIB2来做的。直接用JDK动态代理缺点比较多:性能损失比较大;对目标类可能不透明;目标类必须要接口。。。

而用CGLIB2生成代理可以实现得非常高效,我手上自己的框架现在的性能可以超过JBossAOP1.0了。


>直接用JDK动态代理缺点比较多:性能损失比较大;
shenli 说的是真的吗?为什么呢?

>对目标类可能不透明;目标类必须要接口。。。
确实,Spring 动态代理机制实现的 AOP 好像总是有点麻烦, 非得要去
作那么一个接口。使用的时候想想还真的有点不方便,不知道大家感觉怎么样? 若是选择,还是去选择CGLIB2方式的实现,至少省去作接口的麻烦。 虽然接口能够解偶,
但不至于你的每个类都要去作接口把!至于shenli 说CGLIB2提高性能,
可否分析,讲解一下原因?

Spring 缺省的是动态代理机制,它好像推荐这么做。其实这个性能确实不好,建议使用性能最好的aspectwerkz,aspectwerkz 作者已经调试成功嵌入Spring的方式。

在Spring的动态代理实现源码JdkDynamicAopProxy中关键invoke方法中,有一句:

invocation = new ReflectiveMethodInvocation(proxy, target,
method, args, targetClass, chain);

也就是说,每次通过Spring AOP动态代理拦截一个方法,都要有一个对象ReflectiveMethodInvocation生成,对象的频繁创建是最损耗性能的,因此这里也是一个性能败笔。当然,有可能以后版本会改进,不过,我觉得没有更好的更改办法。


性能问题主要取决于使用,如果是像spring推荐的只对service做拦截,一般问题不大;但是如果你使用的粒度很细,比如说所有domain object的所有getter方法,那性能开销就比较大了。

banq说得不错,invocation创建开销比较大。开始我不觉得,原来用jprofiler profile了一下也发现很小,但是直接benchmark发现它消耗了将近一半的时间。所以cglib好像在那里看到建议缓存在thread里面,类似hibernate session管理。我现在也改成这样了,确实有提高。其实这个最大的好处也许还是大大缓解了GC的压力。

aspectwerkz性能当然很好,主要它是直接修改字节码做静态优化。但是CGLIB2的interceptor可以到method级别,实际上估计差不多了,至少JBoss AOP和AW是一个原理,我的CGLIB实现比JBOSS的interceptor还略快一点点。

不过AW的主要麻烦是在于启动太麻烦了,把即插即用的好处都抵消了,这也是我不再喜欢它的缘故吧(其实它也许也可以提供一个工厂而不一定非要让用户选择切换classloader)。AW和JBoss的作者都喜欢用预编译,而且特别是AW整个就是在模仿aspectj,把它的一些弱点也包揽了(我觉得),那样我宁可用aspectj,虽然不是纯java。另外,CGLIB也支持切换classloader,本来也可以作到透明初始化。

提醒的真是不错,在java世界里,有好多原本很容易理解的东西,被装饰的,很多人都有些晕了。我认为我们搞技术的,关键是看他利用java的那些根本或比较基本的原理。这是最重要的。有些概念大家可能还不清楚,可是我们不能人云亦玩概念游戏,一个东西稍加修饰可以变换出太多的概念来。

我觉得说的就是代理和装饰的相同之处,运用模式的时候看你对问题的观点而定了。

装饰的主要目的是增加新的行为。而aop是增强旧的行为。这个不同还是希望注意到。

"aop是增强旧的行为"我有点疑问,如果我增加新的拦截器,不是变成了增加新的行为了吗?

他们直接的区别类似装饰和代理之间的区别。