Spring之AOP

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与模式》一书)

原文地址:https://www.cnblogs.com/uu5666/p/6635772.html