Spring对AOP的支持1

首先让我们从一些重要的AOP概念和术语开始。这些术语不是Spring特有的。不过AOP术语并不是特别的直观,如果Spring使用自己的术语,将会变得更加令人困惑。

· 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式或者基于@Aspect注解的方式来实现。

· 连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。

· 通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。

· 切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。

· 引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。

· 目标对象(Target Object): 被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。

· AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

· 织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

通知类型:

· 前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。

· 后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

· 异常通知(After throwing advice):在方法抛出异常退出时执行的通知。

· 最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

· 环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。

环绕通知是最常用的通知类型。和AspectJ一样,Spring提供所有类型的通知,我们推荐你使用尽可能简单的通知类型来实现需要的功能。例如,如果你只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,你不需要在JoinPoint上调用用于环绕通知的proceed()方法,就不会有调用的问题。在Spring 2.0中,所有的通知参数都是静态类型,因此你可以使用合适的类型(例如一个方法执行后的返回值类型)作为通知的参数而不是使用Object数组。 通过切入点匹配连接点的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。 切入点使得通知可以独立对应到面向对象的层次结构中。例如,一个提供声明式事务管理的环绕通知可以被应用到一组横跨多个对象的方法上(例如服务层的所有业务操作)。

4.2 创建通知

我们通过一个简单的例子来理解AOP。

代码清单1

public class Foo {

public void printName(String name){

System.out.println("The Name is : " + name);

}

public void printAge(String age){

System.out.println("The Age is : " + age);

}

}

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class FooBeforeAdvice implements MethodBeforeAdvice{

@Override

public void before(Method method, Object[] args, Object object)

throws Throwable {

//打印出类的名称

System.out.print("Class Name is " +

object.getClass().getSimpleName() + " ");

//打印出参数的值

System.out.println("arg is "+(String)args[0] + " ");

}

}

import org.springframework.aop.framework.ProxyFactory;

public class Test {

public static void main(String[] args) {

Foo foo = new Foo();

FooBeforeAdvice advice = new FooBeforeAdvice();

//Spring提供的代理工厂

ProxyFactory pf = new ProxyFactory();

//设置代理目标

pf.setTarget(foo);

//为代理目标添加增强

pf.addAdvice(advice);

//生成代理实例

Foo proxy = (Foo)pf.getProxy();

proxy.printName("Tony");

proxy.printAge("27");

}

}

控制台输出信息

Class Name is Foo arg is Tony

The Name is : Tony

Class Name is Foo arg is 27

The Age is : 27

代码清单1中我们的Foo.java类有两个方法,我们创建了FooBeforeAdvice继承前置增强接口MethodBeforeAdvice,在before方法中我同通过参数object获得代理的对象信息,通过参数args获得代理方法的参数值,最后我们通过ProxyFactory将目标类和增强类融合,生成了代理实例并调用代理实例的方法。而在Spring中又如何配置AOP呢?

4.3前置增强

代码清单2

<bean id="foo" class="com.tony.test.Foo" scope="singleton"/>

<bean id="fooBeforeAdvice" class="com.tony.test.FooBeforeAdvice"/>

<bean id="proxy"

class="org.springframework.aop.framework.ProxyFactoryBean">

<property name="interceptorNames"><!-- 指定增强 -->

<list>

<value>fooBeforeAdvice</value>

</list>

</property>

<!-- 指定目标代理Bean -->

<property name="target" ref="foo"/>

</bean>

import org.springframework.beans.factory.BeanFactory;

import org.springframework.beans.factory.xml.XmlBeanFactory;

import org.springframework.core.io.ClassPathResource;

public class Test {

public static void main(String[] args) {

ClassPathResource resource = new

ClassPathResource("spring-config-beans.xml");

//实例化BeanFactory

BeanFactory factory = new XmlBeanFactory(resource);

Foo foo = (Foo)factory.getBean("proxy");

foo.printName("Tony");

foo.printAge("27");

}

}

控制台信息

Class Name is Foo arg is Tony

The Name is : Tony

Class Name is Foo arg is 27

The Age is : 27

代码清单2中我们只需修改Spring的配置文件,将增强类和目标类装配起来就可以了,我们在Test.java就像正常的调用Foo一样,可是控制台取已经是被拦截了。

4.4后置增强

代码清单1

public class Foo {

public String printName(String name){

return "The Name is : " + name;

}

public String printAge(String age){

return "The Age is : " + age;

}

}

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

