Spring AOP APIs


上一章描述了Spring对带有 @Aspectj 和 schema-based 的⽅⾯定义的AOP的⽀持。在本章中,我们将讨论较低级别的 SpringAOP API。对于常⻅的应⽤程序,我们建议使⽤带有AspectJ切点的SpringAOP,如前⼀章所述。

一. Pointcut API

1.1 概念

Spring的切⼊点模型⽀持独⽴于增强类型的切⼊点重⽤。您可以使用相同的切入点来定位不同的增强(advice)。org.springframework.aop.Pointcut接⼝是⼀个中⼼接⼝,⽤于将增强定向到特定的类和⽅法。完整定义如下:

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();
}

将Pointcut接⼝拆分为两部分允许重⽤类和⽅法匹配部分以及细粒度组合操作(例如,使⽤另⼀个⽅法匹配器执⾏“联 合”)。

ClassFilter接⼝⽤于将切⼊点限制为⼀组给定的⽬标类。如果matches()⽅法始终返回true,则所有⽬标类都匹配。下 ⾯的列表显示ClassFilter接⼝定义:

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代理时可以执⾏此评估,以避免对每个⽅法调⽤进⾏测试。如果两个参数匹配的⽅法对于给定的⽅法返回true,⽽methodMatcher isRuntime()⽅法返回true,则在每次⽅法调⽤时都会调⽤三个参数匹配的⽅法。这允许切⼊点查看在执⾏⽬标通知之 前⽴即传递给⽅法调⽤的参数。

⼤多数MethodMatcher实现是静态的,这意味着它们的isRuntime()⽅法返回false。在本例中,从不调⽤三个参数 matches⽅法。

如果可能,尝试使切⼊点静态化,允许AOP框架在创建AOP代理时缓存切⼊点评估的结果。

1.2 Pointcuts的操作

Spring支持的对切入点的操作(尤其是unionintersection)。

联合(Union)是指切⼊点匹配任意⼀个⽅法。联合(intersection)表示切⼊点同时匹配的⽅法。Union通常更有⽤。你可以使⽤ org.springframework.aop.support.Pointcuts类中的静态⽅法或在同⼀个包中使⽤ComposablePointcut类来撰写切⼊ 点。然⽽,使⽤AspectJ切⼊点表达式通常是⼀种更简单的⽅法。

1.3 AspectJ表达式Pointcuts

从2.0开始,Spring使⽤的最重要的切⼊点类型是org.springframework.aop.aspectj.AspectJExpressionPointcut。这是⼀个切⼊点,它使⽤AspectJ提供的库来分析AspectJ切⼊点表达式字符串。

1.4 方便的Pointcut实现

Spring提供了⼏个⽅便的切⼊点实现。你可以直接使⽤其中的⼀些。其他的则被⽤在特定于应⽤程序的切⼊点中进⾏⼦类化。

1.4.1 静态切入点

静态切⼊点基于⽅法和⽬标类,不能考虑⽅法的参数。静态切⼊点对于⼤多数使⽤来说是最好的。当第⼀次调⽤⽅法时,Spring可以只计算⼀次静态切⼊点。之后,不需要对每个⽅法调⽤再次计算切⼊点。

正则表达式切入点

指定静态切入点的一种明显方法是正则表达式。除了Spring之外,还有几个AOP框架使之成为可能。 org.springframework.aop.support.JdkRegexpMethodPointcut是通用的正则表达式切入点,它使用JDK中的正则表达式支持。

使用JdkRegexpMethodPointcut类,可以提供模式字符串的列表。如果其中任何一个匹配,则切入点的计算结果为true。(因此,最终的切入点实际上是指定模式的并集。)

下⾯的示例演示如何使⽤JdkRegexpMethodPointcut

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

Spring提供了一个名为的便捷类RegexpMethodPointcutAdvisor,它使我们也可以引用Advice(Advice可以是interceptor,before advice, throws advice 以及其他)。在后台,Spring使用JdkRegexpMethodPointcut。使⽤ RegexpMethodPointcutAdvisor简化了连接,因为⼀个bean同时封装了pointcut和advice,如下示例所示:

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

你可以在任何Advice类型中使⽤RegexpMethodPointcutAdvisor

属性驱动Pointcuts

⼀种重要的静态切⼊点类型是元数据驱动的切⼊点。这将使⽤元数据属性的值(通常是源级元数据)。

1.4.2 动态切入点

动态切⼊点的计算成本⽐静态切⼊点⾼。它们考虑了⽅法参数和静态信息。这意味着必须⽤每个⽅法调⽤对它们进⾏评估,并且不能缓存结果,因为参数会有所不同。

主要的例⼦是控制流切⼊点。

控制流切入点

Spring控制流切⼊点在概念上与AspectJ CFlow切⼊点相似,但功能较弱。(⽬前⽆法指定在与另⼀个切⼊点匹配的连接点下⽅执⾏切⼊点。)控制流切⼊点与当前调⽤堆栈匹配。例如,如果连接点是由com.mycompany.web包中的⽅法 或SomeCaller类调⽤的,则它可能会激发。通常使⽤org.springframework.aop.support.ControlFlowPointcut类指定控制流切⼊点。

控制流切⼊点在运⾏时的计算成本⽐其他动态切⼊点都要⾼。在Java 1.4中,成本是其他动态切⼊点的五倍。

