AOP(Aspect Oroented Programming,面向切面编程)是消除代码重复的一种方法。
AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向方面编程。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。
举例:假设有在一个应用系统中,有一个共享的数据必须被并发同时访问,首先,将这个数据封装在数据对象中,称为Data Class,同时,将有多个访问类,专门用于在同一时刻访问这同一个数据对象。
为了完成上述并发访问同一资源的功能,需要引入锁Lock的概念,也就是说,某个时刻,当有一个访问类访问这个数据对象时,这个数据对象必须上锁Locked,用完后就立即解锁unLocked,再供其它访问类访问。
使用传统的编程习惯,我们会创建一个抽象类,所有的访问类继承这个抽象父类,如下:
abstract class Worker{
abstract void locked();
abstract void accessDataObject();
abstract void unlocked();
}
AOP的缺点
accessDataObject()方法需要有“锁”状态之类的相关代码。
Java只提供了单继承,因此具体访问类只能继承这个父类,如果具体访问类还要继承其它父类,比如另外一个如Worker的父类,将无法方便实现。
重用被打折扣,具体访问类因为也包含“锁”状态之类的相关代码,只能被重用在相关有“锁”的场合,重用范围很窄。
仔细研究这个应用的“锁”,它其实有下列特性:
“锁”功能不是具体访问类的首要或主要功能,访问类主要功能是访问数据对象,例如读取数据或更改动作。
“锁”行为其实是和具体访问类的主要功能可以独立、区分开来的。
“锁”功能其实是这个系统的一个纵向切面,涉及许多类、许多类的方法。如下图:
因此,一个新的程序结构应该是关注系统的纵向切面,例如这个应用的“锁”功能,这个新的程序结构就是aspect(方面)
在这个应用中,“锁”方面(aspect)应该有以下职责:
提供一些必备的功能,对被访问对象实现加锁或解锁功能。以保证所有在修改数据对象的操作之前能够调用lock()加锁,在它使用完成后,调用unlock()解锁。
AOP应用范围
很明显,AOP非常适合开发J2EE容器服务器,目前JBoss 4.0正是使用AOP框架进行开发。
具体功能如下:
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging 调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
AOP有必要吗?
当然,上述应用范例在没有使用AOP情况下,也得到了解决,例如JBoss 3.XXX也提供了上述应用功能,但是没有使用AOP。
但是,使用AOP可以让我们从一个更高的抽象概念来理解软件系统,AOP也许提供一种有价值的工具。可以这么说:因为使用AOP结构,现在JBoss 4.0的源码要比JBoss 3.X容易理解多了,这对于一个大型复杂系统来说是非常重要的。
从另外一个方面说,好像不是所有的人都需要关心AOP,它可能是一种架构设计的选择,如果选择J2EE系统,AOP关注的上述通用方面都已经被J2EE容器实现了,J2EE应用系统开发者可能需要更多地关注行业应用方面aspect。
AOP具体实现
AOP是一个概念,并没有设定具体语言的实现,它能克服那些只有单继承特性语言的缺点(如Java),目前AOP具体实现有以下几个项目:
AspectJ (TM): 创建于Xerox PARC. 有近十年历史,成熟
缺点:过于复杂;破坏封装;需要专门的Java编译器。
动态AOP:使用JDK的动态代理API或字节码Bytecode处理技术。
基于动态代理API的具体项目有:
JBoss 4.0 JBoss 4.0服务器
nanning 这是以中国南宁命名的一个项目,搞不清楚为什么和中国相关?是中国人发起的?
基于字节码的项目有:
aspectwerkz
spring ?
定义和概念
AOP像大多数编程范式一样,有她自己的词汇表。下表定义了许多在阅读AOP相关内容或者应用AOP工作时可能会遇到的词汇和短语。这些定义不是Spring特有的。
表9.1 AOP定义
Term
术语
|
Definition
定义
|
Concern A particular issue
(关注特定问题)
|
感兴趣应用的特定问题、概念、范围。例如,事务管理、持久化、日志、安全等。
|
Crosscutting Concern
(横切关注点)
|
在关注点实现中贯穿了很多类,这在面向对象(OOP)中通常很难实现和维护。
|
Aspect
(切面)
|
模块化的横切关注点,通过代码的聚合和隔离实现。
|
Join Point
(连接点)
|
在程序或者类执行时的一个点。在Spring的AOP实现中,连接点总是一个方法调用。其他的例子包括访问字段(包括实例中字段的读写),变量和异常处理。
|
Advice
(通知)
|
特定连接点所采取的动作。Spring有几种不同类型的通知,包括around、before、throws和after returning。在这几种类型的通知中,around是最强大的,在方法调用的前后都有执行一些操作的机会。之前用到的TraceInterceptor就是around类型的通知,它实现了AOP联盟的MethodInterceptor接口。通过实现下面的Spring接口可以使用其他类型的通知:
Ø MethodBeforeAdvice
Ø ThrowsAdvice
Ø AfterReturningAdvice
|
Pointcut
(切入点)
|
连接点的集合,这些连接点确认何时一个通知将会触发。切入点通常使用正则表达式或者是通配符语法。
|
Introduction
(引入)
|
添加字段或者方法到一个通知类。Spring允许你在任何通知对象上引入新的接口。例如,你可以使用引入以便让任意对象实现IsModified接口,来简化缓存。
|
Weaving
(组织)
|
装配切面来创建一个被通知对象。可以在编译期间完成(像AspectJ做的那样)也可以在运行时完成。本章后面的组织策略部分详细讨论不同的组织策略(也就是实现AOP)。
|
Interceptor
(拦截器)
|
一种AOP实现策略,对与特点的连接点,可能存在一个拦截器链。
|
AOP Proxy
(AOP代理)
|
AOP框架创建的对象,包括通知。在Spring中,一个AOP代理可能是JDK动态代理或者是CGLIB代理。
|
Target Object
(目标对象)
|
包含连接点的对象。在使用拦截的框架中,它是在拦截器链末端的对象实例。也叫做被通知对象或者被代理对象。
|
下个部分是关于切入点的,切入点是应用通知的规则。因为Spring的AOP是基于拦截器的,所以我将会用拦截器来代替通知说明问题。
切入点
切入点是AOP的重要部分。他们能让你确认在何时何地调用拦截器。在某种意义上,他们通常都像是声明式的确认,但是相比确认要验证的字段,你更应该确认要检查的方法。在上面的表格中,切入点被定义为:确认何时一个通知(拦截器)将触发的一组连接点的集合。由于Spring只支持方法调用连接点,所有在Spring中切入点也就是应用拦截器的方法的声明。
在Spring的AOP中定义切入点的最简单方法是,在context文件中使用正则表达式。下面的例子为数据处理操作定义了一个切入点。
.*save.*
.*remove.*
这个切入点告诉我们,拦截方法的方法名应该以save或者remove开始。
注意
上例的JdkRegexpMethodPointcut类,需要J2SE1.4,它内置了正则表达式支持。也可以改用Perl5RegexpMethodPointcut,它需要Jakarta ORO包(已经在MyUsers里包括了)。
大多数情况下,你不必像上面一样定义单独的切入点。Spring提供了一个advisor的类,它在同一个bean中封装了拦截器和切入点。
对正则表达式定义的切入点来说,可以使用RegexpMethodPointcutAdvisor这个advisor。下面是一个RegularExpressionPointcutAdvice的例子,它在用户信息被保存的时候触发一个NotificationInterceptor。
.*saveUser
目前,RegexpMethodPointcutAdvisor只支持Perl5的正则表达式规则,也就是说,如果你要用它,那么在你的classpath下必须要有jakarta-oro.jar 这个包。在org.springframework.aop.support包中有一个详细的切入点列表和他们对应的advisor。
组织策略
组织(weaving)是将切面应用到目标对象的过程。下面的列出了实现AOP的基本策略,按从简单到复杂排列。
注意
在这部分大多数信息都以J2EE without EJB中的信息为基础。
Ø JDK动态代理
Ø 动态字节码生成
Ø 自定义类加载器
Ø 语言扩展
这些策略在各种不同的开源AOP框架中都有实现。
注意
这些框架最出色的部分是他们对在API中使用共同的标准很感兴趣。为了支持这一想法,他们创建了AOP联盟计划,定义了大量用来实现的接口。可以阅读AOP联盟的成员列表,看看都哪些框架在这个计划中。
下面详细描述各种组织策略。
JDK
动态代理
动态代理是J2SE1.3以上版本的内置特性。它允许你凭空(on-the-fly)创建一个或更多接口的实现。动态代理内嵌在JDK中,排除了在各种环境下奇怪行为带来的风险。JDK动态代理有个限制就是它只能代理接口不能代理类。当然如果你用接口很好的设计了你的应用,那这就不是一个问题。
使用动态代理的时候还要用一些反射机制,但在J2SE1.4以上的JVM中这点性能消耗可以忽律不计。在代理接口时,Spring默认使用JDK动态代理。dynaop这个项目在代理接口时使用这个策略。
更多关于动态代理的信息,可以查看Java 2 SDK文档。
字节码动态生成
在代理类时,Spring采用字节码动态生成。CGLIB (Code Generation Library)是做这个的一个流行工具。它通过动态生成子类来拦截方法。这些生成的子类改写父类的方法,用钩子(hook)调用拦截器实现。Hibernate广泛使用CGLIB,并且已经被证明是可靠的J2EE解决方案。
一个限制是,动态生成的子类不能改写和代理final方法。
自定义类加载器
使用自定义的类加载器,可以让你通知所创建的实例。这十分强大,因为它提供了修改新操作行为的机会。Jboss AOP和AspectWerkz用的这种方式,根据在XML文件中定义的方式加载和组织类。
这种方式最主要的威胁存在于,J2EE服务器必须仔细地控制类加载层次,在一个服务器上工作很好可以在另一个服务器上就不能正常工作。
语言扩展
AspectJ是java AOP框架实现的排头兵。它包含了语言的扩展并且使用自带的编译器,而不是使用简单的策略进行切面的组织。
虽然AspectJ是非常强大和成熟的AOP实现,它的语法还是有点复杂而且也不是很直观。然而,AOP本身就不是很直观,尝试用一种新的语言去实现更显困难。这种方式的另外一个限制是学习一门新语言的学习曲线。但是,如果你想要完整的AOP功能,包括字段级的拦截,AspectJ可能会成为你最好的伙伴。在本章的末尾,介绍了集成AspectJ和Spring。
在Spring的AOP实现中,采取务实的8-2原则,它解决了最常用的部分,把更专业的部分留给其他AOP框架,而不是试图解决所有的问题。
便于应用的代理bean
像前面提到的,为了把通知应用到context文件里定义的bean上,这些bean必须要通过代理。Spring包含很多支撑类(或者说是Proxy Beans)来简化代理。首先是ProxyFactoryBean,它允许你指明要被代理的bean和要应用的拦截器。下面的例子使用了ProxyFactoryBean来创建一个业务对象的代理。
上面的例子在target属性上使用了内置bean(inner-bean)。内置bean是在代理中隐藏业务对象的一个简便方法,因此在从ApplicationContext中抽取出业务bean时,它总是值得推荐的方法。
TransactionProxyFactoryBean是最有用和最常用的代理Bean。这个bean允许你使用AOP在目标对象上声明式地定义事务。事务特性在之前只能通过EJB容器管理的事务(CMT)来获得。TransactionProxyFactoryBean的使用将会在AOP例子练习部分说明。
自动代理
Bean
前述的代理类为单个bean提供了简单的操作,但是如果你想代理多个bean或者上下文中所有的bean时怎么办?Spring提高了两个类(在org.springframework.aop.framework.autoproxy包中)简化这种处理。
第一个是BeanNameAutoProxyCreator,它允许你指明一个bean名称列表作为属性。这个属性支持字面(实际的bean名)和像*Manager的通配符。可以用interceptorNames属性设置拦截器。
<>
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
*Manager
loggingInterceptor
第二个,更通用的多bean代理创建者是DefaultAdvisorAutoProxyCreator。使用这个代理类,简单的在context文件中定义就可以。
<>
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
不像BeanNameAutoProxyCreator,你不能指明想用的拦截器。它将会检查context文件中的每一个advisor,指出他们的切入点是否可以用到其他的bean上。更多关于advisor的信息,请参阅Spring参考文档。
AOP实际应用的例子
这部分包括了几个使用AOP管理应用中横切关注点的例子,这些关注点包括事务、缓存、事件通知。
事务
指明操作应该在事务中进行,可能导致在DAO中大量的复制。使用事务时,传统的方式需要在数据操作方法中进行大量的tx.begin()和tx.commit()调用。Spring和AOP带来的一个好消息是,可以通过在一个位置声明及配置的方法加强事务管理。
可能还不了解,但是你已经在快速开始那一章,MyUsers这个应用中使用过AOP了。使用Spring的AOP和TransactionProxyFactoryBean,可以让你声明式地在userManager这个bean上指明事务属性。下面的代码显示了一个userManager重构过的版本,这里使用了事务模板bean和内置bean。
< abstract="true">
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
PROPAGATION_REQUIRED,readOnly
警告
在1.1.2版本之前,可以使用lazy-init=”true”来代替abstract=”true”,在1.1.2和之后的版本,Spring会抛出异常:java.lang.IllegalArgumentException: ‘target’ is required。1.1.1版本和之前的版本允许使用lazy-init=”true”来代替abstract=”true”属性。1.1版本添加abstract属性的目的是标记父层bean不会预先初始化。
jwebee
我的个人网站
posted on 2006-11-28 09:17
周行 阅读(2012)
评论(1) 编辑 收藏 所属分类:
IT技术