public class FooAfterAdvice implements AfterReturningAdvice{

@Override//参数分别是代理方法的返回值,被代理的方法,方法的参数,代理对象

public void afterReturning(Object returnValue, Method method,

Object[] args,Object object) throws Throwable {

//打印出类的名称

System.out.print("Class Name is " +

object.getClass().getSimpleName() + " ");

//打印出参数的值

System.out.println("arg is "+

(String)args[0] + " ");

//打印出返回值图

System.out.println("ReturnValue is " +

returnValue.toString());

}

}

<bean id="foo" class="com.tony.test.Foo" scope="singleton"/>

<bean id="fooAfterAdvice" class="com.tony.test.FooAfterAdvice"/>

<bean id="proxy"

class="org.springframework.aop.framework.ProxyFactoryBean">

<property name="interceptorNames"><!-- 指定增强 -->

<list>

<value>fooAfterAdvice</value>

</list>

</property>

<!-- 指定目标代理Bean -->

<property name="target" ref="foo"/>

</bean>

控制台输出

Class Name is Foo arg is Tony

ReturnValue is The Name is : Tony

Class Name is Foo arg is 27

ReturnValue is The Age is : 27

代码清单中我们修改了Foo.java类两个方法都返回String类型的参数,定义了一个FooAfterAdvice.java类这个类实现了AfterReturningAdvice接口,分别打印出被代理类的名称,方法参数值和返回值。我们查看控制台输出的信息,发现在目标方法执行结束后还打印出增强类输出的信息。

4.5环绕增强

代码清单1

public class Foo {

public void printName(String name){

System.out.println("The Name is : " + name);

}

public void printAge(String age){

System.out.println("The Age is : " + age);

}

}

import org.aopalliance.intercept.MethodInterceptor;

import org.aopalliance.intercept.MethodInvocation;

public class FooInterceptor implements MethodInterceptor{

@Override

public Object invoke(MethodInvocation invocation) throws Throwable {

//目标方法入参

Object[] args = invocation.getArguments();

//打印方法入参

System.out.println("准备执行目标方法");

//执行目标方法

Object obj = invocation.proceed();

System.out.println("目标方法执行完成");

//返回方法返回值

return obj;

}

}

<bean id="foo" class="com.tony.test.Foo" scope="singleton"/>

<bean id="fooInterceptor" class="com.tony.test.FooInterceptor"/>

<bean id="fooAfterAdvice" class="com.tony.test.FooAfterAdvice"/>

<bean id="proxy"

class="org.springframework.aop.framework.ProxyFactoryBean">

<property name="interceptorNames"><!-- 指定增强 -->

<list>

<value>fooInterceptor</value>

</list>

</property>

<!-- 指定目标代理Bean -->

<property name="target" ref="foo"/>

</bean>

控制台输出

准备执行目标方法

The Name is : Tony

目标方法执行完成

准备执行目标方法

The Age is : 27

目标方法执行完成

代码清单1中我们定义了FooInterceptor.java实现MethodInterceptor接口对目标方法进行环绕增强,查看控制台我们就能看出FooInterceptor在每次方法的执行前后都进行了处理。

  • 方面(Aspect): 一个关注点的模块化,这个关注点实现可能 另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。
  • 连接点(Joinpoint): 程序执行过程中明确的点,如方法的调 用或特定的异常被抛出。
  • 通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类 型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架 包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器 链。
  • 切入点(Pointcut): 指定一个通知将被引发的一系列连接点 的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。
  • 引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。
  • 目标对象(Target Object): 包含连接点的对象。也被称作 被通知被代理对象。
  • AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
  • 织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时 完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样, 在运行时完成织入。

各种通知类型包括:

  • Around通知: 包围一个连接点的通知,如方法调用。这是最 强大的通知。Aroud通知在方法调用前后完成自定义的行为。它们负责选择继续执行连接点或通过 返回它们自己的返回值或抛出异常来短路执行。
  • Before通知: 在一个连接点之前执行的通知,但这个通知 不能阻止连接点前的执行(除非它抛出一个异常)。
  • Throws通知: 在方法抛出异常时执行的通知。Spring提供 强类型的Throws通知,因此你可以书写代码捕获感兴趣的异常(和它的子类),不需要从Throwable 或Exception强制类型转换。
  • After returning通知: 在连接点正常完成后执行的通知, 例如,一个方法正常返回,没有抛出异常。

Around通知是最通用的通知类型。大部分基于拦截的AOP框架,如Nanning和JBoss4,只提供 Around通知。

