OMG,到底在寻找什么..................
(构造一个完美的J2EE系统所需要的完整知识体系)
posts - 198,  comments - 37,  trackbacks - 0

原贴地址:http://book.csdn.net/bookfiles/111/1001113463.shtml

一.AOP概念

介绍完
IoC 之后,我们来介绍另外一个重要的概念: AOP(Aspect Oriented Programming) ,也就是面向方面编程的技术。 AOP 基于 IoC 基础,是对 OOP 的有益补充。

AOP 将应用系统分为两部分,核心业务逻辑 Core business concerns )及横向的通用逻辑, 也就是所谓的方面 Crosscutting enterprise concerns ,例如,所有大中型应用都要涉及到的持久化管理( Persistent )、事务管理( Transaction Management )、安全管理( Security )、 日志管理( Logging )和调试管理( Debugging )等。

AOP 正在成为软件开发的下一个光环。使用 AOP ,你可以将处理 aspect 的代码注入主程序,通常主程序的主要目的并不在于处理这些 aspect AOP 可以防止代码混乱。

Spring framework 是很有前途的 AOP 技术。作为一种非侵略性的、轻型的 AOP framework ,你无需使用预编译器或其他的元标签,便可以在 Java 程序中使用它。这意味着开发团队里只需一人要对付 AOP framework ,其他人还是像往常一样编程。

6.3.1  A OP 概念

让我们从定义一些重要的 AOP 概念开始。

方面( 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 Jboss 4 只提供 Around 通知。

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

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

下面让我们实现一个 Spring AOP 的例子。在这个例子中,我们将实现一个 before advice ,这意味着 advice 的代码在被调用的 public 方法开始前被执行。以下是这个 before advice 的实现代码。

package com.ascenttech.springaop.test;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;

public class TestBeforeAdvice implements MethodBeforeAdvice {
   public void before(Method m, Object[] args, Object target)  throws Throwable {
     System.out.println("Hello world! (by " + this.getClass().getName()  + ")");
    }
}

接口 MethodBeforeAdvice 只有一个方法 before 需要实现,它定义了 advice 的实现。 before 方法共用 3 个参数,它们提供了相当丰富的信息。参数 Method m advice 开始后执行的方法,方法名称可以用作判断是否执行代码的条件。 Object[] args 是传给被调用的 public 方法的参数数组。当需要记日志时,参数 args 和被执行方法的名称都是非常有用的信息。你也可以改变传给 m 的参数,但要小心使用这个功能;编写最初主程序的程序员并不知道主程序可能会和传入参数的发生冲突。 Object target 是执行方法 m 对象的引用。

在下面的 BeanImpl 类中,每个 public 方法调用前,都会执行 advice ,代码如下。

package com.ascenttech.springaop.test;
public class BeanImpl implements Bean {

 public void theMethod() {
   
 System.out.println(this.getClass().getName()   + "." + new Exception().getStackTrace()  [0].getMethodName   ()  +  "()"  +  "says HELLO!");    
 }
}

BeanImpl 实现了下面的接口 Bean ,代码如下。

package com.ascenttech.springaop.test;
public interface Bean {
 public void theMethod();
}

虽然不是必须使用接口,但面向接口而不是面向实现编程是良好的编程实践, Spring 也鼓励这样做。

pointcut advice 通过配置文件来实现,因此,接下来你只需编写主方法的 Java 代码,代码如下。

package com.ascenttech.springaop.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Main {
 public static void main(String[] args) {

  //Read the configuration file
  ApplicationContext ctx = new FileSystemXmlApplicationContext("springconfig.xml");

  //Instantiate an object
  Bean x = (Bean) ctx.getBean("bean");

  //Execute the public method of the bean (the test)
  x.theMethod();
 }
}

我们从读入和处理配置文件开始,接下来马上要创建它。这个配置文件将作为粘合程序不同部分的“胶水”。读入和处理配置文件后,我们会得到一个创建工厂 ctx ,任何一个 Spring 管理的对象都必须通过这个工厂来创建。对象通过工厂创建后便可正常使用。

仅仅用配置文件便可把程序的每一部分组装起来,代码如下。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework. org/dtd/spring-beans.dtd">
<beans>
 <!--CONFIG-->
 <bean id="bean" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces">
         <value>com.ascenttech.springaop.test.Bean</value>
     </property>

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

  <property name="interceptorNames">
   <list>
    <value>theAdvisor</value>
   </list>
  </property>

 </bean> 

 <!--CLASS--> 
 <bean id="beanTarget" class="com.ascenttech.springaop.test.BeanImpl"/>
 <!--ADVISOR-->
 <!--Note: An advisor assembles pointcut and advice-->

 <bean id="theAdvisor" class="org.springframework.aop.support.RegexpMethod PointcutAdvisor">
  <property name="advice">
      <ref local="theBeforeAdvice"/>
  </property>

  <property name="pattern">
   
   <value>com\.ascenttech\.springaop\.test\.Bean\.theMethod</value>
  </property>

 </bean>
 <!--ADVICE-->

 <bean id="theBeforeAdvice" class="com.ascenttech.springaop.test.TestBefore Advice"/>
</beans>

4 bean 定义的次序并不重要。我们现在有了一个 advice 、一个包含了正则表达式 pointcut advisor 、一个主程序类和一个配置好的接口,通过工厂 ctx ,这个接口返回自己本身实现的一个引用。

BeanImpl TestBeforeAdvice 都是直接配置。我们用一个惟一的 ID 创建一个 bean 元素,并指定了一个实现类,这就是全部的工作。

advisor 通过 Spring framework 提供的一个 RegexMethodPointcutAdvisor 类来实现。我们用 advisor 的第一个属性来指定它所需的 advice-bean ,第二个属性则用正则表达式定义了 pointcut ,确保良好的性能和易读性。

最后配置的是 bean ,它可以通过一个工厂来创建。 bean 的定义看起来比实际上要复杂。 bean ProxyFactoryBean 的一个实现,它是 Spring framework 的一部分。这个 bean 的行为通过以下的 3 个属性来定义。

属性 proxyInterface 定义了接口类。

属性 target 指向本地配置的一个 bean ,这个 bean 返回一个接口的实现。

属性 interceptorNames 是惟一允许定义一个值列表的属性,这个列表包含所有需要在 beanTarget 上执行的 advisor 。注意, advisor 列表的次序是非常重要的。

二.Spring的切入点,通知,advisor

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

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 框架能够缓存切入点的测试结果。

2 .切入点的运算

Spring 支持的切入点的运算有

并表示只要任何一个切入点匹配的方法。交表示两个切入点都要匹配的方法。并通常比较有用。

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

3 .实用切入点实现

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

1 )静态切入点

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

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

