2.2 AOP应用开发
通过对前面内容的学习,本节将进入AOP的应用开发的介绍。
2.2.1 AOP的开发过程
AOP的开发有一个共同的主题,那就是分离关注点,它的目的是通过清晰地分离关注点来解决以上提到的问题。
AOP使你可以用一种松散耦合的方式来实现独立的关注点,然后通过组合这些实现来建立最终系统。用它所建立的系统是使用松散耦合的、模块化实现的横切关注点来搭建的,与之相比,用OOP建立的系统则是用松散耦合的、模块化实现的一般关注点来实现的。在AOP中,这些模块化单元叫方面(Aspect);而在OOP中,这些一般关注点的实现单元叫做类。
AOP的开发包括3个过程,首先是分离关注点,然后是实现关注点与核心功能,最后再是组合关注点与核心功能,形成整个完整的系统,如图2-1所示。
图2-1 AOP的开发过程
详细的解释如下。
● 分离关注点:分解需求提取出横切关注点和一般关注点。在这一步里,你可以把核心模块级关注点和系统级的横切关注点分离开来。就前面所提到的例子来说,你可以分解出3个关注点,即日志关注点、权限关注点、事务关注点;
● 实现关注点:各自独立地实现这些关注点。还是使用上面的例子,你要实现日志单元、权限单元和事务单元;
● 组合关注点:在这一步里,方面集成器通过创建一个模块单元方面来指定重组的规则。重组过程(也叫织入或结合)则使用这些信息来构建最终系统。还拿上面的例子,你可以指定(用某种AOP的实现所提供的语言)每个操作的开始和结束需要的日志、启动和关闭事务,并且指定每个操作在涉及到业务逻辑之前必须通过权限验证。
AOP与OOP的不同,关键在于它处理横切关注点的方式。在AOP中,每个关注点的实现都不知道其他关注点是否会“关注”它。一个AOP实现可以借助其他编程范型作为它的基础,从而原封不动地保留其基础范型的优点。例如,AOP可以选择OOP作为它的基础范型,从而把OOP善于处理一般关注点的好处直接带过来。要达到这样一种实现,独立的一般关注点可以使用OOP技术。这就像过程型语言是许多OOP语言的基础一样。
2.2.2 AOP的实现语言
就像其他编程范型的实现一样,AOP的实现也由两部分组成:语言规范和语言实现。语言规范描述了语言的基础单元和语法;语言实现则按照语言规范来验证代码的正确性并把代码转成目标机器的可执行形式。
从抽象的角度看来,一种AOP语言要说明下面两个方面。
● 实现关注点的代码:把每个需求映射为代码,然后编译器会把它翻译成可执行代码,由于关注点的实现以指定过程的形式出现,你可以使用传统语言如C、C++、Java等。
● 织入关注点的代码:怎样把独立实现的关注点组合起来形成最终系统呢?为了达到这个目的,需要建立一种语言来指定组合不同的实现单元以形成最终系统的规则,这种指定织入规则的语言可以是实现语言的扩展,也可以是一种完全不同的语言。
因此,要实现AOP的功能,编写关注点代码是必不可少的。在编写了关注点的代码后,就需要将这些代码织入到系统的代码中,这个过程就需要由专门的AOP编译器来实现。这包括以下两个步骤:
第一步:将多个关注点的代码组装起来。
第二步:把组装结果转成可执行代码。
AOP实现可以用多种方式实现织入,包括如下几种。
● 源码到源码的转换:它通过预处理每个方面的源码产生织入过的源码,然后把织入过的源码交给基础语言的编译器产生最终可执行代码。比如,一个基于Java的AOP实现可以先把不同的方面转化成Java源代码,然后让Java编译器把它转化成字节码;
● 也可以直接在字节码级别执行织入。毕竟,字节码本身也是一种源码;
● 此外,执行系统(Java虚拟机)也可以是方面认知的。基于Java的AOP实现如果使用这种方式的话,虚拟机可以先装入织入规则,然后对后来装入的类都应用这种规则,也就是说,它可以执行just-in-time的方面织入。
在下一节中我们将会展开讲解具体的织入策略。
2.2.3 AOP的应用范围
AOP非常适合开发Java EE容器服务器,目前AOP可以应用在以下几个方面。
● Authentication:权限;
● Caching:缓存;
● Context passing:内容传递;
● Error handling:错误处理;
● Lazy loading:懒加载;
● Debugging:调试;
● logging, tracing, profiling and monitoring:记录跟踪、优化、校准;
● Performance optimization:性能优化;
● Persistence:持久化;
● Resource pooling:资源池;
● Synchronization:同步;
● Transactions:事务。
在后面的框架技术中,将会讲解上面的某些实现案例。
2.2.4 AOP最佳实践——为HelloWorld添加日志功能
我们应用AOP的方向有很多,这里以最具代表性的Log为例。大多数人了解AOP,都是从经典的Log关注点的剥离开始的。这是每一个AOP的爱好者都耳熟能详的案例——一个经典的使用AOP做日志的例子。
按照AOP的思想,我们假定业务逻辑的开发人员在编写代码的时候不应该考虑边缘逻辑。因此,日志代码实际对业务逻辑的开发者不可见。我们以主流的Log4J为记日志的实现方式,以AspectJ作为Aspect的实现方式,编写下面的方面代码Log。
import org.apache.log4j.Logger; public aspect Log { pointcut log() : execution(* Hello.sayHello(..)); before() : log() { Class clazz = thisJoinPoint.getClass(); Logger log = Logger.getLogger(clazz); log.info("start execute: " + thisJoinPoint.getSignature()); } after() returning() : log() { Class clazz = thisJoinPoint.getClass(); Logger log = Logger.getLogger(clazz); log.info("end execute: " + thisJoinPoint.getSignature()); } }
上面定义了一个切入点log(),用以匹配Hello的sayHello()函数,并分别定义了两个通知函数。在这两个通知函数中分别调用了Log4j的类Logger来创建一个日志对象,然后记录日志信息,并同时输出当前执行的函数名。
此时执行Hello.java的类,就会输出如下的信息。
Hi, 2007-08-09 09:24:41,296 INFO [main] (Log.aj:12) - start execute: void com.Hello.sayHello() Hello 2007-08-09 09:24:41,296 INFO [main] (Log.aj:18) - end execute: void com.Hello.sayHello() World!
从上面的输出可以看出,它是先执行了World的方面,然后执行Log的方面,输出了日志的时间和执行函数名信息。
根据统计,在一个中型的Java EE项目中,大约有7万行记录Log的调用。如果我们不做任何相关的抽取工作,直接一对一地对切入点进行描述,那么在原始代码中记录日志的代码就有7万行之多。姑且不算工作量,即使这样做了,将来需要维护的代码量也将是天文数字。而现在通过日志的方面实现,仅需要这样一个类就可以取代那7万行日志代码,不仅使代码清晰,而且代码的工作量也很小。由此可见AOP的威力所在。
2.2.5 AOP最佳实践——为HelloWorld添加权限控制功能
引入AOP概念后的权限实现已经不像通常的实现那样“落后”,我们可以创建一个Aspect,专门用于权限检查,如下:
public aspect Auth { pointcut check() : execution(* Hello.sayHello(..)); before() : check() { String name = thisJoinPoint.getSignature().getName(); if(name.equals("sayHello")) { throw new RuntimeException(); } } }
该段代码的功能是:当系统运行Hello.sayHello()方法之前,将首先检查是否有权限操作。代码中pointcut触发的条件是执行check()方法,它用来检查当前执行的函数是否有执行权限。
这里的演示比较简单,只是通过比较执行的函数名来抛出一个异常。因此当执行该函数时,就不会再输出“Hello”的字符串了。执行后的输出如下:
Exception in thread "main" java.lang.RuntimeException at com.Auth.ajc$before$com_Auth$1$625e745(Auth.aj:10) at com.Hello.sayHello(Hello.java:6) at com.Hello.main(Hello.java:10)
在实际的应用中可以将函数名与当前用户的角色进行对比,以达到最细化的权限检验。此时可以利用如RBAC等模型进行权限控制。