如同AspectJ,Spring提供所有类型的通知,我们推荐你使用最为合适的通知类型来实现需要的行为。例如,如果只是需要用一个方法的返回值来更新缓存,你最好实现一个after returning 通知而不是around通知,虽然around通知也能完成同样的事情。使用最合适的通知类型使编程模型变得简单,并能减少潜在错误。例如你不需要调用在around通知中所需使用的的MethodInvocation的 proceed()方法,因此就调用失败。

切入点的概念是AOP的关键,使AOP区别于其它使用拦截的技术。切入点使通知独立于OO的 层次选定目标。例如,提供声明式事务管理的around通知可以被应用到跨越多个对象的一组方法上。因此切入点构成了AOP的结构要素。

5.1.2.Spring AOP的功能

Spring AOP用纯Java实现。它不需要特别的编译过程。Spring AOP不需要控制类装载器层次,因此适用于J2EE web容器或应用服务器。

Spring目前支持拦截方法调用。成员变量拦截器没有实现,虽然加入成员变量拦截器支持并不破坏 Spring AOP核心API。

成员变量拦截器在违反OO封装原则方面存在争论。我们不认为这在应用程序开发中是明智的。如果你需要使用成员变量拦截器,考虑使用AspectJ。

Spring提供代表切入点或各种通知类型的类。Spring使用术语advisor来 表示代表方面的对象,它包含一个通知和一个指定特定连接点的切入点。

各种通知类型有MethodInterceptor (来自AOP联盟的拦截器API)和定义在org.springframework.aop包中的 通知接口。所有通知必须实现org.aopalliance.aop.Advice标签接口。 取出就可使用的通知有 MethodInterceptor、 ThrowsAdvice、 BeforeAdvice和 AfterReturningAdvice。我们将在下面详细讨论这些通知类型。

