IoC和AOP这两个缩写总是一起出现。在形式上,两者同为三个字母的缩写,而且第二个字母都是O,有对仗美;在性质上,两者同为Spring的核心技术和特色,是最常被提起的概念。
但与面向切面编程AOP真正对应的,是OOP,即面向对象编程。
未说面向切面,先说面向过程。
面向对象侧重静态,名词,状态,组织,数据,载体是空间;
面向过程侧重动态,动词,行为,调用,算法,载体是时间;
这两者,运行于不同维度,本不互相冲突,理应携手合作,相互配合。
所以,web项目中的controller,service,dao等各层组件,有行为无状态,有方法无属性,即使有属性,也只是对下一层组件的持有;
所以,web项目中的entity,dto等各种实体,有状态无行为,有属性无方法,即使有方法,也只是getter/setter等,围着状态打转;
反倒是我们刚学「面向对象」时说的「既有眼睛又会叫」的小狗那种状态行为俱全的对象,基本见不到了。
程序需要状态,但对象不需要状态。
如果对象有了状态,就会引发烦人的多线程问题,在集群环境下更是麻烦。
程序的状态,统一由数据库,缓存,任务队列这些外部容器来容纳,在处理时,仅仅在对象的方法中以局部变量的面目偶尔出现,被封在线程内部,朝生夕灭,任由回收。
基于Java语言的web开发,本质是用面向对象的组织,面向过程的逻辑,来解决问题。应用实践中灵活具体,不拘泥,不教条。
但仍会遇到一种麻烦,即假如一个流程分三个步骤,分别是X,A,Y,另一个流程的三个步骤是X,B,Y。
写在程序里,两个方法体分别是XAY和XBY,显然,这出现了重复,违反了DRY原则。
你可以把X和Y分别抽成一个方法,但至少还是要写一条语句来调用方法,xAy,xBy,重复依然存在。
如果控制反转来处理这问题,将采用模板方法的模式,在抽象父类方法体中声明x?y,其中?部分为抽象方法,由具体子类实现。
但这就出现了继承,而且调用者只能调用父类声明的方法,耦合性太强,不灵活。
所以,我们常看到,只有那些本来就是调用者调用父类声明的方法的情况,比如表现层,或者本来就不用太灵活,比如只提供增删改查的持久层,才总出现抽象父类的身影。
具体Controller is-a 抽象Controller,具体Dao is-a 抽象Dao,这大家都能接受。
但除了在抽象Controller、抽象Dao中固定的步骤之外,我们就不需要点别的吗?
比如在某些Controller方法运行之前做点什么,在某些Dao方法运行之前之后做点什么?
而且最好能基于配置,基于约定,而不是都死乎乎硬编码到代码里。
这些需求,基本的编程手段就解决不了了。
于是乎,面向切面横空出世。
《Spring3.x企业应用开发实战》(下称《3.x》)第6章写道:
AOP是OOP的有益补充。
Spring实现的AOP是代理模式,给调用者使用的实际是已经过加工的对象,你编程时方法体里只写了A,但调用者拿到的对象的方法体却是xAy。
x和y总还是需要你来写的,这就是增强。
x和y具体在什么时候被调用总还是需要你来规定的,虽然是基于约定的声明这种简单的规定,这就是切点。
《EXPERT ONE ON ONE J2EE DEVELOPMENT WITHOUT EJB》第8章、《Spring实战》第4章:
增强(advice,另译为通知,但《3.x》作者不赞成):在特定连接点执行的动作。
切点(pointcut):一组连接点的总称,用于指定某个增强应该在何时被调用。
连接点(join point):在应用执行过程中能够插入切面的一个点。(我注:就是抽象的「切点」声明所指代的那些具体的点。)
切面(aspect):通知(即增强)和切点的结合。
其他概念不赘,如果有兴趣可以自行去翻书,我每次看到这些东西都很头大。
用人话说就是,增强是「干啥」,切入点是「啥时候干」。
生活中例子如端碗-吃饭-放筷子,端碗-吃面-放筷子,你只要定义好端碗和放筷子,并声明在吃点啥之前之后调用它们,业务方法只要实现吃饭、吃面就行了,以后想加个吃饺子也很方便。
生产中例子如事务、安全、日志(*),用声明的方式一次性配好,之后漫漫长夜专注于写业务代码就行了,不再为这些事而烦。
《Spring实战》第4章:
散布于应用中多处的功能(日志、安全、事务管理等)被称为横切关注点。
把横切关注点与业务逻辑分离是AOP要解决的问题。
*:但《Spring3.x企业应用开发实战》第6章说:
很多人认为很难用AOP编写实用的程序日志。笔者对此观点非常认同。(我注:我也认同)
总之,面向切面的目标与面向对象的目标没有不同。
一是减少重复,二是专注业务。
相比之下,面向对象是细腻的,用继承和组合的方式,绵绵编织成一套类和对象体系。
而面向切面是豪放的,大手一挥:凡某包某类某开头的方法,一并如斯处理!
《Javascript DOM编程艺术》说,dom是绣花针,innerHTML是砍柴斧。
我看面向对象和面向切面,也可做如是观。
没有依赖注入,面向切面就失去立足之本。
没有面向切面,依赖注入之后也只好在各个方法里下死力气写重复代码,或者搞出来一个超级复杂的抽象基类。
同时有了这两者,才真正能履行拆分、解耦、模块化、约定优于配置的思想,才真正能实现合并重复代码、专注业务逻辑的愿望。
不过,这面向切面不是Spring的专利,Java Web开发中最基本的Filter,就是一层一层的切面,突破了之后才能触及Servlet这内核。
但Filter过于暴力粗放,只能运行在Servlet之外而不能在之内,能上不能下,稍微细一点的批处理它就不行了,而Spring的AOP可以。
(Struts2的Intercepter也算,关于这就不多说了,如感兴趣可看《Struts2技术内幕》第8章Intercepter部分)
从理论上说,Filter和Spring AOP前者是责任链模式(Struts2 Intercepter也是),后者是代理模式,性质不同,但从「层层包裹核心」的共同特点看,是一致的。
所以无论是宽是窄,只要你遇到了「好多方法里都有重复代码好臭哇呀」的情况(关于代码的坏气味可以参考《重构》),而又无法应用策略、装饰器、模板方法等模式,就考虑AOP吧!
毕竟虽然Spring的书籍里讲到AOP就连篇累牍、名词繁多、配法多样、望而生畏,但具体写起来还是非常简单的。
(不过,如果能用「绣花针」OOP的设计模式实现,还是不建议轻易动用AOP这「劈柴刀」,不得已才用之。关于设计模式,推荐《Java与模式》一书)