AOP是什么? http://www.jdon.com/AOPdesign/jdon-aop.htm

为什么要区分J2EE容器和J2EE应用系统?

  我们知道,J2EE应用系统只有部署在J2EE容器中才能运行,那么为什么划分为J2EE容器和J2EE应用系统? 通过对J2EE容器运行机制的分析(见我的电子教材“EJB实用原理”),我们可以发现:实际上J2EE容器分离了一般应用系统的一些通用功能,例如事务机制、安全机制以及对象池或线程池等性能优化机制。

  这些功能机制是每个应用系统几乎都需要的,因此可以从具体应用系统中分离出来,形成一个通用的框架平台,而且,这些功能机制的设计开发有一定难度,同时运行的稳定性和快速性都非常重要,必须经过长时间调试和运行经验积累而成,因此,形成了专门的J2EE容器服务器产品,如Tomcat JBoss、Websphere、WebLogic等。

  从J2EE系统划分为J2EE容器和J2EE应用系统两个方面,我们已经看到一种分散关注的思路(separation of concerns)。

分散关注

  将通用需求功能从不相关类之中分离出来;同时,能够使得很多类共享一个行为,一旦行为发生变化,不必修改很多类,只要修改这个行为就可以。

   AOP就是这种实现分散关注的编程方法,它将“关注”封装在“方面”中。