Spring实现了AOP联盟的拦截器接口( http://www.sourceforge.net/projects/aopalliance). Around通知必须实现AOP联盟的org.aopalliance.intercept.MethodInterceptor 接口。这个接口的实现可以运行在Spring或其他AOP联盟兼容的实现中。目前JAC实现了AOP联盟的接 口,Nanning和Dynaop可能在2004年早期实现。

Spring实现AOP的途径不同于其他大部分AOP框架。它的目标不是提供及其完善的AOP实现( 虽然Spring AOP非常强大);而是提供一个和Spring IoC紧密整合的AOP实现,帮助解决企业应用 中的常见问题。因此,例如Spring AOP的功能通常是和Spring IoC容器联合使用的。AOP通知是用普通 的bean定义语法来定义的(虽然可以使用"autoproxying"功能);通知和切入点本身由Spring IoC 管理:这是一个重要的其他AOP实现的区别。有些事使用Spring AOP是无法容易或高效地实现,比如通知 非常细粒度的对象。这种情况AspectJ可能是最合适的选择。但是,我们的经验是Spring针对J2EE应 用中大部分能用AOP解决的问题提供了一个优秀的解决方案。

5.1.3.Spring中AOP代理

Spring默认使用JDK动态代理实现AOP代理。这使得任何接口或 接口的集合能够被代理。

Spring也可以是CGLIB代理。这可以代理类,而不是接口。如果业务对象没有实现一个接口, CGLIB被默认使用。但是作为一针对接口编程而不是类编程良好实践,业务对象通常实现一个或多个业务接口。

也可以强制使用CGLIB:我们将在下面讨论,并且会解释为什么你会要这么做。

Spring 1.0后,Spring可能提供额外的AOP代理的类型,包括完全生成的类。这将不会影响编程模型。

5.2.Spring的切入点

让我们看看Spring如何处理切入点这个重要的概念。

5.2.1.概念

Spring的切入点模型能够使切入点独立于通知类型被重用。同样的切入点有可能接受不同的 通知。

org.springframework.aop.Pointcut 接口是重要的接口, 用来指定通知到特定的类和方法目标。完整的接口定义如下:

public interface Pointcut {

ClassFilter getClassFilter();

MethodMatcher getMethodMatcher();

}

将Pointcut接口分成两个部分有利于重用类和方法的匹配部分,并且组合细粒度的 操作(如和另一个方法匹配器执行一个”并“的操作)。

ClassFilter接口被用来将切入点限制到一个给定的目标类的集合。如果matches()永远返回true,所有的目标类都将被匹配。

public interface ClassFilter {

boolean matches(Class clazz);

}

MethodMatcher接口通常更加重要。完整的接口如下:

public interface MethodMatcher {

boolean matches(Method m, Class targetClass);

boolean isRuntime();

boolean matches(Method m, Class targetClass, Object[] args);

}

matches(Method, Class) 方法被用来测试这个切入点是否匹 配目标类的给定方法。这个测试可以在AOP代理创建的时候执行,避免在所有方法调用时都需要进行测试。如果2个参数的匹配方法对某个方法返回true,并且MethodMatcher的 isRuntime()也返回true,那么3个参数的匹配方法将在每次方法调用的时候被调用。这使 切入点能够在目标通知被执行之前立即查看传递给方法调用的参数。

大部分MethodMatcher都是静态的,意味着isRuntime()方法 返回false。这种情况下3个参数的匹配方法永远不会被调用。

如果可能,尽量使切入点是静态的,使当AOP代理被创建时,AOP框架能够缓存切入点的 测试结果。

5.2.2.切入点的运算

Spring支持的切入点的运算有: 值得注意的是

并表示只要任何一个切入点匹配的方法。

交表示两个切入点都要匹配的方法。

并通常比较有用。

切入点可以用org.springframework.aop.support.Pointcuts 类的静态方法来组合,或者使用同一个包中的ComposablePointcut类。

5.2.3.实用切入点实现

Spring提供几个实用的切入点实现。一些可以直接使用。另一些需要子类化来实现应用相关的切入点。

5.2.3.1.静态切入点

静态切入点只基于方法和目标类,而不考虑方法的参数。静态切入点足够满足大多数情况的使用。Spring可以只在方法第一次被调用的时候计算静态切入点,不需要在每次方法调用 的时候计算。

让我们看一下Spring提供的一些静态切入点的实现。

5.2.3.1.1.正则表达式切入点

一个很显然的指定静态切入点的方法是正则表达式。除了Spring以外,其它的AOP框架也实 现了这一点。org.springframework.aop.support.RegexpMethodPointcut 是一个通用的正则表达式切入点,它使用Perl 5的正则表达式的语法。

使用这个类你可以定义一个模式的列表。如果任何一个匹配,那个切入点将被计算成 true。(所以结果相当于是这些切入点的并集)。

用法如下:

<bean id="settersAndAbsquatulatePointcut"

class="org.springframework.aop.support.RegexpMethodPointcut">

<property name="patterns">

<list>

<value>.*get.*</value>

<value>.*absquatulate</value>

</list>

</property>

</bean>

RegexpMethodPointcut一个实用子类, RegexpMethodPointcutAdvisor, 允许我们同时引用一个通知。 (记住通知可以是拦截器,before通知,throws通知等等。)这简化了bean的装配,因为一个bean 可以同时当作切入点和通知,如下所示:

<bean id="settersAndAbsquatulateAdvisor"

class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">

<property name="interceptor">

<ref local="beanNameOfAopAllianceInterceptor"/>

</property>

<property name="patterns">

<list>

<value>.*get.*</value>

<value>.*absquatulate</value>

</list>

</property>

</bean>

RegexpMethodPointcutAdvisor可以用于任何通知类型。

RegexpMethodPointcut类需要Jakarta ORO正则表达式包。

5.2.3.1.2.属性驱动的切入点

一类重要的静态切入点是元数据驱动的切入点。 它使用元数据属性的值:典型地,使用源代码级元数据。

5.2.3.2.动态切入点

动态切入点的演算代价比静态切入点高的多。它们不仅考虑静态信息,还要考虑方法的 参数。这意味着它们必须在每次方法调用的时候都被计算;并且不能缓存结果 ,因为参数是变化的。

这个主要的例子就是控制流切入点。

5.2.3.2.1.控制流切入点

Spring的控制流切入点概念上和AspectJ的cflow 切入点一致,虽然没有其那么强大(当前没有办法指定一个切入点在另一个切入点后执行)。 一个控制流切入点匹配当前的调用栈。例如,连接点被 com.mycompany.web包或者 SomeCaller类中一个方法调用的时候,触发该切入点。控制流切入点的实现类是 org.springframework.aop.support.ControlFlowPointcut。

注意

控制流切入点是动态切入点中计算代价最高的。Java 1.4中, 它的运行开销是其他动态切入点的5倍。在Java 1.3中则超过10倍。

5.2.4.切入点超类

Spring提供非常实用的切入点的超类帮助你实现你自己的切入点。

因为静态切入点非常实用,你很可能子类化StaticMethodMatcherPointcut,如下所示。 这只需要实现一个抽象方法(虽然可以改写其它的方法来自定义行为)。

class TestStaticPointcut extends StaticMethodMatcherPointcut {

public boolean matches(Method m, Class targetClass) {

// return true if custom criteria match

}

}

当然也有动态切入点的超类。

Spring 1.0 RC2或以上版本,自定义切入点可以用于任何类型的通知。

5.2.5.自定义切入点

因为Spring中的切入点是Java类,而不是语言特性(如AspectJ),因此可以定义自定义切入点, 无论静态还是动态。但是,没有直接支持用AspectJ语法书写的复杂的切入点表达式。不过, Spring的自定义切入点也可以任意的复杂。

后续版本的Spring可能象JA一样C提供”语义切入点“的支持:例如,“所有更改目标对象 实例变量的方法”。

5.3.Spring的通知类型

现在让我们看看Spring AOP是如何处理通知的。

5.3.1.通知的生命周期

Spring的通知可以跨越多个被通知对象共享,或者每个被通知对象有自己的通知。这分别对应 per-classper-instance 通知。

Per-class通知使用最为广泛。它适合于通用的通知,如事务adisor。它们不依赖被代理 的对象的状态,也不添加新的状态。它们仅仅作用于方法和方法的参数。

Per-instance通知适合于导入,来支持混入(mixin)。在这种情况下,通知添加状态到 被代理的对象。

可以在同一个AOP代理中混合使用共享和per-instance通知。

5.3.2.Spring中通知类型

Spring提供几种现成的通知类型并可扩展提供任意的通知类型。让我们看看基本概念和标准的通知类型。

5.3.2.1.Interception around advice

Spring中最基本的通知类型是interception around advice .

Spring使用方法拦截器的around通知是和AOP联盟接口兼容的。实现around通知的 类需要实现接口MethodInterceptor:

public interface MethodInterceptor extends Interceptor {

Object invoke(MethodInvocation invocation) throws Throwable;

}

invoke()方法的MethodInvocation 参数暴露将被调用的方法、目标连接点、AOP代理和传递给被调用方法的参数。 invoke()方法应该返回调用的结果:连接点的返回值。

一个简单的MethodInterceptor实现看起来如下:

public class DebugInterceptor implements MethodInterceptor {

public Object invoke(MethodInvocation invocation) throws Throwable {

System.out.println("Before: invocation=[" + invocation + "]");

Object rval = invocation.proceed();

System.out.println("Invocation returned");

return rval;

}

}

注意MethodInvocation的proceed()方法的调用。这个调用会应用到目标连接点的拦截器链中的每一个拦截器。大部分拦截器会调用这个方法,并返回它的返回值。但是, 一个MethodInterceptor,和任何around通知一样,可以返回不同的值或者抛出一个异常,而 不调用proceed方法。但是,没有好的原因你要这么做。

MethodInterceptor提供了和其他AOP联盟的兼容实现的交互能力。这一节下面 要讨论的其他的通知类型实现了AOP公共的概念,但是以Spring特定的方式。虽然使用特定 通知类型有很多优点,但如果你可能需要在其他的AOP框架中使用,请坚持使用MethodInterceptor around通知类型。注意目前切入点不能和其它框架交互操作,并且AOP联盟目前也没有定义切入 点接口。

5.3.2.2.Before通知

Before通知是一种简单的通知类型。 这个通知不需要一个MethodInvocation对象,因为它只在进入一个方法前被调用。

Before通知的主要优点是它不需要调用proceed() 方法, 因此没有无意中忘掉继续执行拦截器链的可能性。

MethodBeforeAdvice接口如下所示。 (Spring的API设计允许成员变量的before通知,虽然一般的对象都可以应用成员变量拦截,但Spring 有可能永远不会实现它)。

public interface MethodBeforeAdvice extends BeforeAdvice {

void before(Method m, Object[] args, Object target) throws Throwable;

}

注意返回类型是void。 Before通知可以在连接点执行之前 插入自定义的行为,但是不能改变返回值。如果一个before通知抛出一个异常,这将中断拦截器 链的进一步执行。这个异常将沿着拦截器链后退着向上传播。如果这个异常是unchecked的,或者 出现在被调用的方法的签名中,它将会被直接传递给客户代码;否则,它将被AOP代理包装到一个unchecked 的异常里。

下面是Spring中一个before通知的例子,这个例子计数所有正常返回的方法:

public class CountingBeforeAdvice implements MethodBeforeAdvice {

private int count;

public void before(Method m, Object[] args, Object target) throws Throwable {

++count;

}

public int getCount() {

return count;

}

}

Before通知可以被用于任何类型的切入点。

5.3.2.3.Throws通知

如果连接点抛出异常,Throws通知 在连接点返回后被调用。Spring提供强类型的throws通知。注意这意味着 org.springframework.aop.ThrowsAdvice接口不包含任何方法:它是一个标记接口,标识给定的对象实现了一个或多个强类型的throws通知方法。这些方法形式 如下:

afterThrowing([Method], [args], [target], subclassOfThrowable)

只有最后一个参数是必需的。这样从一个参数到四个参数,依赖于通知是否对方法和方法 的参数感兴趣。下面是throws通知的例子。

如果抛出RemoteException异常(包括子类), 这个通知会被调用

public class RemoteThrowsAdvice implements ThrowsAdvice {

public void afterThrowing(RemoteException ex) throws Throwable {

// Do something with remote exception

}

}

如果抛出ServletException异常, 下面的通知会被调用。和上面的通知不一样,它声明了四个参数,所以它可以访问被调用的方法,方法的参数和目标对象:

public static class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {

// Do something will all arguments

}

}

