2.3 面向切面编程

本节主要认识横切、纵切,理解什么是AOP(Aspect-Oriented Programming,面向切面编程)以及AOP的实现原理。在此基础上通过示例动手操作加深理解。

2.3.1 认识横切和纵切

首先认识一下什么是横切、纵切,这就要利用生物方面的知识了。切面的方向是这样规定的:拿植物的茎举例,纵切就是沿长轴来切,横切即是垂直与纵切的切法。编程相对来说是比较抽象的,有时候我们通过身边的事物来将抽象的具体化,这样也能更容易理解。

利用百度搜索“横切”“纵切”时,首先搜出来的结果是剖宫产的结果,内容是这样描述的:“由于人体和血管、神经系统等都是纵向的走向,所以纵切更有利于皮肤和伤口的愈合,通常在1年到2年就可以彻底恢复。但纵切的缺点是伤口较大,会留下明显的疤痕,会影响美观。横切的方法出血较少,并且出现伤口感染的机率要低于纵切,所以安全性比纵切高一些,只是不利再次进行剖宫产手术。”这里的横切、纵切与编程中的还是挺相似的。

2.3.2 什么是AOP

AOP,可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为(日志、安全、事务)的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如,日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码。在OOP设计中,它导致了大量代码重复、模块间的藕合度高,不利于各个模块的重用。

AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为“Aspect”,即切面。所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

2.3.3 AOP原理

AOP实际上是由目标类的代理类实现的。AOP代理其实是由AOP框架动态生成的一个对象,该对象可作为目标对象使用。AOP代理包含了目标对象的全部方法(见图2-3),但AOP代理中的方法与目标对象的方法存在差异,AOP方法在特定切入点添加了增强处理,并回调了目标对象的方法。

图2-3

由于是代理实现AOP,因此有必要学习一下代理。下面通过实例一步一步地了解静态代理和动态代理。新建一个ServiceImplA类,实现IService接口,想在调用service方法前后增加日志打印或为service方法增加try catch,那么该怎么做呢?

1.在每处调用的地方增加日志和try catch

这也是一种方法,但缺点是很明显的,就是每处都要更改,量也会很大,显然不可取。这里是每个点都要加,一个方法可能被调用多处,就要写多次。而且以后再进行修改时也不方便,每个地方都要修改。

2.代理模式

代理模式又分为动态代理模式和静态代理模式。

(1)静态代理

静态代理关键是在代理对象和目标对象实现共同的接口,并且代理对象持有目标对象的引用。这里用类ProxyServiceA来实现IService接口,同时将实现IService接口的对象作为一个属性。

输出结果:

     log start
     ServiceImplA service:CYW
     log end

有了ProxyServiceA之后,打印日志和增加try-catch只需放在ProxyServiceA类里面,便于后续修改,比如现在打印日志是输出在操作台的,哪天需要输入到日志文件时也只需修改ProxyServiceA中的打印操作即可。但问题来了:项目中的接口可不止一个,可能会有很多,而且每个接口中的方法也会有好多,这样一个一个地增加也是问题,于是有了动态代理。

(2)动态代理

在Java的动态代理机制中,有两个重要的类或接口:一个是InvocationHandler(Interface),另一个是Proxy(Class)。这一个类和接口是实现动态代理所必须用到的。

输出结果:

通过Proxy.newProxyInstance()生成的动态代理对象A都会与实现InvocationHandler接口的对象B关联,动态代理对象A调用目标对象方法时都会变成调用B中的invoke方法。在invoke方法中织入增强处理,并通过反射回调目标对象方法。在本例中,通过bind()生成目标对象ServiceImplA的动态代理对象A,A关联了实现InvocationHandler接口对象的DynaProxyServiceA,当动态代理对象A调用目标对象方法时会执行DynaProxyServiceA的invoke方法,增加try-catch、打印日志,并回调目标对象的方法。

与前面的静态代理比较发现,动态代理不用再为每个接口手动创建代理类,其他对象只要与InvocationHandler接口对象bind,就能获得该InvocationHandler接口对象的织入增强。