AOP是什么?

  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();

}


  缺点:

  • 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(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
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
PROPAGATION_REQUIRED
PROPAGATION_REQUIRED
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不会预先初始化。
 
更多关于声明式事务和处理相应异常回滚的信息在第十章会有所涉及。
 
 

中间层缓存

 
另一个关于横切关注点的典型例子是缓存数据。增加缓存的主要目的是改进性能,特别是当获取数据是高代价操作的时候。大多数在最后一章讨论的框架都有他们自己的缓存解决方案,然而缓存是很实际的横切关注点。
 
       由于你引入缓存的动机是提高性能,可以增加一个测试类UserManagerTest测试UserManagerImpl的性能。把下面的代码加到test/org/appfuse/service/UserManagerTest.java中。
 注意
测试中的StopWatch是一个记录任务时间的实用类。
 
public void testGetUserPerformance() {
user = new User();
user.setFirstName("Easter");
user.setLastName("Bunny");
 
user = mgr.saveUser(user);
 
String name = "getUser";
StopWatch sw = new StopWatch(name);
sw.start(name);
log.debug("Begin timing of method '" + name + "'");
 
for (int i=0; i < 200; i++) {
mgr.getUser(user.getId().toString());
}
 
sw.stop();
log.info(sw.shortSummary());
log.debug("End timing of method '" + name + "'");
}
 
       执行ant test -Dtestcase=UserManagerTest(applicationContext.xml中没有配置拦截器)会得出类似图9.2的结果。在上面的测试中,获取相同的用户花费大概10秒钟。
 
 
警告
如果使用-Dtestcase=UserManager(没有“Test”后缀),会执行上一章创建的模拟测试。这些测试从Spring分离了UserManager,他们不能证明应用了在applicationContext.xml中定义的拦截器。
 
图9.2 执行ant test -Dtestcase=UserManagerTest的结果
 
       为了提升性能,在UserManagerImpl中添加一个简单的缓存(cache)。下面是缓存的基本需求
1、当需要从数据库取得对象时,把这些对象放入缓存(在本例中用简单的HashMap)。
2、当save()delete()方法调用时,从缓存中剔除对象。
 
       在MyUsers这个应用中,你可以通过添加BaseManager (在org.appfuse.service.impl 包中)实现基本的(非AOP)缓存解决方案,所有的管理方法都从中扩展。
 
package org.appfuse.service.impl;
 
// use your IDE to organize imports
 
public class BaseManager {
// Adding this log variable will allow children to re-use it
protected final Log log = LogFactory.getLog(getClass());
protected Map cache;
 
protected void putIntoCache(String key, Object value) {
if (cache == null) {
cache = new HashMap();
}
cache.put(key, value);
}
 
protected void removeFromCache(String key) {
if (cache != null) {
cache.remove(key);
}
}
}
 
 
修改UserManagerImpl使它继承BaseManager,以使用缓存,然后在每个方法中增加对缓存的调用。下面是添加缓存调用之前UserManagerImpl的简单版本:
 
public User getUser(String userId) {
return dao.getUser(Long.valueOf(userId));
}
public User saveUser(User user) {
dao.saveUser(user);
return user;
}
public void removeUser(String userId) {
dao.removeUser(Long.valueOf(userId));
}
 
添加这些方法(注:缓存调用)之后,他们显得更详细:
 
public User getUser(String userId) {
// check cache for user
User user = (User) cache.get(userId);
if (user == null) {
// user not in cache, fetch from database
user = dao.getUser(Long.valueOf(userId));
super.putIntoCache(userId, user);
}
return user;
}
 
public User saveUser(User user) {
dao.saveUser(user);
// update cache with saved user
super.putIntoCache(String.valueOf(user.getId()), user);
return user;
}
 
public void removeUser(String userId) {
dao.removeUser(Long.valueOf(userId));
// remove user from cache
super.removeFromCache(userId);
}
 
执行ant test -Dtestcase=UserManagerTest,在图9.3中可以看见,运行时间是9秒(快了1秒)。
图9.3 执行ant test -Dtestcase=UserManagerTest的结果
 
 
虽然在像MyUsers这样的应用中添加对来自缓存的add/remove调用很简单,但是在大一些的应用中它马上就变得困难起来。不得不记住在每个管理类中添加这些方法。你不应该过分关注缓存,因为它不是应用的核心关注点。
 
       使用AOP,可以去掉这些方法调用,检查应该那些需要使用缓存的方法。另外,从代码中提出缓存,可以让你更关注业务逻辑以实现工程。
To add a caching interceptor, perform the following steps:
 
按下面的步骤增加缓存拦截器:
 
1、用下面的代码在org.appfuse.aop这个包里创建一个CacheInterceptor类。
 
package org.appfuse.aop;
 
// use your IDE to organize imports
 
public class CacheInterceptor implements MethodInterceptor {
private final Log log = LogFactory.getLog(CacheInterceptor.class);
 
public Object invoke(MethodInvocation invocation) throws Throwable {
String name = invocation.getMethod().getName();
Object returnValue;
 
// check cache before executing method
if (name.indexOf("get") > -1 && !name.endsWith("s")) {
String id = (String) invocation.getArguments()[0];
returnValue = cache.get(id);
if (returnValue == null) {
// user not in cache, proceed
returnValue = invocation.proceed();
putIntoCache(id, returnValue);
return returnValue;
} else {
//log.debug("retrieved object id '" + id + "' from cache");
}
} else {
returnValue = invocation.proceed();
 
// update cache after executing method
if (name.indexOf("save") > -1) {
Method getId = returnValue.getClass().getMethod("getId", new Class[]{});
Long id = (Long) getId.invoke(returnValue, new Object[]{});
putIntoCache(String.valueOf(id), returnValue);
} else if (name.indexOf("remove") > -1) {
String id = (String) invocation.getArguments()[0];
removeFromCache(String.valueOf(id));
}
}
return returnValue;
}
 
protected Map cache;
 
protected void putIntoCache(String key, Object value) {
if (cache == null) {
cache = new HashMap();
}
cache.put(key, value);
}
 
protected void removeFromCache(String key) {
if (cache != null) {
cache.remove(key);
}
}
}
 
2、添加cacheInterceptor这个bean到applicationContext.xml (在web/WEB-INF目录下)。
 
 
3、在userManager这个bean的配置中,用cacheInterceptor代替loggingInterceptor
 
 
 
执行ant test -Dtestcase=UserManagerTest,会发现在性能提升上颇为夸张的结果。
图9.4 执行ant test -Dtestcase=UserManagerTest的测试结果。
 
使用缓存切面(caching aspect),性能损耗减到0!性能上夸张提升的原因是,CacheInterceptor几乎总在UserManagerImpl的方法调用之前返回数据。
 
       这里的缓存例子很简单,更健壮的缓存实现可以参考Pieter Coucke的Spring AOP Cache或者using EHCache with Spring。
 
注:
Spring AOP Cache: http://www.onthoo.com/blog/programming/2004/09/spring-aop-cache.html
using EHCache with Spring: http://opensource.atlassian.com/confluence/spring/display/DISC/Caching+the+result+of+methods+using+Spring+and+EHCache
 

事件通知

 
如果正开放面向公众的web应用,你可能想了解新用户的注册。这在批准用户之前检验用户信息时特别有用。在下面的例子中,将会创建一个NotificationInterceptor并且把它配置成新用户注册时发送电子邮件。
 
1、修改UserManagerTest,以保证当新用户注册时发送消息。下面只包含了相关的部分。用下划线来区别类中新添加的代码。添加这些代码之后,运行ant test -Dtestcase=UserManagerTest 测试一下。
 
protected void setUp() throws Exception {
String[] paths = {"/WEB-INF/applicationContext*.xml"};
ctx = new ClassPathXmlApplicationContext(paths);
mgr = (UserManager) ctx.getBean("userManager");
 
// Modify the mailSender bean to use Dumbster's ports
JavaMailSenderImpl mailSender = (JavaMailSenderImpl) ctx.getBean("mailSender");
mailSender.setPort(2525);
}
 
public void testAddAndRemoveUser() throws Exception {
user = new User();
user.setFirstName("Easter");
user.setLastName("Bunny");
 
// setup a simple mail server using Dumbster
SimpleSmtpServer server = SimpleSmtpServer.start(2525);
 
user = mgr.saveUser(user);
 
server.stop();
assertEquals(1, server.getReceievedEmailSize()); // spelling is correct
SmtpMessage sentMessage = (SmtpMessage) server.getReceivedEmail().next();
assertTrue(sentMessage.getBody().indexOf("Easter Bunny") != -1);
log.debug(sentMessage);
 
assertTrue(user.getId() != null);
}
 
2、在src/org/appfuse/aop下,用下面的代码新建NotificationInterceptor
 
package org.appfuse.aop;
 
// organize imports using your IDE
 
public class NotificationInterceptor implements MethodInterceptor {
private final Log log = LogFactory.getLog(NotificationInterceptor.class);
private MailSender mailSender;
private SimpleMailMessage message;
 
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
 
public void setMessage(SimpleMailMessage message) {
this.message = message;
}
 
public Object invoke(MethodInvocation invocation) throws Throwable {
User user = (User) invocation.getArguments()[0];
if (user.getId() == null) {
if (log.isDebugEnabled()) {
log.debug("detected new user...");
}
Object returnValue = invocation.proceed();
StringBuffer sb = new StringBuffer(100);
sb.append("A new account has been created for " + user.getFullName());
sb.append("./n/nView this users information at:/n/t ");
sb.append("http://localhost:8080/myusers/editUser.html?id=" + user.getId());
message.setText(sb.toString());
 
mailSender.send(message);
}
 
return user;
}
}
 
 
3、在web/WEB-INF/applicationContext.xml中配置这个拦截器和它要用到的bean。
 
警告
确信修改下面accountMessage这个bean里属性“to”的e-mail地址,如果不修改测试会失败。
 
 
localhost
 
]]>
]]>
MyUsers Account Information
 
 
4、用RegexpMethodPointcutAdvisor这个类在将要触发的通知(advisor)中配置拦截器。
 