最后一个例子演示了如何在一个类中使用两个方法来同时处理 RemoteException和ServletException 异常。任意个数的throws方法可以被组合在一个类中。

public static class CombinedThrowsAdvice implements ThrowsAdvice {

public void afterThrowing(RemoteException ex) throws Throwable {

// Do something with remote exception

}

public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {

// Do something will all arguments

}

}

Throws通知可被用于任何类型的切入点。

5.3.2.4.After Returning通知

Spring中的after returning通知必须实现 org.springframework.aop.AfterReturningAdvice 接口,如下所示:

public interface AfterReturningAdvice extends Advice {

void afterReturning(Object returnValue, Method m, Object[] args, Object target)

throws Throwable;

}

After returning通知可以访问返回值(不能改变)、被调用的方法、方法的参数和目标对象。

下面的after returning通知统计所有成功的没有抛出异常的方法调用:

public class CountingAfterReturningAdvice implements AfterReturningAdvice {

private int count;

public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable {

++count;

}

public int getCount() {

return count;

}

}

这方法不改变执行路径。如果它抛出一个异常,这个异常而不是返回值将被沿着拦截器链向上抛出。

After returning通知可被用于任何类型的切入点。

5.3.2.5.Introduction通知

Spring将introduction通知看作一种特殊类型的拦截通知。

