定义和概念
AOP像大多数编程范式一样,有她自己的词汇表。下表定义了许多在阅读AOP相关内容或者应用AOP工作时可能会遇到的词汇和短语。这些定义不是Spring特有的。
AOP定义:
下个部分是关于切入点的,切入点是应用通知的规则。因为Spring的AOP是基于拦截器的,所以我将会用拦截器来代替通知说明问题。
切入点是AOP的重要部分。他们能让你确认在何时何地调用拦截器。在某种意义上,他们通常都像是声明式的确认,但是相比确认要验证的字段,你更应该确认要检 查的方法。在上面的表格中,切入点被定义为:确认何时一个通知(拦截器)将触发的一组连接点的集合。由于Spring只支持方法调用连接点,所有在 Spring中切入点也就是应用拦截器的方法的声明。
在Spring的AOP中定义切入点的最简单方法是,在context文件中使用正则表达式。下面的例子为数据处理操作定义了一个切入点。
<bean id="dataManipulationPointcut" class="org.springframework.aop.support. JdkRegexpMethodPointcut"></bean> <property name="patterns"></property> <list></list> <value></value>.*save.* <value></value>.*remove.* |
这个切入点告诉我们,拦截方法的方法名应该以save或者remove开始。
注意
上例的JdkRegexpMethodPointcut类,需要J2SE1.4,它内置了正则表达式支持。也可以改用Perl5RegexpMethodPointcut,它需要Jakarta ORO包(已经在MyUsers里包括了)。
大多数情况下,你不必像上面一样定义单独的切入点。Spring提供了一个advisor的类,它在同一个bean中封装了拦截器和切入点。
对正则表达式定义的切入点来说,可以使用RegexpMethodPointcutAdvisor这个advisor。下面是一个RegularExpressionPointcutAdvice的例子,它在用户信息被保存的时候触发一个NotificationInterceptor。
<bean id="notificationAdvisor" class="org.springframework.aop.support. RegexpMethodPointcutAdvisor"></bean> <property name="advice"></property> <ref bean="notificationInterceptor"></ref> <property name="pattern"></property> <value></value>.*saveUser |
目前,RegexpMethodPointcutAdvisor只支持Perl5的正则表达式规则,也就是说,如果你要用它,那么在你的classpath下必须要有jakarta-oro.jar 这个包。在org.springframework.aop.support包中有一个详细的切入点列表和他们对应的advisor。
组织(weaving)是将切面应用到目标对象的过程。下面的列出了实现AOP的基本策略,按从简单到复杂排列。
注意
在这部分大多数信息都以J2EE without EJB中的信息为基础。
Ø JDK动态代理
Ø 动态字节码生成
Ø 自定义类加载器
Ø 语言扩展
这些策略在各种不同的开源AOP框架中都有实现。
注意
这些框架最出色的部分是他们对在API中使用共同的标准很感兴趣。为了支持这一想法,他们创建了AOP联盟计划,定义了大量用来实现的接口。可以阅读AOP联盟的成员列表,看看都哪些框架在这个计划中。
下面详细描述各种组织策略。
动 态代理是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框架,而不是试图解决所有的问题。
像 前面提到的,为了把通知应用到context文件里定义的bean上,这些bean必须要通过代理。Spring包含很多支撑类(或者说是Proxy Beans)来简化代理。首先是ProxyFactoryBean,它允许你指明要被代理的bean和要应用的拦截器。下面的例子使用了 ProxyFactoryBean来创建一个业务对象的代理。
<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean"></bean> <property name="target"></property> <bean class="org.appfuse.service.BusinessObject"></bean> <property name="interceptorNames"></property> <list></list> <ref bean="loggingInterceptor"></ref> |
上面的例子在target属性上使用了内置bean(inner-bean)。内置bean是在代理中隐藏业务对象的一个简便方法,因此在从ApplicationContext中抽取出业务bean时,它总是值得推荐的方法。
TransactionProxyFactoryBean 是最有用和最常用的代理Bean。这个bean允许你使用AOP在目标对象上声明式地定义事务。事务特性在之前只能通过EJB容器管理的事务(CMT)来 获得。TransactionProxyFactoryBean的使用将会在AOP例子练习部分说明。
前述的代理类为单个bean提供了简单的操作,但是如果你想代理多个bean或者上下文中所有的bean时怎么办?Spring提高了两个类(在org.springframework.aop.framework.autoproxy包中)简化这种处理。
第一个是BeanNameAutoProxyCreator,它允许你指明一个bean名称列表作为属性。这个属性支持字面(实际的bean名)和像*Manager的通配符。可以用interceptorNames属性设置拦截器。
<bean font="" id="managerProxyCreator"></bean> class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"></property> <value></value>*Manager <property name="interceptorNames"></property> <list></list> <value></value>loggingInterceptor |
第二个,更通用的多bean代理创建者是DefaultAdvisorAutoProxyCreator。使用这个代理类,简单的在context文件中定义就可以。
<bean font="" id="autoProxyCreator"></bean> class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> |
不像BeanNameAutoProxyCreator,你不能指明想用的拦截器。它将会检查context文件中的每一个advisor,指出他们的切入点是否可以用到其他的bean上。更多关于advisor的信息,请参阅Spring参考文档。