正则表达式切入点

一个很显然的指定静态切入点的方法是正则表达式。除了 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 正则表达式包。

属性驱动的切入点

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

2 )动态切入点

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

控制流切入点

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

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 .自定义切入点

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

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

1 .通知的生命周期

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

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

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

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

2 Spring 中通知类型

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

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 联盟目前也没有定义切入点接口。

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 通知可以被用于任何类型的切入点。

3 Throws 通知

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

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

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

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

public  class RemoteThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}

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

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 通知可被用于任何类型的切入点。

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

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

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

DelegatingIntroductionInterceptor 的设计是将导入委托到真正实现导入接口的接口,隐藏完成这些工作的拦截器。委托可以使用构造方法参数设置到任何对象中,默认的委托就是自己(当无参数的构造方法被使用时)。这样,在下面的例子里,委托是 DelegatingIntroduction Interceptor 的子类 LockMixin 。给定一个委托(默认是自身)的 DelegatingIntroductionIntercepto r 实例寻找被这个委托(而不是 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 中配置(推荐这种方式)。下面将讨论所有代理创建,包括“自动代理创建者”,选择代理创建以正确地处理导入和有状态的混入。


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

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

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


三.用ProxyFactoryBean创建AOP代理


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

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

1 .基本概要

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

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

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 通知。

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 的引用存有疑问。原因就在于如果 ProxyFactory Bean 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 接口(该接口在下面的章节中论述)。

4 .代理类

如果你需要代理的是类,而不是一个或多个接口,又该怎么办呢 ?

象一下我们上面的例子,如果没有 Person 接口,我们需要通知一个叫 Person 的类,而且该类没有实现任何业务接口。在这种情况下,你可以配置 Spring 使用 CGLIB 代理,而不是动态 代理。你只要在上面的 ProxyFactoryBean 定义中把它的 proxyTargetClass 属性改成 true 就可以。

只要你愿意,即使在有接口的情况下,你也可以强迫 Spring 使用 CGLIB 代理。

CGLIB 代理是通过在运行期产生目标类的子类来进行工作的。 Spring 可以配置这个生成的子类,来代理原始目标类的方法调用。这个子类是用 Decorator 设计模式置入到 advice 中的。

CGLIB 代理对于用户来说应该是透明的。然而,还有以下一些因素需要考虑。

1 Final 方法不能被通知,因为不能被重写。

2 )你需要在你的 classpath 中包括 CGLIB 的二进制代码,而动态代理对任何 JDK 都是可用的。

3 CGLIB 和动态代理在性能上有微小的区别,对 Spring 1.0 来说,后者稍快。另外,以后可能会有变化。在这种情况下,性能不是决定性因素。

posted on 2006-11-01 11:17 OMG 阅读(1377) 评论(0)  编辑  收藏 所属分类: Spring

只有注册用户登录后才能发表评论。


网站导航:
 

<2006年11月>
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

常用链接

留言簿(1)

随笔分类

随笔档案

IT风云人物

文档

朋友

相册

经典网站

搜索

  •  

最新评论

阅读排行榜

评论排行榜