Introduction需要实现IntroductionAdvisor, 和IntroductionInterceptor接口:

public interface IntroductionInterceptor extends MethodInterceptor {

boolean implementsInterface(Class intf);

}

继承自AOP联盟MethodInterceptor接口的 invoke()方法必须实现导入:也就是说,如果被调用的方法是在 导入的接口中,导入拦截器负责处理这个方法调用,它不能调用proceed() 方法。

Introduction通知不能被用于任何切入点,因为它只能作用于类层次上,而不是方法。你可以只用InterceptionIntroductionAdvisor来实现导入通知,它有下面的方法:

public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor {

ClassFilter getClassFilter();

IntroductionInterceptor getIntroductionInterceptor();

Class[] getInterfaces();

}

这里没有MethodMatcher,因此也没有和导入通知关联的 切入点。只有类过滤是合乎逻辑的。

getInterfaces()方法返回advisor导入的接口。

让我们看看一个来自Spring测试套件中的简单例子。我们假设想要导入下面的接口到一个 或者多个对象中:

public interface Lockable {

void lock();

void unlock();

boolean locked();

}

这个例子演示了一个mixin。我们想要能够 将被通知对象类型转换为Lockable,不管它们的类型,并且调用lock和unlock方法。如果我们调用 lock()方法,我们希望所有setter方法抛出LockedException异常。这样我们能添加一个方面使的对象不可变,而它们不需要知道这一点:这是一个很好的AOP例 子。

首先,我们需要一个做大量转化的IntroductionInterceptor。 在这里,我们继承 org.springframework.aop.support.DelegatingIntroductionInterceptor 实用类。我们可以直接实现IntroductionInterceptor接口,但是大多数情况下 DelegatingIntroductionInterceptor是最合适的。

