《Spring 5官方文档》 Spring AOP的经典用法

原文连接html

在本附录中,咱们会讨论一些初级的Spring AOP接口,以及在Spring 1.2应用中所使用的AOP支持。
对于新的应用,咱们推荐使用 Spring AOP 2.0来支持,在AOP章节有介绍。但在已有的项目中,或者阅读数据或者文章时,可能会遇到Spring AOP 1.2风格的示例。Spring 2.0彻底兼容Spring 1.2,在本附录中全部的描述都是Spring 2.0所支持的。java

Spring中的切入点API

一块儿看一下Spring是如何处理关键切入点这个概念。web

概念

Spring的切入点模型可以让切入点重用不一样的独立的加强类型。这样能够实现,针对不一样的加强,使用相同的切入点。正则表达式

org.springframework.aop.Pointcut是一个核心接口,用于将加强定位到特定的类或者方法上。
完整的接口信息以下:spring

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}

Pointcut拆分红两个部分,容许重用类和方法匹配的部分,和细粒度的组合操做(例如和其余的方法匹配器执行一个“组合”操做)。数据库

ClassFilter接口用于将切点限制在给定的目标类上。
若是matches()方法老是返回true,全部的类都会被匹配上。编程

public interface ClassFilter {

    boolean matches(Class clazz);

}

MethodMatcher接口一般更为重要。完整的接口描述以下:c#

public interface MethodMatcher {

    boolean matches(Method m, Class targetClass);

    boolean isRuntime();

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

}

matches(Method, Class)方法用于测试切点是否匹配目标类的一个指定方法。
这个测试能够在AOP代理建立时执行,避免须要在每个方法调用时,再测试一次。
若是对于一个给定的方法,matches(Method, Class)方法返回true,而且对于同MethodMatcher实例的isRuntime()方法也返回true,
那么在每次被匹配的方法执行时,都会调用boolean matches(Method m, Class targetClass, Object[] args)方法。
这样使得在目标加强执行前,一个切点能够在方法执行时当即查看入参。api

大部分MethodMatcher是静态的,意味着他们isRuntime()方法的返回值是false。
在这种状况下,boolean matches(Method m, Class targetClass, Object[] args)方法是永远不会被调用的。数组

提示

若是能够,尽可能将切点设置为静态,这样在一个AOP代理生成后,能够容许AOP框架缓存评估的结果。

切点操做

Spring在切点的操做:尤为是,组合(union)交叉(intersection)

  • 组合意味着方法只需被其中任意切点匹配。
  • 交叉意味着方法须要被全部切点匹配。
  • 组合一般更为有用。
  • 切点可使用org.springframework.aop.support.Pointcuts类或者org.springframework.aop.support.ComposablePointcut中的静态方法组合。
    然而,使用AspectJ的切点表达式一般是一种更为简单的方式。

AspectJ切点表达式

自从2.0版之后,Spring所使用的最重要切点类型就是org.springframework.aop.aspectj.AspectJExpressionPointcut
这个切点使用了一个AspectJ支持的库,用以解析AspectJ切点表达式的字符串。

有关原始AspectJ切点元素支持的讨论,请参阅以前章节。

方便的切点实现

Spring提供了几个方便的切点具体实现。有些能够在框架外使用;其余的则为应用程序的特定切点实现所须要的子类。

静态切点

静态切点是基于方法和目标类的,不能将方法参数也考虑其中。
对于大多数用法,静态切点是足够且最佳的选择。

对于Spring来讲,当一个方法第一次被调用是,对静态切点仅仅评估一次是可行的:在本次评估后,再次调用该方法时,就没有必要再对切点进行评估。

咱们一块儿看一些Spring中包含的静态切点具体实现。

正则表达式切点

一个显而易见的方式是使用正则表达式来指定静态切点。几个在Spring以外的框架能够实现这部分功能。
org.springframework.aop.support.Perl5RegexpMethodPointcut是一个常见的正则表达式切点,使用Perl 5正则表达式语法。
Perl5RegexpMethodPointcut类的正则表达式匹配依赖于Jakarta ORO。
Spring也提供了JdkRegexpMethodPointcut类,能够在JDK 1.4版本之上使用正则表达式。

使用Perl5RegexpMethodPointcut类,你能够提供一个正则表达式字符串的列表。
若是与该列表的中的某个正则匹配上了,那么切点的断定就为true(断定的结果是这些切点的有效组合)。

使用方法以下所示:

<bean id="settersAndAbsquatulatePointcut"
        class="org.springframework.aop.support.Perl5RegexpMethodPointcut">
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

Spring提供了一个方便的类,RegexpMethodPointcutAdvisor,容许咱们引用一个Advice(记住一个Advice多是一个介入加强、前置加强、或者异常抛出加强等)。
实际上,Spring会使用JdkRegexpMethodPointcut类。
使用RegexpMethodPointcutAdvisor简化配置,这个类封装了切点和加强。以下所示:

<bean id="settersAndAbsquatulateAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
        <ref bean="beanNameOfAopAllianceInterceptor"/>
    </property>
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

RegexpMethodPointcutAdvisor能够被任意类型的加强使用。

属性驱动切入

一个重要的静态切点就是metadata-driven切点。它会使用一些元数据属性信息:一般是源码级的元数据。

动态切点

动态切点的断定代价比静态切点要大。动态切点除了静态信息外,还须要考虑方法参数
这意味着它们在每次方法调用时都必须进行断定;断定的结果不能被缓存,由于参数是变化的。

表明性的事例是控制流切点。

控制流切点

Spring的控制流切点在概念上与AspectJ的cflow切点相似,不过功能稍弱。(目前没有方法,能够指定一个切点在其余切点匹配的链接点后执行。)
一个控制流切点匹配当前的调用栈【待定】。例如,若是一个链接点被一个在com.mycompany.web包中、或者SomeCaller类中的方法调用,就会触发。
控制流切点使用org.springframework.aop.support.ControlFlowPointcut类来指定。
说明