.*saveUser
 
 
5、把notificationAdvisor添加到userManager这个bean中的preInterceptors属性中。建议注释掉其他的例子。
 
 
 
6、保存所有文件,运行ant test -Dtestcase=UserManagerTest。控制台输出应该和图9.5类似。
 
图9.5 运行ant test -Dtestcase=UserManagerTest的测试结果
 
 
经过像这样的调整,你可以很容易地添加基于值和条件的通知。
 
 
       这些例子很好的介绍了AOP,以及怎么在你的应用中使用她。其他可能在应用中用到AOP的例子有:
Ø        资源池
Ø        线程
Ø        XSLT 缓冲
Ø        验证和授权
 
 
实现这些的例子和代码可以在《AspectJ in Action》中找到。
 
AOP非常强大,初学者应该很小心的在他们的代码中引入AOP。这有两条关于如何开始学习AOP以及继续深入的建议:
1、通过代码学习:从书本和文章中复制粘贴代码。
2、从非正式工程开始:最好不要一次应用所有的AOP特性。AOP有一个良好的增长曲线,你可以在开发中开始(例如,日志/追踪这个例子),然后在已存档的项目中检验,或者用测试来帮助学习。一旦熟悉了,就可以在工程中使用AOP了。
 
AOP可能不适合所有人,但在创建模块和可维护的java应用中还是有其位置。
 

总结

 
AOP已经有几年了,但直到最近才在java舞台上变得如此流行。这应该归功于诸多开源的AOP实现。在本书写书时,这些框架有AspectJ,AspectWerkz,dynaop,JAC,JBoss AOP和Spring AOP。
 
Spring为AOP提供了一组简单易用的API,并且和其他AOP框架集成的很好。到Spring的1.1版本,她提供了与AspectJ强大的集成功能。Spring与AspectWerkz的集成也不错。在所有这些集成中,Spring的IOC容器配置和管理那些创建出来的切面的生命周期。
 
本章中,学到了从切面到切入点到组织这些有关AOP的不同内容。在空洞地定义了AOP中的概念后,探究了不同的实现策略。在前述的AOP框架中,JDK动态代理和字节码处理是最流行的组织AOP的策略。AspectJ是最成熟和久经考验的AOP实现,她为集成切面和类提供了自己的语言和编译器。最后在MyUsers这个应用中学习了如何用切面和通知来实现日志、缓存、事务、事件通知。
 
       那么Spring的AOP框架有何特点?根据她的路线图(roadmap),大多数的目标已经在1.1这个发布版本中实现,包括性能提升和与AspectJ的集成。缓存和安全一般实现Spring AOP Cache和Acegi Security System for Spring是很好的例子。随着更多的用户熟悉并开始在他们的应用中使用AOP,资源池也将得到广泛应用。
 
注:
powerful integration with AspectJ: http://www.springframework.org/docs/reference/ch06.html
integrates nicely with AspectWerkz : http://blogs.codehaus.org/people/jboner/archives/000826_spring_and_aspectwerkz_a_happy_marriage.html
AspectJ: http://eclipse.org/aspectj/
AspectWerkz: http://aspectwerkz.codehaus.org/
dynaop: https://dynaop.dev.java.net/
JAC: http://jac.objectweb.org/
JBoss AOP: http://www.jboss.org/products/aop
Spring AOP roadmap: http://www.springframework.org/docs/reference/aop.html#d0e3961
Spring AOP Cache: http://www.onthoo.com/blog/programming/2004/09/spring-aop-cache.html
Acegi Security System for Spring: http://acegisecurity.sourceforge.net/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值