DelegatingIntroductionInterceptor的设计是将导入 委托到真正实现导入接口的接口,隐藏完成这些工作的拦截器。委托可以使用构造方法参数 设置到任何对象中;默认的委托就是自己(当无参数的构造方法被使用时)。这样在下面的例子里,委托是DelegatingIntroductionInterceptor的子类 LockMixin。给定一个委托(默认是自身)的 DelegatingIntroductionInterceptor实例寻找被这个委托(而不 是IntroductionInterceptor)实现的所有接口,并支持它们中任何一个导入。子类如 LockMixin也可能调用suppressInterflace(Class intf) 方法隐藏不应暴露的接口。然而,不管IntroductionInterceptor 准备支持多少接口,IntroductionAdvisor将控制哪个接口将被实际 暴露。一个导入的接口将隐藏目标的同一个接口的所有实现。

这样,LockMixin继承DelegatingIntroductionInterceptor 并自己实现Lockable。父类自动选择支持导入的Lockable,所以我们不需要指定它。用这种方法我们可以导入任意数量的接口。

注意locked实例变量的使用。这有效地添加额外的状态到目标 对象。

public class LockMixin extends DelegatingIntroductionInterceptor

implements Lockable {

private boolean locked;

public void lock() {

this.locked = true;

}

public void unlock() {

this.locked = false;

}

public boolean locked() {

return this.locked;

}

public Object invoke(MethodInvocation invocation) throws Throwable {

if (locked() && invocation.getMethod().getName().indexOf("set") == 0)

throw new LockedException();

return super.invoke(invocation);

}

}

通常不要需要改写invoke()方法:实现 DelegatingIntroductionInterceptor就足够了,如果是导入的方法, DelegatingIntroductionInterceptor实现会调用委托方法, 否则继续沿着连接点处理。在现在的情况下,我们需要添加一个检查:在上锁状态下不能调用setter方法。

所需的导入advisor是很简单的。只有保存一个独立的 LockMixin实例,并指定导入的接口,在这里就是 Lockable。一个稍微复杂一点例子可能需要一个导入拦截器(可以 定义成prototype)的引用:在这种情况下,LockMixin没有相关配置,所以我们简单地 使用new来创建它。

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

public LockMixinAdvisor() {

super(new LockMixin(), Lockable.class);

}

}

我们可以非常简单地使用这个advisor:它不需要任何配置。(但是,有一点 必要的:就是不可能在没有IntroductionAdvisor 的情况下使用IntroductionInterceptor。) 和导入一样,通常 advisor必须是针对每个实例的,并且是有状态的。我们会有不同的的LockMixinAdvisor 每个被通知对象,会有不同的LockMixin。 advisor组成了被通知对象的状态的一部分。

和其他advisor一样,我们可以使用 Advised.addAdvisor() 方法以编程地方式使用这种advisor,或者在XML中配置(推荐这种方式)。 下面将讨论所有代理创建,包括“自动代理创建者”,选择代理创建以正确地处理导入和有状态的混入。

5.4.Spring中的advisor

在Spring中,一个advisor就是一个aspect的完整的模块化表示。 一般地,一个advisor包括通知和切入点。

撇开导入这种特殊情况,任何advisor可被用于任何通知。 org.springframework.aop.support.DefaultPointcutAdvisor 是最通用的advisor类。例如,它可以和MethodInterceptor、 BeforeAdvice或者ThrowsAdvice一起使 用。

Spring中可以将advisor和通知混合在一个AOP代理中。例如,你可以在一个代理配置中 使用一个对around通知、throws通知和before通知的拦截:Spring将自动创建必要的拦截器链。

5.5.用ProxyFactoryBean创建AOP代理

如果你在为你的业务对象使用Spring的IoC容器(例如ApplicationContext或者BeanFactory), 你应该会或者你愿意会使用Spring的aop FactoryBean(记住,factory bean引入了一个间接层, 它能创建不同类型的对象).

在spring中创建AOP proxy的基本途径是使用org.springframework.aop.framework.ProxyFactoryBean. 这样可以对pointcut和advice作精确控制。但是如果你不需要这种控制,那些简单的选择可能更适合你。

5.5.1.基本概要

ProxyFactoryBean,和其他Spring的 FactoryBean实现一样,引入一个间接的层次。如果你定义一个名字为foo的ProxyFactoryBean, 引用foo的对象所看到的不是ProxyFactoryBean 实例本身,而是由实现ProxyFactoryBean的类的 getObject()方法所创建的对象。这个方法将创建一个包装了目标对象的AOP代理。

使用ProxyFactoryBean或者其他IoC可知的类来创建AOP代理 的最重要的优点之一是IoC可以管理通知和切入点。这是一个非常的强大的功能,能够实现其他AOP框架很难实现的特定的方法。例如,一个通知本身可以引用应用对象(除了目标对象, 它在任何AOP框架中都可以引用应用对象),这完全得益于依赖注入所提供的可插入性。