控制流切点在运行时进行评估明显代价更大,甚至是其余动态切点。在Java 1.4,大概是其余动态切点的5倍。

Pointcut父类

Spring提供了一些有用的切点父类,方便开发者实现本身的切点。

由于静态切点是最为实用的,你可能须要实现StaticMethodMatcherPointcut的子类,以下所示。
这里只须要实现一个抽象方法便可(虽然也能够覆盖其余方法来自定义类的行为)。

class TestStaticPointcut extends StaticMethodMatcherPointcut {

    public boolean matches(Method m, Class targetClass) {
        // return true if custom criteria match
    }

}

Spring也有动态切点的父类。
在Spring 1.0 RC2版本以后,能够自定义任意加强类型的切点。

自定义切点

因为切点在Spring AOP中都是Java类,而不是语言特征(就像在AspectJ中),能够声明自定义切点,不管静态仍是动态。
自定义切点在Spring中是能够任意复杂的。然而,若是能够,推荐使用AspectJ切点表达式。

说明

Spring以后的版本可能支持由JAC提供的“语义切点”。
例如:在目标对象中,全部修改实例变量的方法。

Spring中的Advice接口

如今让咱们看一下Spring AOP如何处理Advice(加强)。

Advice的生命周期

每一个Advice都是一个Spring的Bean。一个Advice实例在被加强的对象间共享,或者对于每个被加强的对象都是惟一的。
这取决于加强是类级的、仍是对象级的【待定】。
Each advice is a Spring bean. An advice instance can be shared across all advised
objects, or unique to each advised object. This corresponds to per-class or
per-instance advice.

Per-class级加强最为经常使用。它适用于一般的加强,例如事务加强。这种加强不依赖于代理对象或者增长新的状态;它们只是对方法和参数进行加强。
Per-instance级加强适用于介绍,支持它很复杂【待定】。在本示例中,加强对被代理的对象添加了一个状态。

也能够在同一个AOP代理中,使用共享和per-instance级加强的组合。

Spring中的加强类型

Spring在框架层以外,支持多种方式的加强,而且支持任意加强类型的可扩展性。
咱们一块儿了解一下标准加强类型和加强的基础概念。

拦截式环绕型加强

Spring中最基本的加强类型之一就是拦截式环绕型加强
经过使用方法拦截器,Spring彻底符合AOP联盟的环绕型加强接口。
环绕型方法拦截器应该实现如下接口:

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;

}

invoke() 方法的MethodInvocation表示了将要被调用的方法、目标链接点、AOP代理、以及该方法的参数。
invoke() 方法应当返回调用结果:目标链接点的返回值。

一个简单的方法拦截器实现以下所示:

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()方法。这个方法将拦截器链路调向链接点。
大多数拦截器会调用该方法,并返回该方法的值。可是,就像任意环绕加强同样,一个方法拦截器也能够返回一个不一样的值,或者抛出一个异常,而不是调用proceed()方法。
可是,没有足够的理由,不要这么干!

说明

方法拦截器提供与其余AOP联盟标准的AOP实现的互通性。
在本章剩余部分讨论的其余类型加强,会以Spring特定的方式实现AOP的概念。
使用最为具体的类型加强有必定优点,但若是你想在其余AOP框架中使用切面,就须要坚持使用方法拦截器。
须要注意的是,切点在框架间是不通用的,AOP联盟目前没有定义切点的接口。

前置加强

一个简单的加强类型是前置加强。这种加强不须要一个MethodInvocation对象,由于它仅仅在方法进入时被调用。

前置加强的优点是不须要调用proceed()方法,所以不会无端中断调用链。

MethodBeforeAdvice接口以下所示。【待定】
interface is shown below. (Spring’s API design would allow for
field before advice, although the usual objects apply to field interception and it’s
unlikely that Spring will ever implement it).

public interface MethodBeforeAdvice extends BeforeAdvice {

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

}

须要注意的是该方法的返回类型是void。前置加强能够在链接点钱插入一些自定义的行为,可是不能改变返回结果。
若是一个前置加强抛出一个异常,它会中断调用链中接下来的执行步骤。这个异常将传递到调用链的上一层。
若是该异常没有被处理,或者在被调用方法中签名【待定】,这个异常会直接传递给方法调用方;不然,该异常会被AOP代理类封装到一个未经检查的异常中。

在Spring中,一个前置加强的例子:统计全部方法的执行次数:

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

提示

前置加强能够被任何切点使用。

异常抛出加强

当链接点返回的结果是一个抛出的异常时,异常抛出加强会被调用。
Spring提供异常抛出加强。
须要主意的是org.springframework.aop.ThrowsAdvice 接口不包括任何方法:它是一个标签式接口,标识给出的对象实现了一个或多个类型的异常抛出加强。
它们的格式以下所示:

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

只有最后一个参数是必须的。这个方法可能拥有1个或者4个参数,取决于加强方法是否对被加强的方法和方法参数感兴趣。
下面的类是异常抛出加强的例子。

若是一个RemoteException(包括子类)被抛出,下面这个加强就会被调用:

public class RemoteThrowsAdvice implements ThrowsAdvice {

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

}

若是一个ServletException被抛出,下面这个加强就会被调用。
与上面不一样的是,该方法声明了4个参数,所以它能够访问被调用的方法、方法参数和目标对象:

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }

}

最后一个示例描述了,一个类中若是声明两个方法,能够同时处理RemoteExceptionServletException
一个类中能够包含任意个异常抛出加强的处理方法。

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 with all arguments
    }
}

说明

若是一个异常抛出加强自己抛出了一个异常,它将覆盖掉原始的异常(例如,改变抛给用户的异常)。
这个覆盖的异常一般是一个运行时异常;这样就能够兼容任何的方法签名。
可是,若是一个异常抛出加强抛出了一个检查时异常,这个异常必须和该目标方法的声明匹配,以此在必定程度上与特定的目标签名相结合。

不要抛出与目标方法签名不兼容的检查时异常!

提示

异常抛出加强能够被任意切点使用。

后置加强

后置加强必须实现org.springframework.aop.AfterReturningAdvice接口,以下所示:

public interface AfterReturningAdvice extends Advice {

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

}

一个后置加强能够访问被调用方法的返回值(不能修改)、被调用方法、方法参数、目标对象。

下面的后置加强统计了全部执行成功的方法调用,即没有抛出异常的调用:

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

}

这个加强不会改变执行路径。若是它抛出了一个异常,该异常会抛出到拦截链,而不是返回返回值。

提示

后置加强能够被任意切点使用。

引介加强

Spring将引介加强看成一个特殊的拦截式加强。

引介加强须要一个IntroductionAdvisor和一个IntroductionInterceptor实现如下接口:

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);

}

invoke()方法继承自AOP联盟的MethodInterceptor接口,必须被引介实现:
也就是说,若是被调用的方式是一个被介入的接口,该引介拦截器就会负责处理该方法的调用,不能调用proceed()方法。

不是全部的切点均可以使用引介加强,由于它只适用于类级,而不是方法级。
你能够经过IntroductionAdvisor来使用引介加强,该类有以下几个方法:

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;

}

public interface IntroductionInfo {

    Class[] getInterfaces();

}

没有MethodMatcher,所以也没有Pointcut与引介加强相关联。只有类过滤器是符合逻辑的。
getInterfaces()方法会返回被该加强器引介的接口集合。
validateInterfaces()会在内部被调用,用于肯定被引介的接口是否能够被配置的IntroductionInterceptor所实现。

让咱们一块儿看一个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被设计成代理一个须要被引介接口的真实实现,隐藏使用拦截器去这样作。
使用构造函数的参数,能够把代理设置为任意对象;默认的代理(使用无参构造函数时)就是引介加强【待定】。
The delegate can be set to any object using a constructor argument; the
default delegate (when the no-arg constructor is used) is this.
所以在下面的示例中,代理是DelegatingIntroductionInterceptor的子类LockMixin
给定的代理(默认是自身),一个DelegatingIntroductionInterceptor对象查找全部被该代理所实现的接口结合(除了IntroductionInterceptor),
并支持代理介入它们。
LockMixin的子类调用suppressInterface(Class intf)方法,能够禁止不能被暴露的接口被调用。
然而不管一个IntroductionInterceptor准备支持多少个接口,IntroductionAdvisor都会控制哪些接口实际是被暴露的。
一个被引介的接口会隐藏掉目标对象的全部接口的实现。

所以DelegatingIntroductionInterceptor的子类LockMixin,也实现了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代理会调用方法,不然调用链接点,一般也是足够了。
在这种状况下,咱们须要加入一个检查:若是处于锁住的模式,任何setter方法都是不能被调用。

所须要的引介加强器很是简单。它所须要作的仅仅是持有一个明确的LockMixin对象,指定须要被引介的接口(在本示例中,仅仅是Lockable接口)。
一个更加复杂的例子是持有一个引介拦截器的引用(被定义为一个原型):在本示例中,没有配置和一个LockMixin对象相关,全部咱们简单使用new来建立。

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }

}

咱们能够很是简单的使用这个加强器:不须要任何配置。(可是,使用IntroductionInterceptor的同时,不使用IntroductionAdvisor是不行的。)
和以前介绍的同样,Advisor是一个per-instance级的,它是有状态的。
所以对于每个被加强的对象,就像LockMixin 同样,咱们都须要一个不一样的LockMixinAdvisor
Advisor就是被加强对象状态的一部分。

咱们可使用编程的方式应用这个Advisor,使用Advised.addAdvisor()方法,或者在XML中配置(推荐),就像其余Advisor同样。
下面会讨论全部代理建立的选择方式,包括“自动代理建立者”正确的处理引介和有状态的mixins。

Spring中的Advisor接口

在Spring中,一个Advisor是一个切面,仅仅包括了一个和切点表达式相关联的加强对象。

除了介绍的特殊状况,任何Advisor均可以被任意加强使用。
org.springframework.aop.support.DefaultPointcutAdvisor 是最为经常使用的advisor类。
例如,它能够被MethodInterceptorBeforeAdviceThrowsAdvice使用。

在Spring的同一个AOP代理中,有可能会混淆Advisor和加强。
例如,在一个代理的配置中,你可能使用了一个拦截式环绕加强、异常抛出加强和前置加强:Spring会自动建立须要的拦截链。

使用ProxyFactoryBean建立AOP代理

若是你的业务对象使用了Spring IoC容器(一个ApplicationContext或者BeanFactory),你应该、也会但愿使用一个Spring的AOP FactoryBean。
(须要注意的是,一个FactoryBean间接的引入了一层,该层能够建立不一样类型的对象。)

说明

Spring 2.0 AOP在内部也是用了工厂对象。

在Spring中,建立AOP代理最基础的方式是使用org.springframework.aop.framework.ProxyFactoryBean类。
这样能够彻底控制将要使用的切点和加强,以及它们的顺序。
然而,更简单的是这是可选的,若是你不须要这样的控制。

基础

ProxyFactoryBean就像Spring其余FactoryBean的实现同样,间接的引入了一个层次。
若是你定义了一个名为fooProxyFactoryBean,那么对象引用的foo,不是ProxyFactoryBean实例自己,
而是ProxyFactoryBean 对象调用getObject()方法的返回值。
这个方法会建立一个AOP代理来包装目标对象。

使用一个ProxyFactoryBean或者IoC感知类来建立AOP代理的最大好处之一是,加强和切点一样也能够被IoC管理。
这是一个强大的功能,实现的方法是其余AOP框架难以企及的。

JavaBean属性

与大多数Spring所提供的FactoryBean实现相同的是,ProxyFactoryBean 自己也是一个JavaBean。
它的属性用于:

一些关键的属性继承自org.springframework.aop.framework.ProxyConfig(Spring中全部代理工厂的超类)
这些关键属性包括:

  • proxyTargetClass: 若是目标类将被代理标志为true,而不是目标类的接口。
    若是该属性设置为true,CGLIB代理就会被建立(但也须要参见基于JDK和CGLIB的代理
  • optimize: 控制是否积极优化经过CGLIB建立的代理类。除非彻底了解AOP代理相关的优化处理,不然不要使用这个设置。
    这个设置当前只对CGLIB代理有效;对JDK动态代理无效。
  • frozen: 若是一个代理配置是frozen,那么就再也不容许对该配置进行更改。
    若是你不想调用者在代理建立后操做该代理(经过被加强的接口),做为轻微的优化手段是该配置是颇有用的。
    该配置的默认值是false,所以增长附带的advice是容许的。
  • exposeProxy: 该属性决定当前的代理是否在暴露在ThreadLocal中,让它能够被目标对象访问到。
    若是一个目标对象须要获取该代理,exposeProxy就设置为true,目标对象能够经过AopContext.currentProxy()方法获取固然的代理。
  • aopProxyFactory: 须要使用的AopProxyFactory实现。提供了是否使用动态代理的自定义方式,CGLIB或者其余代理模式。
    该属性的默认是适当的选择动态代理或者CGLIB。没有必要使用该属性;在Spring 1.1中它的目的在于添加新的代理类型。

ProxyFactoryBean的其余属性:

  • proxyInterfaces: 接口名称的字符串数组。若是没有提供该属性,会使用一个CGLIB代理参见(基于JDK和CGLIB的代理
  • interceptorNames: Advisor字符串数组,须要使用的拦截器或者其余advice的名称。
    顺序很是重要,先到的先处理。也就是列表中的第一个拦截器将会第一个处理调用。

这些名称是当前工厂的实例名称,包括从祖先工厂继承来的名称。
这里不能包括bean的引用,由于这么作的结果是ProxyFactoryBean忽略advice的单例设置。

你能够在一个拦截器名称后添加一个星号( *)。这样在应用中,全部以型号前的部分为名称开始的advisor对象,都将被应用。
这个特性的示例能够在使用’全局’advisor中找到。

  • singleton: 是否该工厂返回一个单例对象,不管调用多少次getObject()方法。
    某些FactoryBean实现提供了这样的方法。该配置的默认值是true
    若是你须要使用一个有状态的advice,例若有状态的mixins,使用prototype的advice,以及将该属性设置为false

基于JDK和CGLIB的代理

本章做为明确的文档,介绍ProxyFactoryBean对于一个特定的目标对象(即被代理的对象)如何选择建立一个基于JDK的仍是基于CGLIB的代理。

说明

ProxyFactoryBean建立基于JDK或基于CGLIB的代理在Spring 1.2.x和2.0版本间有所改变。
ProxyFactoryBean目前与TransactionProxyFactoryBean类的自动检测接口所表现的语义类似。

若是被代理的目标对象的类(如下简称目标类)没有实现任何接口,那么就会建立基于CGLIB的代理。
这是一个最简单的情景,由于JDK代理是基于接口的,没有接口就意味着JDK代理类是行不通的。
即一个简单的目标类插入,经过interceptorNames属性指定一系列的拦截器。
须要注意的是即便ProxyFactoryBeanproxyTargetClass属性被设置为false,也会建立基于CGLIB的代理。
(这显然没有任何意义,并且最好从Bean定义中移除,由于它是冗余的,并且是很糟的混淆。)

若是目标类实现了一个(或者多个)接口,那么被建立代理的类型取决于ProxyFactoryBean的配置。
若是ProxyFactoryBeanproxyTargetClass属性被置为true,那么会建立基于CGLIB的代理。
这颇有道理,而且符合最小惊讶原则。
即便ProxyFactoryBeanproxyInterfaces属性被设置成一个或多个全量的接口名称,只要proxyTargetClass属性被置为true,就会建立基于CGLIB的代理。

即便ProxyFactoryBeanproxyInterfaces属性被设置成一个或多个全量的接口名称,那么就会建立基于JDK的代理。
被建立的代理会实现全部proxyInterfaces所指定的接口;若是目标类也实现的接口多余proxyInterfaces所指定的,这也是能够的,但这些额外的接口不会被建立的代理所实现。

若是ProxyFactoryBeanproxyInterfaces没有被设置,可是目标类也没有实现一个(或多个)接口,
ProxyFactoryBean会自动检测至少一个目标类实际实现的接口,而且建立一个基于JDK的代理。
实际上被代理的接口,就是目标类全部实现的接口;事实上,这和简单的将目标类实现的每个接口所组成的列表设置为proxyInterfaces属性,效果是同样的。
然而,自动检测显然减小了工做量,也不容易出现拼写错误。

代理接口

咱们一块儿看一个简单的ProxyFactoryBean示例。这个例子涉及:

  • 一个将被代理的目标对象。在下面的示例中定义的是”personTarget”对象。
  • 一个Advisor和一个Interceptor用以提供加强。
  • 一个AOP代理对象指定了目标对象(”personTarget”对象)和须要代理的接口,以及应用的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.DebugInterceptor">
</bean>

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces"><value>com.mycompany.Person</value></property>
    <property name="target"><ref bean="personTarget"/></property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

须要主意的是interceptorNames属性使用的是一个字符串列表:当前工厂的interceptor或者advisor名称。
Advisor、拦截器、前置加强、后置加强、异常抛出加强均可以被使用。Advisor的排序很重要。

说明

你可能会疑惑,为何列表没有持有bean的引用。
缘由是若是一个ProxyFactoryBean的singleton属性是false,它就必须返回一个独立的代理对象。
若是每个advisor对象自己是一个prototype的,就应该返回一个独立的对象,所以从工厂中得到一个prototype的实例是有必要的;持有一个引用是不行的。

上面定义的”person”对象能够被一个Person实现所替代,以下所示:

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

在同一个IoC上下文中的其余bean,也能够强类型依赖它,做为一个原生的java对象:

<bean id="personUser" class="com.mycompany.PersonUser">
    <property name="person"><ref bean="person" /></property>
</bean>

本示例中的PersonUser类暴露了一个Person类型的属性。
就此而言,AOP代理能够透明的替代一个“真实”person的实现。
然而,它的class是一个动态代理类。它也能够被强制转换为Advised接口(接下来会讨论)。

可使用内部匿名bean来隐藏目标和代理的区别。
只有ProxyFactoryBean的定义是不同的;包含的advice仅仅是为了示例的完整性:

<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.DebugInterceptor"/>

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces"><value>com.mycompany.Person</value></property>
    <!-- Use inner bean, not local reference to target -->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name"><value>Tony</value></property>
            <property name="age"><value>51</value></property>
        </bean>
    </property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

这样作有一个好处是只会有一个Person类型的对象:若是咱们想要阻止用户从应用上下文中获取一个没有被advise的对象是颇有用的,
或者须要阻止Spring IoC容器的自动注入时的歧义。
还有一个能够做为优势的是,ProxyFactoryBean定义是独立的。
可是,有时候从工厂中能够获得一个没有被advise的目标也是一个优势:好比在特定的测试场景。

代理类

若是你须要代理一个类,而不是代理一个或多个接口?

设想一下,在上面的实例中,若是没有Person接口,咱们须要去加强一个叫Person的类,该类没有实现任何业务接口。
在这种状况下,你须要配置Spring,使用CGLIB代理,而不是动态代理。
只须要将ProxyFactoryBean的proxyTargetClass属性置为true。
虽然最好使用接口变成,而不是类,但当加强遗留的代码时,加强目标类而不是目标接口,可能更为有用。
(一般状况下,Spring不是约定俗成的。它对应用好的实践很是简单,而且避免强制使用特定的实践方式)

若是须要,你能够在任何状况下强制使用CGLIB,甚至对于接口。

CGLIB代理的的工做原理是在运行时生成目标类的子类。
Sprig将原始目标对象的方法调用委托给该生成的子类:该子类使用了装饰器模式,在加强时织入。

CGLIB代理一般对用户是透明的。然而,有一些问题须要考虑:

  • Final方法是不能被advise的,由于它们不能被重写。
  • 从Spring 3.2以后,就再也不须要在项目的classpath中加入CGLIB的库,CGLIB相关的类已经被从新打包在org.springframework包下,直接包含在prig-core 的jar包中。
    这样即方便使用,又不会和其余项目所依赖的CGLIB出现版本冲突。

CGLIB代理和动态代理在性能上有所差别。
自从Spring 1.0后,动态代理稍快一些。
然而,这种差别在将来可能有所改变。
在这种状况下,性能再也不是考虑的关键因素。

使用全局advisor

经过在拦截器的名称上添加星号,全部匹配星号前部分的名称的advisor,都将添加到advisor链路中。
若是你须要添加一个套标准的全局advisor,这可能会派上用场。0

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="service"/>
    <property name="interceptorNames">
        <list>
            <value>global*</value>
        </list>
    </property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>

简明的代理定义

特别是在定义事务代理时,最终可能有许多相似的代理定义。
使用父、子bean定义,以及内部bean定义,可能会使代理的定义更加清晰和简明。

首先,建立一个代理的父模板定义。

<bean id="txProxyTemplate" abstract="true"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributes">
        <props>
            <prop key="*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

这个定义自身永远不会实例化,因此其实是不完整的定义。
而后每一个须要被建立的代理,只须要一个子bean的定义,将目标对象包装成一个内部类定义,由于目标对象永远不会直接被使用。

<bean id="myService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MyServiceImpl">
        </bean>
    </property>
</bean>

固然也能够覆盖父模板的属性,例如在本示例中,事务传播的设置:

<bean id="mySpecialService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MySpecialServiceImpl">
        </bean>
    </property>
    <property name="transactionAttributes">
        <props>
            <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="store*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

须要主意的是,在上面的示例中,咱们经过abstract属性明确的将父bean标记为抽象定义,
就如前面介绍的子bean定义,所以该父bean永远不会被实例化。
应用上下文(不是简单的bean工厂)默认会预先实例化全部单例。
所以,重要的是,若是你有一个仅仅想做为模板的bean(父bean)定义,而且指定了该bean的class,
那么你必须保证该bean的abstract属性被置为tue,不然应用上下文会尝试在实际中预先实例化该bean。

使用ProxyFactory以编程的方式建立AOP代理

使用Spring以编程的方式建立AOP代理很是简单。
这也运行你在不依赖Spring IoC容器的状况下使用Spring的AOP。

下面的代码展现了使用一个拦截器和一个advisor建立一个目标对象的代理。

ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addInterceptor(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();

第一步是构造一个org.springframework.aop.framework.ProxyFactory对象。
像上面的示例同样,可使用一个目标对象建立它,或者使用指定接口集的构造函数替代来建立该ProxyFactory。

你能够添加拦截器和advisor,并在ProxyFactory的生命周期中操做它们。
若是你添加一个IntroductionInterceptionAroundAdvisor,可使得该代理实现附加的接口集合。

在ProxyFactory也有一些好用的方法(继承自AdvisedSupport),容许你天机其余的加强类型,好比前置加强和异常抛出加强。
AdvisedSupport是ProxyFactory和ProxyFactoryBean的超类。

提示

在IoC框架中集成AOP代理的建立在大多数应用中是最佳实践。
一般,咱们推荐在Java代码以外配置AOP。

操做被加强的对象

当你建立了AOP代理,你就能使用org.springframework.aop.framework.Advised接口来操做他们。
任何一个AOP代理,都能强制转换成该接口,或者不管任何该代理实现的接口。
这个接口包含如下方法:

Advisor[] getAdvisors();

void addAdvice(Advice advice) throws AopConfigException;

void addAdvice(int pos, Advice advice) throws AopConfigException;

void addAdvisor(Advisor advisor) throws AopConfigException;

void addAdvisor(int pos, Advisor advisor) throws AopConfigException;

int indexOf(Advisor advisor);

boolean removeAdvisor(Advisor advisor) throws AopConfigException;

void removeAdvisor(int index) throws AopConfigException;

boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;

boolean isFrozen();

getAdvisors()方法会返回添加到该工厂的每个advisor、拦截器或者其它类型的加强。
若是你添加了一个Advisor,那么返回Advisor数组在该索引下的对象,就是你添加的那个。
若是你添加的是一个拦截器或者其余类型的加强,Spring将会把它包装成一个带有切点(切点判断恒为真)的Advisor。
若是你添加了MethodInterceptor对象,该advisor getAdvisors()方法返回值,该索引处会是一个DefaultPointcutAdvisor对象,
该对象包括了你添加的MethodInterceptor对象和一个匹配全部类和方法的切点。

addAdvisor()能够用于添加任何Advisor。
一般该advisor是一个普通的DefaultPointcutAdvisor对象,包括了切点和advice,能够和任何advice或切点一块儿使用(除了引介加强)。

默认状况下,在一个代理被建立后,也能够添加或者删除advisor和拦截器。
惟一的限制是,不能增长或者删除一个引介advisor,由于已经从工厂生成的代理不能再进行接口修改。
(你能够从工厂中从新获取一个新的代理来避免该问题)。

一个简单的例子是强制转换一个AOP代理成为Advised 对象,而且检验和操做它的advice:

Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");

// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(new DebugInterceptor());

// Add selective advice using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);

说明

问题是,是否建议(没有一语双关)在生产环境中对一个业务对象进行修改advice,尽管这毫无疑问是一个合法的使用案例。
然而,在开发环境是很是有用的:例如,在测试过程当中。
我有时发现将一个拦截器或者advice增长到测试代码中是很是有用的,进入到一个方法中,调用我想测试的部分。
(例如,advice能够进入到一个方法的事务中:例如运行一个SQL后检查数据库是否正确更新,在该事务标记回滚以前。)

根据你建立的代理,一般你能够设置一个frozen标志,在这种状况下, AdvisedisFrozen()方法会返回true,
而且任何经过添加或者删除方法试图修改advice都会抛出一个AopConfigException异常。
在一些状况下,冻结一个advise对象的状态是有用的,例如,阻止调用代码删除安全拦截器。
在Spring 1.1也用于积极优化,当运行时的修改被认为是不必的。

使用“autoproxy”能力

至此咱们已经考虑过使用一个ProxyFactoryBean或者类似的工厂类建立明确的AOP代理。

Spring容许咱们使用“autoproxy”bean定义,能够自动代理选择的bean定义。
这是创建在Spring“bean后处理器(BeanPostProcessor)”机制之上,这能够容许在容器加在后修改任何bean定义。

在这个模型上,你能够在bean定义的XML文件中设置一些特殊的bean定义,用以配置自动代理机制。
这容许你只须要声明符合代理条件的目标便可:你不须要使用 ProxyFactoryBean

有两种方式实现:

  • 在当前上下文中,使用一个指定了目标bean定义的自动代理建立器,
  • 一些特殊自动代理建立器须要分开考虑;由源码级元数据信息驱动的自动代理建立器。

自动代理Bean的定义

org.springframework.aop.framework.autoproxy包提供了如下标准自动代理建立器

BeanNameAutoProxyCreator

BeanNameAutoProxyCreator类是一个BeanPostProcessor,为纯文本或者通配符匹配出的命名为目标bean自动建立AOP代理。

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames"><value>jdk*,onlyJdk</value></property>
    <property name="interceptorNames">
        <list>
            <value>myInterceptor</value>
        </list>
    </property>
</bean>

ProxyFactoryBean同样,有一个interceptorNames属性,而不是一个列表拦截器,
确保原型advisor正确的访问方式。
命名为 “interceptors”,可使任何advisor或者任何类型的advice。

和一般的自动代理同样,使用BeanNameAutoProxyCreator的要领是,使用最小的配置量,将相同的配置一致地应用到多个对象上。

与bean定义匹配的命名,好比上面示例中的”jdkMyBean”和”onlyJdk”,就是目标类普通的原有bean定义。
一个AOP代理会被BeanNameAutoProxyCreator自动建立。相同的advice会被应用到全部匹配的bean上。
须要注意的是,被应用的advisor(不是上面示例中的拦截器),对不一样的bean可能使用不一样的切点。

DefaultAdvisorAutoProxyCreator

一个更通常且更强大的自动代理建立器是DefaultAdvisorAutoProxyCreator
在上下文中会自动应用符合条件的advisor,不须要在自动代理建立器的bean定义中指定目标对象的bean名称。
它也提供了相同的有点,一致的配置和避免重复定义BeanNameAutoProxyCreator

使用此机制涉及:

  • 指定一个DefaultAdvisorAutoProxyCreator bean定义。
  • 在相同或者相关的上下文中指定一系列的Advisor。须要注意的是,这些都必须是Advisor,而不只仅是拦截器或者其余的advice。
    这很必要,由于这里必须有评估的切点,以便检测候选bean是否符合每个advice。

DefaultAdvisorAutoProxyCreator会自动的评估包含在每个advisor中的切点,用以肯定每个业务对象须要应用的advice(就像示例中的 “businessObject1”和”businessObject2”)。

这意味着任意数量的advisor都能自动的应用到每个业务对象。
若是全部在advisor的切点都不能匹配一个业务对象中的任何方法,这个对象就不会被代理。
因为bean的定义都是添加给建立的业务对象。若是须要,它们都会被自动代理。

一般状况下,自动代理都有使调用方或者依赖方不能获取未被advise对象的优势。

在本ApplicationContext中调用getBean(“businessObject1”)会返回一个AOP代理,而不是目标业务对象(以前的“内部bean”也提供了这种优势)。

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>

<bean id="businessObject1" class="com.mycompany.BusinessObject1">
    <!-- Properties omitted -->
</bean>

<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>

若是你想对许多业务对象应用相同的advice,DefaultAdvisorAutoProxyCreator将会有所帮助。
一旦基础定义设置完成,你就能够简单添加新的业务对象,不须要特定的proxy配置。
你也能够轻松地添加其余切面。例如,使用最小的配置修改,添加跟踪或性能监控切面。

DefaultAdvisorAutoProxyCreator提供过滤(使用命名约定,以便只有特定的advisor被评估,容许在相同的工厂中使用多个、不一样的被配置的AdvisorAutoProxyCreator)和排序的支持。
Advisor能够实现org.springframework.core.Ordered接口,当顺序是一个问题时,确保正确的顺序。
在上面示例中使用的TransactionAttributeSourceAdvisor,有一个可配置的顺序值;默认配置是无序的。

AbstractAdvisorAutoProxyCreator

AbstractAdvisorAutoProxyCreator是DefaultAdvisorAutoProxyCreator的超类。
你能够经过继承这个类建立本身的自动代理建立器,
虽然这种状况微乎其微,advisor定义为DefaultAdvisorAutoProxyCreator框架的行为提供了有限的定制。

使用元数据驱动

一个特别重要的自动代理类型就是元数据驱动。这和 .NET的ServicedComponents编程模型相似。
事务管理和其余企业服务的配置在源码属性中保存,而不是像在EJB中同样使用XML部署描述符。

在这种状况下,你结合可以解读元数据属性的Advisor,使用DefaultAdvisorAutoProxyCreator
元数据细节存放在备选advisor的切点部分,而不是自动建立器类的自己中。

这其实是DefaultAdvisorAutoProxyCreator的一种特殊状况,但值得考虑(元数据感知代码在advisor切点中,而不是AOP框架自身上)。

JPetStore示例应用程序的/attributes目录,展现了属性驱动的使用方法。
在这种状况下,不必使用TransactionProxyFactoryBean
简单在业务对象上定义事务属性就足够了,由于使用的是元数据感知切点。
包含了下面代码的bean定义,在 /WEB-INF/declarativeServices.xml文件中。
须要注意的是这是通用的,也能够在JPetStore以外使用。

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="transactionInterceptor"
        class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributeSource">
        <bean class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource">
            <property name="attributes" ref="attributes"/>
        </bean>
    </property>
</bean>

<bean id="attributes" class="org.springframework.metadata.commons.CommonsAttributes"/>

DefaultAdvisorAutoProxyCreatorbean(命名不是重点,甚至能够省略)定义会获取全部在当前应用上下文中符合的切点。
在这种状况下,TransactionAttributeSourceAdvisor类星的”transactionAdvisor” bean定义,将适用于携带了事务属性的类或者方法。
TransactionAttributeSourceAdvisor经过构造函数依赖一个TransactionInterceptor对象。
本示例中经过自动装配解决该问题。
AttributesTransactionAttributeSource依赖一个org.springframework.metadata.Attributes接口的实现。
在本代码片断中,”attributes” bean知足这一点,使用Jakart aCommons Attributes API来获取属性信息。
(这个应用代码必须使用Commons Attributes编译任务编译)

JPetStore示例应用程序的/annotation目录包含了一个相似自动代理的示例,须要JDK 1.5版本以上的注解支持。
下面的配置能够自动检测Spring的Transactional,为包含该注解的bean配置一个隐含的代理。

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="transactionInterceptor"
        class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributeSource">
        <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>
    </property>
</bean>

这里定义的 TransactionInterceptor依赖一个PlatformTransactionManager定义,没有被包含在这个通用文件中(尽管能够包含),
由于它对应用的事务需求是定制的(一般是JTA,就像本示例,或者是Hibernate、JDBC):

<bean id="transactionManager"
        class="org.springframework.transaction.jta.JtaTransactionManager"/>

提示

若是你只须要声明式事务管理,使用这些通用的XML定义会致使Spring为全部包含事务属性的类或方法建立自动代理。
你不须要直接使用AOP,以及.NET和ServicedComponents类似的编程模型。

这种机制是可扩展的,能够基于通用属性自动代理。你须要:

  • 定义你本身的个性化属性。
  • 指定包含必要advice的Advisor,包括一个切点,该切点会被一个类或方法上存在的定义属性所触发。
    你也可使用一个已有的advice,仅仅实现了获取自定义属性的一个静态切点。

对每一个被advise的类,这样的advisor均可能是惟一的(例如mixins【待定】):
它们的bean须要被定义为prototype,而不是单例。
例如,Spring测试套件中的LockMixin引介拦截器,能够对一个mixin目标与一个属性驱动切点一块儿使用。
咱们使用通用的DefaultPointcutAdvisor,使用JavaBean属性进行配置。

<bean id="lockMixin" class="org.springframework.aop.LockMixin"
        scope="prototype"/>

<bean id="lockableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
        scope="prototype">
    <property name="pointcut" ref="myAttributeAwarePointcut"/>
    <property name="advice" ref="lockMixin"/>
</bean>

<bean id="anyBean" class="anyclass" ...

若是该属性感知切点匹配了anyBean或者其余bean定义的任何方法,这个mixin都会被应用。
须要注意的是 lockMixinlockableAdvisor都是prototype的。
myAttributeAwarePointcut切点能够是一个单例定义,由于它不会持有被advise对象的个性状态。

使用TargetSources

Spring提供了一个TargetSource概念,由org.springframework.aop.TargetSource接口所表示。
该接口负责返回实现了链接点的目标对象。
【待定】每次AOP代理处理一个方法调用时,TargetSource`实现都须要一个目标的实例。

开发人员使用Spring AOP一般不须要直接使用TargetSource,可是它提供了一个强大的供给池、热替换和其余复杂的目标。
例如,一个池化的TargetSource能够为每次调用返回不一样的目标示例,经过池子来管理这些实例。

若是你没有指定一个TargetSource,默认的实现手段是使用一个包装的本地对象。
每次调用返回的是同一个目标(如你所愿)。

让咱们看一个Spring提供的标准TargetSource,以及如何使用它们。

提示

当使用一个自定义的TargetSource时,你的目标一般是一个prototype bean定义,而不是单例bean定义。
这容许Spring在须要时建立一个新的目标实例。

热替换TargetSource

org.springframework.aop.target.HotSwappableTargetSource的存在,容许一个AOP代理的目标进行切换,同时容许调用者持有她们的引用。

修改TargetSource的目标对象会当即生效。HotSwappableTargetSource是线程安全的。

你能够以下所示,经过HotSwappableTargetSourceswap()方法修改目标对象:

HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);

须要参考的XML定义以下所示:

<bean id="initialTarget" class="mycompany.OldTarget"/>

<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
    <constructor-arg ref="initialTarget"/>
</bean>

<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource" ref="swapper"/>
</bean>

上面的调用的swap()方法,修改了swappable对象的目标对象。
持有这个bean引用的客户端对此更改毫无感知,可是会当即开始命中新的目标对象。

尽管这个示例没有添加任何advice,而且添加一个advice到一个使用的TargetSource中也是不必的,固然任何TargetSource均可以和任意的advice结合使用。

池化TargetSources

使用一个池化的TargetSource,提供了一个与无状态会话的EJB相似的编程模型,池子中维护了相同类型的实例,当方法调用时释放池子中的对象。

Spring池子和SLSB池子的关键区别在于,Spring池子能够适用于任意POJO类。一般和Spring同样,这个服务能够用非侵入性的方式使用。

Spring提供了对框架外Commons Pool 2.2的支持,Commons Pool 2.2提供了一个至关有效的池子实现。
使用该特性,须要在应用的classpath中加入commons-pool的jar包。
也能够继承org.springframework.aop.target.AbstractPoolingTargetSource,来支持任意其余的池子API。

说明

Commons Pool 1.5版本以上也被支持,不过在Spring Framework 4.2被弃用了。

示例配置以下所示:

<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject" scope="prototype">
    ... properties omitted
</bean>

<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
    <property name="maxSize" value="25"/>
</bean>

<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource" ref="poolTargetSource"/>
    <property name="interceptorNames" value="myInterceptor"/>
</bean>

须要主意的是目标对象,即本示例中的”businessObjectTarget”必须是prototype的。
这容许PoolingTargetSource在须要的时候建立为目标对象建立新的实例来扩张池子大小。
参考 AbstractPoolingTargetSource的Javadoc文档,以及你要使用的具体的子类属性信息:
“maxSize”是最基础的,须要保证它存在

在本示例中,”myInterceptor”是一个拦截器的名称,须要在同一个IoC上下文中被定义。
然而,不须要为使用的池子,指定拦截器。
若是你只须要池子,不须要任何advice,就不要设置interceptorNames属性。

也能够经过Spring配置将任意池化的对象强制转成org.springframework.aop.target.PoolingConfig接口,
经过一个引介,能够显示当前池子的配置和大小信息。
你须要定义一个像这样的advisor:

<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="poolTargetSource"/>
    <property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>

这个advisor经过调用AbstractPoolingTargetSource类中的一个方法方法获取,所以使用MethodInvokingFactoryBean。
这个advisor的命名(本示例中的”poolConfigAdvisor” )必须在ProxyFactoryBean暴露的池化对象的拦截器列表中。

强制转化以下所示:

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());

说明

池化无状态的服务实例一般是没必要要的。
咱们不认为这是默认选择,由于大多数的无状态对象天然是县城安全的,而且若是资源被缓存,实例池会存在问题。

简单的池子也可使用自动代理。可使用任何自动代理建立器设置 TargetSource。

Prototype类型的TargetSource

设置一个”prototype”的TargetSource和池化一个TargetSource是相似的。
在本示例中,当每一个方法调用时,都会建立一个目标的示例。
尽管在现代JVM中建立一个对象的成本不高,绑定一个新对象(知足IoC的依赖)可能花费更多。
所以,没有一个很好的理由,你不该该使用这个方法,。

为此,你能够修改上面定义的 poolTargetSource成以下所示(为了清楚起见,我也修改了命名):

<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
    <property name="targetBeanName" ref="businessObjectTarget"/>
</bean>

只有一个属性:目标bean的命名。在TargetSource实现中使用继承是为了确保命名的一致性。
与池化TargetSource同样,目标bean的定义也必须是prototype。

ThreadLocal的TargetSource

若是你须要为每个进来的请求(每一个线程一个那种)建立一个对象,那么ThreadLocal的TargetSource将会有所帮助。
JDK范畴提供ThreadLocal的概念是在线程上透明存储资源的能力。
创建一个ThreadLocalTargetSource对其余类型的TargetSource,与该概念十分类似:

<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
</bean>

说明

当在多线程和多classload环境中,不正确的使用ThreadLocal时会出现一些问题(潜在的结果是内存泄漏)。
应当始终考虑将 ThreadLocal封装在一些类(包装类除外)中,不能直接使用 ThreadLocal 自己。
一样的,应当始终记得为线程中的资源正确的使用set和unset(后者只涉及到调用一个ThreadLocal.set(null)方法)。
unset应当在任何状况都调用,由于不掉用unset可能会致使行为错误。
Spring的ThreadLocal支持该功能,应当始终考虑同意使用ThreadLocal,没有其余正确的处理代码【待定】。

定义新的Advice类型

Spring AOP被设计为可扩展的。
虽然拦截器实现策略目前是在内部使用的,可是它能够支持框架以外任意类型的advice(拦截式环绕加强、前置加强、异常抛出加强和后置加强)。

org.springframework.aop.framework.adapter包是一个SPI包,在不改动核心框架的状况下,支持添加新的自定义advice类型。

自定义Advice只有一个约束,就是必须实现org.aopalliance.aop.Advice标签接口。

请参阅org.springframework.aop.framework.adapter包的Javadoc文档,获取更多信息。

更多资源

有关Spring AOP的更多示例,请参阅Spring示例应用程序:

  • JPetStore的默认配置,展现了将TransactionProxyFactoryBean应用于声明式事务管理。
  • JPetStore的/attributes目录展现了使用属性驱动声明式事务管理。

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文连接地址: 《Spring 5官方文档》37. Spring AOP的经典用法


FavoriteLoading添加本文到个人收藏 原文地址:http://ifeve.com/classic-aop-spring/
相关文章
相关标签/搜索