1.5 Pointcut父类

Spring提供了有⽤的切⼊点超类来帮助你实现⾃⼰的切⼊点。

因为静态切⼊点最有⽤,所以你可能应该将StaticMethodMatcherPointCut⼦类化。这只需要实现⼀个抽象⽅法(尽管 你可以重写其他⽅法来定制⾏为)。下⾯的示例显示如何将StaticMethodMatcherPointCut⼦类化:

class TestStaticPointcut extends StaticMethodMatcherPointcut {

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

还有动态切⼊点的超类。你可以对任何通知类型使⽤⾃定义切⼊点。

1.6 自定义Pointcuts

因为Spring AOP中的切⼊点是Java类,⽽不是语⾔特征(如AspectJ中),所以可以声明⾃定义切⼊点,⽆论是静态的还是动态的。Spring中的⾃定义切⼊点可以任意复杂。但是,如果可以的话,我们建议使⽤AspectJ切⼊点表达式语⾔。

Spring的较新版本可能⽀持jac-提供的“语义切⼊点”,例如,“所有更改⽬标对象中实例变量的⽅法”。

二. Advice API

下⾯我们看⼀下Spring AOP怎么处理advice。

2.1 Advice生命周期

每个Advice都是⼀个Spring beanAdvice实例可以在所有Advice对象之间共享,也可以对每个Advice对象都是唯⼀的。这对应于每个类或每个实例的通知。

Per-class advice最常⽤。它适⽤于⼀般的advice,如事务advisors。这些不依赖于代理对象的状态或添加新状态。他们 只根据⽅法和参数⾏事。

Per-instanceadvice都适⽤于introductions,以⽀持混合。在这种情况下,建议将状态添加到代理对象。

你可以在同⼀个AOP代理中混合使⽤共享和基于实例的建议。

2.2 Advice类型

Spring提供了⼏种Advice类型,并且可以扩展以⽀持任意Advice类型。本节介绍基本概念和标准Advice类型。

2.2.1 环绕拦截Advice

Spring最基本的建议类型是围绕建议进⾏拦截。

Spring与AOP联盟接⼝兼容,以获取使⽤⽅法拦截的 around advice。实现MethodInterceptor和实现around advice的类还应实现以下接⼝:

public interface MethodInterceptor extends Interceptor {
    Object invoke(MethodInvocation invocation) throws Throwable;
}

invoke()⽅法的methodInvocation参数公开了被调⽤的⽅法、⽬标连接点、AOP代理以及该⽅法的参数。invoke()⽅法应返回调⽤的结果(连接点的返回值)

下⾯的示例显示了⼀个简单的SystemInterceptor实现:

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()⽅法的调⽤。这将沿着拦截器链向连接点前进。⼤多数拦截器调⽤此⽅法并返回其返回值。但是,与任何around通知⼀样,MethodIntercetor可以返回不同的值或引发异常,⽽不是调⽤proceed⽅法。 但是,如果没有充分的理由,你不想这样做。

MethodIntercetor实现提供与其他AOP联盟兼容的AOP实现的互操作性。本节其余部分讨论的其他建议类型以特 定于Spring的⽅式实现了常⻅的AOP概念。虽然在使⽤最具体的通知类型⽅⾯有⼀个优势,但是如果你可能希望 在另⼀个AOP框架中运⾏该⽅⾯,那么请坚持使⽤围绕通知的MethodInterceptor。注意,切⼊点⽬前在框架之间不可互操作,AOP联盟⽬前没有定义切⼊点接⼝。

2.2.2 Before Advice

更简单的建议类型是before建议。这不需要MethodInvocation对象,因为它只在进⼊⽅法之前被调⽤。

before通知的主要优点是不需要调⽤proceed()⽅法,因此,不可能⽆意中中断拦截器链。

下⾯的列表显示了MethodBeforeAdvice接⼝:

public interface MethodBeforeAdvice extends BeforeAdvice {

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

(Spring的API设计将允许在建议之前提供字段,尽管通常的对象适⽤于字段拦截,但是Spring不太可能实现它。)

请注意,返回类型为void。before advice可以在连接点执⾏之前插⼊⾃定义⾏为,但不能更改返回值。如果before增强抛出异常,它将中⽌拦截器链的进⼀步执⾏。异常向上传播到拦截器链。如果他是unchecked或在被调⽤⽅法的签名上,则直接传递给客户端。否则,AOP代理会将其包装在unchecked exception中。

下⾯的示例显示了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 advice可以⽤在任意pointcut中。

2.2.3 Throws Advice

如果连接点引发异常,则在返回连接点后调⽤throws通知。Spring提供 typed throws advice。注意,这意味着 org.springframework.aop.ThrowsAdvice接⼝不包含任何⽅法。它是⼀个标记接⼝,标识给定对象实现⼀个或多个类型化的throw advice⽅法。应采⽤以下形式:

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

只需要最后⼀个参数。⽅法签名可以有⼀个或四个参数,这取决于advice⽅法对⽅法和参数是否感兴趣。下⾯的两个列 表显示了属于throw建议示例的类。

如果抛出了RemoteException(包括⼦类),将调⽤以下建议:

public class RemoteThrowsAdvice implements ThrowsAdvice {

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

与前⾯的建议不同,下⼀个示例声明了四个参数,这样它就可以访问被调⽤的⽅法、⽅法参数和⽬标对象。如果抛出 servletexception,将调⽤以下通知:

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

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

最后⼀个示例说明了这两个⽅法如何在处理RemoteExceptionServletException的单个类中使⽤。可以在单个类中组合任意数量的throw advice⽅法。下⾯的列表显示了最后⼀个示例:

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

如果throws advice⽅法本身引发异常,它将重写原始异常(即,它将更改引发给⽤户的异常)。重写异常通常是 RuntimeException,它与任何⽅法签名都兼容。但是,如果throw advice⽅法抛出⼀个受检(checked)异常,它必须与⽬标⽅法的声明异常匹配,因此在某种程度上与特定的⽬标⽅法签名耦合。不要抛出与⽬标⽅法的签名不兼容的未声明的已检查异常!

Throws advice可以在任何pointcut使⽤。

2.2.4 After Returning Advice

在Spring中after returning advice,必须实现org.springframework.aop.AfterReturningAdvice接⼝,如下所示:

public interface AfterReturningAdvice extends Advice {

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

after returning advice可以访问返回值(它不能修改)、调⽤的⽅法、⽅法的参数和⽬标。

返回通知后,以下内容将统计所有未引发异常的成功⽅法调⽤:

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 advice可以⽤在任何pointcut。

2.2.5 Introduction Advice

SpringIntroduction Advice视为⼀种特殊的拦截AdviceIntroduction需要⼀个IntroductionAdvisor和⼀个实现以下接⼝的IntroductionInterceptor

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}

AOP Alliance MethodIntercetor接⼝继承的invoke()⽅法必须实现introduction。也就是说,如果被调⽤的⽅法在引⼊的接⼝上,则引⼊拦截器负责处理⽅法调⽤-它不能调⽤proceed()

Introduction advice不能与任何切⼊点⼀起使⽤,因为它只适⽤于类,⽽不是⽅法级别。你只能将 introduction advice IntroductionAdvisor⼀起使⽤,其⽅法如下:

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class<?>[] getInterfaces();
}

没有methodMatcher,因此没有与 introduction advice 相关联的切⼊点。只有类筛选是合乎逻辑的。

getInterfaces()⽅法返回此advisor引⼊的接⼝。

validateInterfaces()⽅法在内部⽤于查看所引⼊的接⼝是否可以由配置的IntroductionInterceptor实现。

考虑⼀下Spring测试套件中的⼀个示例,假设我们要将以下接⼝引⼊⼀个或多个对象:

public interface Lockable {
    void lock();
    void unlock();
    boolean locked();
}

这⾥展示⼀个mixin。我们希望能够将建议的对象强制转换为Lockable的对象,⽆论其类型如何,并调⽤锁定和解锁⽅法。如果调⽤lock()⽅法,我们希望所有setter⽅法都抛出lockedException。因此,我们可以添加⼀个⽅⾯,使对象在不了解它的情况下保持不变:AOP的⼀个很好的例⼦。

⾸先,我们需要⼀个IntroductionInterceptor来完成这项⼯作。在本例中,我们扩展了 org.springframework.aop.support.DelegatingIntroductionInterceptor便利类。我们可以直接实现 IntroductionInterceptor,但对于⼤多数情况,使⽤DelegatingIntroductionInterceptor是最好的。

DelegingIntroductionInterceptor设计⽤于将introduction委托给所引⼊接⼝的实际实现,从⽽隐藏使⽤拦截来完成此操作。可以使⽤构造函数参数将委托设置为任何对象。默认委托(使⽤⽆参数构造函数时)是this。因此,在下⼀个示例中,委托是DelegatingIntroductionInterceptorLockMixin⼦类。对于委托(默认情况下,它本身),DelegatingIntroductionInterceptor实例查找委托实现的所有接⼝(⽽不是IntroductionInterceptor),并⽀持针对其中任何⼀个进⾏introductionsLockMixin等⼦类可以调⽤SuppressInterface(Class Intf)⽅法来抑制不应公开的接 ⼝。然⽽,不管⼀个IntroductionInterceptor准备⽀持多少个接⼝,IntroductionAdvisor被⽤来控制哪些接⼝实际上被公开。引⼊的接⼝隐藏了⽬标对同⼀接⼝的任何实现。

因此,LockMixin扩展了DelegatingIntroductionInterceptor并实现了Lockable接⼝。超类会⾃动选取可以⽀持introductionLockable,所以我们不需要指定它。我们可以⽤这种⽅式引⼊任意数量的接⼝。

注意使⽤locked的实例变量。这有效地为⽬标对象中保存的状态添加了额外的状态。

下⾯的示例显示了LockMixin类的示例:

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实现(如果引⼊了该⽅法,则调⽤delegate⽅法,否则将进⼊连接点)通常就⾜够了。

在本例中,我们需要添加⼀个检查:如果处于锁定模式,则不能调⽤setter⽅法。

所需的introduction只需要保存⼀个不同的LockMixin实例并指定引⼊的接⼝(在本例中,仅Lockable)。⼀个更复杂的例⼦可能会引⽤介绍拦截器(将被定义为原型)。在这种情况下,没有与LockMixin相关的配置,因此我们使⽤new创 建它。下⾯的示例显示了我们的LockMixinAdvisor类:

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

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

我们可以⾮常简单地应⽤这个advisor,因为它不需要配置。(然⽽,在没有IntroductionAdvisor的情况下,不可能使⽤ IntroductionInterceptor。)与通常的introductions⼀样,advisor是有状态的。对于每个建议的对象,我们需要⼀个不同的LockMixinAdvisor实例,因此需要LockMixinadvisor是被advised对象状态的⼀部分。

我们可以通过在XML配置中使⽤advised.addAdvisor()⽅法或(推荐的⽅法)以编程⽅式应⽤此Advisor,就像任何其他Advisor⼀样。下⾯讨论的所有代理创建选项,包括“auto-proxy creators”,正确处理introductionsstateful 的混 合。

三. Advisor API

Spring中,Advisor是⼀个⽅⾯,它只包含与切⼊点表达式关联的单个advice对象。

除了介绍的特殊情况外,任何advisor都可以与任何advice⼀起使⽤。 org.springframework.aop.support.DefaultPointcutAdvisor是最常⽤的Advisor类。它可以与MethodInterceptorBeforeAdviceThrowsAdvice⼀起使⽤。

在同⼀个AOP代理中,可以在Spring中混合AdvisorAdvice类型。例如,在⼀个代理配置中,你可以使⽤ interception around advice,抛出通知,并在通知之前使⽤拦截。Spring⾃动创建必要的拦截器链。

四. 使用ProxyFactoryBean来创建AOP代理

如果将Spring IOC容器(applicationContextbeanFactory)⽤于业务对象,你希望使⽤Spring的AOP FactoryBean实现之⼀。(请记住,⼯⼚bean引⼊了⼀个间接层,允许它创建不同类型的对象。)

SpringAOP⽀持还使⽤了⼯⼚级的bean。

在Spring中创建AOP代理的基本⽅法是使⽤org.springframework.aop.framework.ProxyFactoryBean。这可以完全控制切⼊点、任何适⽤的advice以及它们的顺序。但是,如果你不需要这样的控制,可以选择更简单的选项。

4.1 基础

ProxyFactoryBean与其他Spring FactoryBean实现⼀样,引⼊了⼀个间接级别。如果定义⼀个名为foo的 ProxyFactoryBean,则引⽤foo的对象不会看到ProxyFactoryBean实例本身,⽽是由ProxyFactoryBeangetobject() ⽅法的实现创建的对象。此⽅法创建包装⽬标对象的AOP代理。

使⽤ProxyFactoryBean或其他具有IOC意识的类来创建AOP代理最重要的好处之⼀是,通知和切⼊点也可以由IOC管理。这是⼀个强⼤的特性,使得某些⽅法很难⽤其他AOP框架实现。例如,⼀个建议本身可以引⽤应⽤程序对象(除了⽬标之外,⽬标应该在任何AOP框架中都可⽤),这得益于依赖注⼊提供的所有可插⼊性。

4.2 JavaBean属性

与Spring提供的⼤多数FactoryBean实现相同,proxyFactoryBean类本身就是⼀个JavaBean。其属性⽤于:

指定要代理的⽬标。 指定是否使⽤cglib(稍后介绍,另请参阅基于jdk和cglib的代理)。

⼀些关键属性继承⾃org.spring framework.aop.framework.ProxyConfig(Spring中所有AOP代理⼯⼚的超类)。这些关键属性包括:

  • proxyTargetClass:如果要代理⽬标类,⽽不是⽬标类的接⼝,则为true。如果将此属性值设置为true,则会创建 CGLIB代理(也可参⻅基于JDK和CGLIB的代理)。
  • 优化(optimize):控制是否对通过cglib创建的代理应⽤积极的优化。除⾮你完全理解相关AOP代理如何处理优化,否则不应随意使⽤此设置。这⽬前仅⽤于cglib代理。它对JDK动态代理没有影响。
  • 冻结(frozen):如果代理配置被冻结,则不再允许更改配置。这对于轻微的优化和不希望调⽤⽅能够在创建代理后(通过建议的接⼝)操纵代理的情况都很有⽤。此属性的默认值为false,因此允许进⾏更改(例如添加其他通知)。
  • ExposeProxy:确定当前代理是否应在ThreadLocal中公开,以便⽬标可以访问它。如果⽬标需要获取代理,并且ExposeProxy属性设置为true,则该⽬标可以使⽤AopContext.currentProxy()⽅法。

ProxyFactoryBean特有的其他属性包括:

  • proxyInterfaces:字符串接⼝名称的数组。如果没有提供,将使⽤⽬标类的cglib代理(也可参⻅基于jdk和cglib的代理)。
  • 拦截器(interceptorNames):要应⽤的Advisorinterceptor或其他advice名称的字符串数组。Ordering⾮常重要,以先到先得的⽅式提供。也就是说,列表中的第⼀个拦截器是第⼀个能够拦截调⽤的拦截器。名称是当前⼯⼚中的bean名称,包括来⾃祖先⼯⼚的bean名称。这⾥不能提到bean引⽤,因为这样做会导致ProxyFactoryBean忽略通知的singleton设置。你可以附加⼀个带有星号(*)的拦截器名称。这样做会导致所有AdvisorBean的应⽤,其名称以要应⽤的星号前⾯的部分开头。你可以在使⽤“全局”Advisor中找到使⽤此功能的示例。
  • singleton:⼯⼚是否应该返回单个对象,⽆论调⽤getObject()⽅法的频率如何。⼀些FactoryBean实现提供了这样的 ⽅法。默认值为true。如果你想使⽤有状态的建议(例如,对于有状态的混合),可以使⽤原型建议和单例值false。

4.3 JDK和基于CGLIB代理

本节是关于ProxyFactoryBean如何选择为特定⽬标对象(要代理)创建基于JDK的代理或基于CGLIB的代理的最终⽂档。

ProxyFactoryBean在创建基于jdk或cglib的代理⽅⾯的⾏为在Spring的1.2.x和2.0版本之间发⽣了变化。在⾃动检测接⼝⽅⾯,ProxyFactoryBean现在显示出与TransactionProxyFactoryBean类相似的语义。

如果要代理的⽬标对象的类(以下简称为⽬标类)不实现任何接⼝,则创建基于CGLIB的代理。这是最简单的场景,因为JDK代理是基于接⼝的,没有接⼝意味着JDK代理甚⾄不可能实现。你可以插⼊⽬标bean并通过设置interceptorNames属性指定拦截器列表。注意,即使ProxyFactoryBeanproxyTargetClass属性设置为false,也会创建基于cglib的代理。(这样做毫⽆意义,最好从bean定义中删除,因为它充其量是多余的,最坏的情况是令⼈困惑。)

如果⽬标类实现⼀个(或多个)接⼝,则创建的代理的类型取决于proxyFactoryBean的配置。

如果proxyFactoryBeanproxyTargetClass属性设置为true,则会创建基于cglib的代理。这是有道理的,符合最不令⼈ 惊讶的原则。即使proxyFactoryBeanproxyInterfaces属性已设置为⼀个或多个完全限定的接⼝名称, proxyTargetClass属性设置为true的事实也会导致基于cglib的代理⽣效。

如果proxyFactoryBeanproxyInterfaces属性已设置为⼀个或多个完全限定的接⼝名称,则将创建基于JDK的代理。创建的代理实现在proxyInterfaces属性中指定的所有接⼝。如果⽬标类恰好实现了⽐proxyInterfaces属性中指定的接⼝多得多的接⼝,那么这⼀切都很好,但是这些附加接⼝不是由返回的代理实现的。

如果尚未设置proxyfactorybeanproxyInterfaces属性,但⽬标类确实实现了⼀个(或多个)接⼝,则proxyfactorybean会⾃动检测到⽬标类确实实现了⾄少⼀个接⼝,并创建了基于JDK的代理。实际代理的接⼝是⽬标类实现的所有接⼝。 实际上,这与向proxyInterfaces属性提供⽬标类实现的每个接⼝的列表相同。然⽽,它的⼯作量明显减少,⽽且不容易出现排版错误。

4.4 代理接口

考虑⼀个简单的ProxyFactoryBean实例。这个例⼦涉及:

  • 代理的⽬标bean。这是示例中的PersonTarget bean定义。
  • Advisor和Interceptor用来提供增强。
  • AOP代理bean定义,⽤于指定⽬标对象(PersonTarget bean),代理接口以及要应用的增强

下⾯的列表显示了示例:

<bean id="personTarget" class="com.mycompany.PersonImpl">
    <property name="name" value="Tony"/>
    <property name="age" value="51"/>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</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"/>

    <property name="target" ref="personTarget"/>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

注意,interceptorNames属性采⽤⼀个字符串列表,其中包含当前⼯⼚中拦截器或advisors的bean名称。你可以在返回之前、之后使⽤顾问、拦截器,并抛出advice对象。advisors的ordering意义重⼤。

你可能想知道为什么列表不包含bean引⽤。原因是,如果proxyfactorybeansingleton属性设置为false,它必须能够返回独⽴的代理实例。如果任何顾问本身是prototype,则需要返回⼀个独⽴的实例,因此需要能够从⼯⼚获取prototype的实例。持有reference是不够的。

前⾯显示的PersonBean定义可以⽤来代替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代理来代替“真实的”⼈员实现。但是,它的类将是动态代理类。可以将其转换为Advised接⼝(稍后讨论)。

你可以使⽤匿名内部bean隐藏⽬标和代理之间的区别。只有ProxyFactoryBean定义不同。使⽤该advice仅为了完整性。下⾯的示例演示如何使⽤匿名内部bean:

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</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"/>
    <!-- Use inner bean, not local reference to target -->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name" value="Tony"/>
            <property name="age" value="51"/>
        </bean>
    </property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

使⽤匿名内部bean的优点是只有⼀个person类型的对象。如果我们希望防⽌应⽤程序上下⽂的⽤户获得对un-advised对 象的引⽤,或者需要避免SpringIOC autowiring的任何模糊性,那么这是⾮常有⽤的。也可以说,proxyfactorybean定义是独⽴的,这是⼀个优势。但是,有时能够从⼯⼚获得un-advised的⽬标实际上是⼀种优势(例如,在某些测试场景 中)。

4.5 代理Classes

如果你需要代理⼀个类,⽽不是⼀个或多个接⼝呢?

假设在前⾯的示例中,没有Person接口。我们需要增强⼀个名为person的类,该类没有实现任何业务接⼝。在这种情况 下,可以将spring配置为使⽤cglib代理,⽽不是动态代理。为此,请将前⾯显示的proxyFactoryBean上的proxyTargetClass属性设置为true。虽然最好是编程到接⼝⽽不是类,但是在处理遗留代码时,advise不实现接⼝的类 的能⼒是⾮常有⽤的。(⼀般来说,Spring不是规定性的。虽然它使应⽤好的实践变得容易,但它避免了强制使⽤特定 的⽅法。)

如果你愿意,你可以在任何情况下强制使⽤cglib,即使你确实有接⼝。

CGLIB代理通过在运⾏时⽣成⽬标类的⼦类来⼯作。Spring将⽣成的⼦类配置为将⽅法调⽤委托给原始⽬标。⼦类⽤于 实现装饰器模式,编织在advice中。

cglib代理通常对⽤户是透明的。但是,有⼀些问题需要考虑:

  • ⽆法通知Final⽅法,因为它们不能被重写。
  • 不需要将cglib添加到类路径中。从Spring3.2开始,cglib被重新包装并包含在spring-core Jar中。换句话说,基于 cglib的AOP和JDK动态代理⼀样“开箱即⽤”。

CGLIB代理和动态代理的性能差别不⼤。在这种情况下,性能不应是决定性的考虑因素。

4.6 使用全局的Advisors

通过向拦截器名称添加星号,所有具有与星号之前部分匹配的bean名称的advisor都将添加到advisor链中。如果你需要添加⼀组标准的“全局”Advisor,这将⾮常有⽤。以下示例定义了两个全局Advisors:

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

五. 简洁的代理定义

特别是当你对transactional代理下定义时,你可以⽤⼤量类似的代理定义来结束。⽗类和⼦类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>

上⾯的类并没有实例化,所以他并不是完整的。然后每个需要被创建的代理都是它的⼦类,它包装了定义为内部类的代理target,因为该target不会以他⾃⼰的⽅式使⽤。下⾯是⼀个⼦类的例⼦:

<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为true,如上⾯所示,所以他并没有被初始化,Application contexts(⽽不是简单的bean factories),会默认初始化所有的singletons。因此,如果你有⼀个⽗bean的定义,你打算只把他作 模板,并且他是⼀个类,那么你必须将他的abstract属性设置为true。否则application context 会尝试预先实例化他。

六. 使用ProxyFactory创建AOP代理

⽤Spring编程创建AOP代理很容易。这允许你使⽤SpringAOP⽽不依赖SpringIOC。 由⽬标对象实现的接⼝被⾃动代理。下⾯的列表显示使⽤⼀个拦截器和⼀个顾问为⽬标对象创建代理:

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

第⼀步是构造org.springframework.aop.framework.proxyFactory类型的对象。你可以使⽤⽬标对象创建此对象,如前⾯的示例所示,或者指定要在备⽤构造函数中代理的接⼝。 你可以添加建议(advices)(拦截器作为⼀种专⻔的建议)、顾问(advisors),或者两者兼⽽有之,并在代理⼯⼚的⽣命周期中操纵它们。如果添加⼀个IntroductionInterceptionAroundAdvisor,则可以使代理实现其他接⼝。ProxyFactory(继承⾃AdvisedSupport)上还有⼀些⽅便⽅法,允许你添加其他通知类型,如beforethrow adviceAdvisedSupportProxyFactoryProxyFactoryBean的超类。

在⼤多数应⽤程序中,将AOP代理创建与IOC框架集成是最佳实践。我们建议你⼀般⽤AOP将Java代码的配置外部化。

七. 操作被通知的对象

但是,你可以通过使⽤org.springframework.aop.framework.advised接⼝来创建AOP代理,从⽽对其进⾏操作。任何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将它包装在⼀个顾问中,并使⽤⼀个始终返回true的切⼊点。因此,如果添加了⼀个MethodInterceptor,返回给该索引的顾问是一个DefaultPointcutAdvisor,它返回您的MethodInterceptor和一个与所有类和方法匹配的切入点。

addAdvisor()⽅法可⽤于添加任何Advisor。通常,持有切⼊点和建议的AdvisorDefaultPointcutAdvisor,你可以将其⽤于任何建议或切⼊点(但不⽤于introductions)。

默认情况下,即使创建了代理,也可以添加或删除顾问(advisors)或拦截器。唯⼀的限制是不可能添加或删除简介顾问(introduction advisor),因为⼯⼚的现有代理不显示接⼝更改。(你可以从⼯⼚获取新代理以避免此问题。)

下⾯的示例显示将AOP代理强制转换到Advised的接⼝,并检查和操作其advice:

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

尽管⽆疑存在合法的使⽤案例,但是否建议(不打算使⽤双关语)修改⽣产中业务对象的建议仍然存在疑问。但是,它在开发中⾮常有⽤(例如,在测试中)。我们有时发现,能够以拦截器或其他建议的形式添加测试代码, 进⼊我们想要测试的⽅法调⽤中是⾮常有⽤的。(例如,在将事务标记为回滚之前,建议可以进⼊为该⽅法创建的事务中,或者运⾏SQL检查数据库是否正确更新。)

根据创建代理的⽅式,通常可以设置frozen标志。在这种情况下,Advised isfrozen()⽅法返回true,任何通过添加或删除来修改通知的尝试都会导致AopConfigException。在某些情况下(例如,为了防⽌调⽤代码删除安全拦截器),冻结建议对象的状态的功能⾮常有⽤。

八. 使用auto-proxy功能

到⽬前为⽌,我们已经考虑通过使⽤ProxyFactoryBean或类似的⼯⼚bean显式地创建AOP代理。

Spring还允许我们使⽤auto-proxy bean定义,它可以⾃动代理选定的bean定义。这是在Spring的bean post processor基础设施上构建的,它⽀持在容器加载时修改任何bean定义。

在这个模型中,你在XML bean定义⽂件中设置了⼀些特殊的bean定义,以配置⾃动代理基础结构。这允许你声明符合⾃动代理条件的⽬标。你不需要使⽤ProxyFactoryBean。

有两种⽅法可以做到这⼀点:

  • 通过使⽤在当前上下⽂中引⽤特定bean的⾃动代理创建者。
  • ⼀个需要单独考虑的⾃动代理创建的特殊情况:由源代码级元数据属性驱动的⾃动代理创建。

8.1 Auto-proxy Bean定义

本节介绍由org.springframework.aop.framework.autoproxy包提供的⾃动代理创建者。

8.1.1 BeanNameAutoProxyCreator

BeannameAutoProxyCreator类是⼀个BeanPostProcessor,它⾃动为名称与⽂字值或通配符匹配的Bean创建AOP代理。下⾯的示例演示如何创建BeanNameAutoProxyCreator bean:

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

ProxyFactoryBean⼀样,有⼀个interceptorNames属性⽽不是⼀个拦截器列表,以允许原型顾问(prototype advisors)的正确⾏为。命名的 “拦截器”可以是顾问(advisors)或任何增强(advice)类型。

与⼀般的⾃动代理⼀样,使⽤BeanNameAutoProxyCreator的主要⽬的是将相同的配置⼀致地应⽤于多个对象,配置量最⼩。它是将声明性事务应⽤于多个对象的常⽤选择。

名称匹配的bean定义(如前⾯示例中的jdkMyBeanonlyjdk)是带有⽬标类的普通旧bean定义。AOP代理由 BeanNameAutoProxyCreator⾃动创建。同样的建议也适⽤于所有匹配的bean。注意,如果使⽤advisors(⽽不是前⾯ 示例中的拦截器),则切⼊点可能会不同地应⽤于不同的bean。

8.2 DefaultAdvisorAutoProxyCreator

⼀个更通⽤和强⼤的⾃动代理创建者是DefaultAdvisorAutoProxyCreator。这将在当前上下⽂中⾃动应⽤合格的 advisors,⽽不需要在⾃动代理顾问的bean定义中包含特定的bean名称。它提供了与BeanNameAutoProxyCreator相同的配置和避免重复的优点。

使⽤此机制涉及:

  • 指定DefaultAdvisorAutoProxyCreator bean定义
  • 在相同或相关的上下⽂中指定任意数量的顾问(advisors)。请注意,这些必须是顾问(advisors),⽽不是拦截器或其他建议(advices)。这是必要的,因为必须有⼀个切⼊点来评估,以检查每个建议对候选bean定义的合格性。

DefaultAdvisorAutoProxyCreator⾃动评估每个顾问(advisor)中包含的切⼊点,以查看它应该应⽤于每个业务对象(如示例中的 businessObject1和businessObject2)的建议。

这意味着任何数量的顾问(advisors)都可以⾃动应⽤于每个业务对象。如果任何顾问(advisors)中没有与业务对象中的任何⽅法匹配的切⼊ 点,则不会代理该对象。当为新的业务对象添加bean定义时,如果需要,它们将被⾃动代理。

⼀般来说,⾃动代理的优点是使调⽤者或依赖项⽆法获得未建议(un-advised)的对象。在此applicationContext上调⽤ getBean("businessObject1") 将返回AOP代理,⽽不是⽬标业务对象。(前⾯显示的“内部bean”也提供了这个好处。)

下⾯的示例创建⼀个DefaultAdvisorAutoProxyCreatorbean和本节讨论的其他元素:

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

如果你希望将相同的建议⼀致地应⽤于许多业务对象,那么DefaultAdvisorAutoProxyCreator⾮常有⽤。⼀旦基础结构定义就位,就可以添加新的业务对象,⽽不包括特定的代理配置。你还可以通过对配置的最⼩更改轻松地进⼊其他⽅⾯ (例如,跟踪或性能监视⽅⾯)。

DefaultAdvisorAutoProxyCreator⽀持过滤(通过使⽤命名约定,只评估某些advisors ,从⽽允许在同⼀⼯⼚中使⽤多个不同配置的AdvisorAutoProxyCreators)和排序。Advisors可以实现org.springframework.core.Ordered接⼝,以确保在出现问题时正确排序。在前⾯的示例中使⽤的TransactionAttributeSourceAdvisor具有可配置的order值。默认设置是⽆序的。

九. 使⽤TargetSource的实现

Spring提供了TargetSource的概念,⽤org.springframework.aop.TargetSource接⼝表示。此接⼝负责返回实现连接点的“⽬标对象”。每次AOP代理处理⽅法调⽤时,都会要求TargetSource实现⼀个⽬标实例。 使⽤Spring AOP的开发⼈员通常不需要直接使⽤TargetSource实现,但这提供了⽀持池、热交换和其他复杂⽬标的强⼤⽅法。例如,池⽬标源可以通过使⽤池来管理实例,为每次调⽤返回不同的⽬标实例。 如果不指定TargetSource,则使⽤默认实现包装本地对象。每次调⽤都会返回相同的⽬标(如你所期望的那样)。 本节的其余部分描述了Spring提供的标准⽬标源以及如何使⽤它们。

本节的其余部分描述了Spring提供的标准⽬标源以及如何使⽤它们。

当使⽤⾃定义⽬标源时,你的⽬标通常需要是原型⽽不是单例bean定义。这允许Spring在需要时创建新的⽬标实 例。

9.1 热交换Target Sources

存在org.springframework.aop.target.HotSwappableTargetSource以允许AOP代理的⽬标切换,同时允许调⽤⽅保留对它的引⽤。 更改⽬标源的⽬标将⽴即⽣效。HotSwappableTargetSource是线程安全的。 你可以使⽤HotSwappableTargetSource上的swap()⽅法更改⽬标,如下示例所示:

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的⽬标。持有对该bean的引⽤的客户端不知道该更改,并且⽴即开始使⽤新的⽬标。 尽管此示例不添加任何advice(不需要添加advice以使⽤TargetSource),但任何TargetSource都可以与任意advice结合使⽤。

9.2 Pooling Target Sources

使⽤pooling target source为⽆状态会话EJB提供了类似的编程模型,其中维护了相同实例的池,⽅法调⽤将释放池中的对象。

Spring池和SLSB池的⼀个重要区别是Spring池可以应⽤于任何POJO。与⼀般的Spring⼀样,此服务可以以⾮侵⼊性的⽅式应⽤。

Spring⽀持Commons Pool 2.2,它提供了⼀个相当有效的池实现。你需要应⽤程序类路径上的commons-pool来使⽤此功能。你还可以将org.springframework.aop.target.AbstractPoolingTargetSource⼦类化,以⽀持任何其他池API。

下⾯是⼀个配置的例⼦:

<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实现创建⽬标的新实例,以便在必要时扩⼤池。有关其属性的信息,请参阅AbstractPoolingTargetSourceJavaDoc和你希望使⽤的具体⼦类。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>

通过对AbstractPoolingTargetSource类调⽤⼀个⽅便的⽅法来获得此Advisor,因此使⽤了 MethodInvokingFactoryBean。此顾问(advisor)的名称(此处为PoolConfigdVisor)必须位于公开池对象的ProxyFactoryBean中拦截器名称的列表中。

其定义如下:

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

通常不需要合并无状态服务对象。我们不认为这应该是默认的选择,因为⼤多数⽆状态对象都是⾃然线程安全的, 如果缓存资源,实例池也会有问题。

使⽤⾃动代理可以使池的使⽤更加简单。您可以设置任何自动代理创建者使用的TargetSource实现。

9.3 Prototype Target Sources

设置“prototype”⽬标源类似于设置池TargetSource。在这种情况下,每次⽅法调⽤都会创建⽬标的新实例。虽然在现代的JVM中创建新对象的成本并不⾼,但是连接新对象(满⾜其IOC依赖性)的成本可能更⾼。因此,如果没有很好的理由,你不应该使⽤这种⽅法。

为此,你可以修改前⾯显示的poolTargetSource定义,如下所示(为了清晰起⻅,我们还更改了名称):

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

唯⼀的属性是⽬标bean的名称。在TargetSource实现中使⽤继承以确保⼀致的命名。与池⽬标源⼀样,⽬标bean必须是原型bean定义。

9.4 ThreadLocal Target Sources

如果需要为每个传⼊请求(即每个线程)创建⼀个对象,则ThreadLocal⽬标源⾮常有⽤。ThreadLocal的概念提供了⼀ 个JDK范围的⼯具,可以透明地将资源存储在线程旁边。设置ThreadLocalTargetSource与其他类型的⽬标源的说明⼏乎相同,如下示例所示:

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

ThreadLocal实例在多线程和多类加载器环境中错误地使⽤它们时会出现严重问题(可能导致内存泄漏)。你应该始终考虑在其他类中包装⼀个threadlocal,⽽不要直接使⽤threadlocal本身(包装类中除外)。此外,你应该始终记住正确地设置和取消设置(后者只涉及对ThreadLocal.set(null))线程的本地资源。在任何情况下都应进⾏取消设置,因为不取消设置可能会导致问题⾏为。Spring的threadLocal⽀持为你提供了这⼀点,应该始终考虑在没有其他适当处理代码的情况下使⽤threadLocal实例。

9.5 定义新的Advice Types

Spring AOP设计为可扩展的。虽然拦截实现策略⽬前在内部使⽤,但是除了around advice 的拦截之外,还可以⽀持任意的通知类型,包括before, throws advice,和after returning advice。org.springframework.aop.framework.adapter包是⼀个SPI包,它允许在不更改核⼼框架的情况下添加对新⾃定义通知类型的⽀持。对⾃定义Advice类型的唯⼀约束是它必须实现org.aopalliance.aop.Advice接⼝。


文章作者: shiv
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 shiv !
评论
  目录