5.5.2.JavaBean的属性

类似于Spring提供的绝大部分FactoryBean实现一样, ProxyFactoryBean也是一个javabean,我们可以利用它的属性来:

  • 指定你将要代理的目标
  • 指定是否使用CGLIB

一些关键属性来自org.springframework.aop.framework.ProxyConfig :它是所有AOP代理工厂的父类。这些关键属性包括:

  • proxyTargetClass: 如果我们应该代理目标类, 而不是接口,这个属性的值为true。如果这是true,我们需要使用CGLIB。
  • optimize: 是否使用强优化来创建代理。不要使用 这个设置,除非你了解相关的AOP代理是如何处理优化的。目前这只对CGLIB代理有效;对JDK 动态代理无效(默认)。
  • frozen: 是否禁止通知的改变,一旦代理工厂已经配置。 默认是false。
  • exposeProxy: 当前代理是否要暴露在ThreadLocal中, 以便它可以被目标对象访问。(它可以通过MethodInvocation得到,不需要ThreadLocal)。 如果一个目标需要获得它的代理并且exposeProxy的值是ture,可以使用 AopContext.currentProxy()方法。
  • aopProxyFactory: 所使用的AopProxyFactory具体实现。 这个参数提供了一条途径来定义是否使用动态代理、CGLIB还是其他代理策略。默认实现将适当地选择动态 代理或CGLIB。一般不需要使用这个属性;它的意图是允许Spring 1.1使用另外新的代理类型。

其他ProxyFactoryBean特定的属性包括:

  • proxyInterfaces: 接口名称的字符串数组。如果这个 没有提供,CGLIB代理将被用于目标类。
  • interceptorNames: Advisor、interceptor或其他 被应用的通知名称的字符串数组。顺序是很重要的。这里的名称是当前工厂中bean的名称,包 括来自祖先工厂的bean的名称。
  • singleton: 工厂是否返回一个单独的对象,无论 getObject()被调用多少次。许多FactoryBean 的实现提供这个方法。默认值是true。如果你想要使用有状态的通知--例如,用于有状态的 mixin--将这个值设为false,使用prototype通知。

5.5.3.代理接口

让我们来看一个简单的ProxyFactoryBean的实际例子。这个例子涉及到 :

  • 一个将被代理的目标bean,在这个例子里,这个bean的被定义为"personTarget".
  • 一个advisor和一个interceptor来提供advice.
  • 一个AOP代理bean定义,该bean指定目标对象(这里是personTarget bean), 代理接口,和使用的advice.

<bean id="personTarget" class="com.mycompany.PersonImpl">

<property name="name"><value>Tony</value></property>

<property name="age"><value>51</value></property>

</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">

<property name="someProperty"><value>Custom string property value</value></property>

</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.NopInterceptor">

</bean>

<bean id="person"

class="org.springframework.aop.framework.ProxyFactoryBean">

<property name="proxyInterfaces"><value>com.mycompany.Person</value></property>

<property name="target"><ref local="personTarget"/></property>

<property name="interceptorNames">

<list>

<value>myAdvisor</value>

<value>debugInterceptor</value>

</list>

</property>

</bean>

请注意:person bean的interceptorNames属性提供一个String列表, 列出的是该ProxyFactoryBean使用的,在当前bean工厂定义的interceptor或者advisor的 名字(advisor,interceptor,before,after returning,和throws advice 对象皆可)。 Advisor在该列表中的次序很重要。

你也许会对该列表为什么不采用bean的引用存有疑问。 原因就在于如果ProxyFactoryBean的singleton属性被设置为false, 那么bean工厂必须能返回多个独立的代理实例。 如果有任何一个advisor本身是prototype的,那么它就需要返回独立的实例, 也就是有必要从bean工厂获取advisor的不同实例,bean的引用在这里显然是不够的。

上面定义的“person”bean定义可以作为Person接口的实现来使用,如下所示:

Person person = (Person) factory.getBean("person");

在同一个IoC的上下文中,其他的bean可以依赖于Person接口,就象依赖于一个普通的java对象一样。

<bean id="personUser" class="com.mycompany.PersonUser">

<property name="person"><ref local="person" /></property>

</bean>

在这个例子里,PersonUser类暴露了一个类型为Person的属性。 只要是在用到该属性的地方,AOP代理都能透明的替代一个真实的Person实现。 但是,这个类可能是一个动态代理类。也就是有可能把它类型转换为一个Advised接口 (该接口在下面的章节中论述) 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值