概述
在Spring中创建AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean。这提供了对切入点、应用的任何通知及其顺序的完全控制。但是,如果你不需要这种控制,则有更简单的选项更可取。
和其他Spring FactoryBean实现一样,ProxyFactoryBean引入了一个间接层。如果定义了一个名为foo的ProxyFactoryBean,那么引用foo的对象不会看到ProxyFactoryBean实例本身,而是看到一个由ProxyFactoryBean中的getObject()方法实现创建的对象。此方法创建封装目标对象的AOP代理。
使用ProxyFactoryBean或另一个支持IoC的类来创建AOP代理的最重要的好处之一是,IoC也可以管理Advice和Pointcut。这是一个强大的特性,可以实现某些用其他AOP框架难以实现的方法。例如,通知本身可以引用应用程序对象(目标之外,它应该在任何AOP框架中可用),受益于依赖项注入提供的所有可插拔性。
ProxyFactoryBean 属性
和Spring提供的大多数FactoryBean实现一样,ProxyFactoryBean类本身就是一个JavaBean。它的属性用于:
- 指定要代理的目标
- 指定是否使用CGLIB
一些关键属性继承自org.springframework.aop.framework.ProxyConfig (Spring中所有AOP代理工厂的超类)。这些关键属性包括: - proxyTargetClass: 如果要代理的是目标类,而不是目标类的接口,则为true。如果该属性值设置为true,则创建CGLIB代理。
- optimize: 控制是否对通过CGLIB创建的代理应用激进的优化。除非你完全理解相关AOP代理如何处理优化,否则不应该轻松地使用此设置。目前仅用于CGLIB代理。它对JDK动态代理没有影响。
- frozen: 如果代理配置被冻结,则不再允许更改该配置。无论是作为轻微的优化,还是当你不希望调用者在创建代理后能够操作代理,这都是有用的。此属性的默认值为false,因此允许更改(例如添加额外的通知)。
- exposeProxy: 确定是否应该在ThreadLocal中暴露当前代理,以便目标可以访问它。如果目标需要获取代理,而exposeProxy属性被设置为true,那么可以使用AopContext.currentProxy()方法。
ProxyFactoryBean特有的其他属性包括:
- proxyInterfaces: 接口名称的字符串数组。如果没有提供,则使用目标类的CGLIB代理
- interceptorNames: 建议器、拦截器或其他要应用的建议器名称的字符串数组。 这些名称是当前工厂中的bean名称,包括来自祖先工厂的bean名称。这里不能提到bean引用,因为这样做会导致ProxyFactoryBean忽略通知的单例设置。
你可以在拦截器名称后面加上星号(*)。这样做的结果是,所有advisor bean的名称都以要应用的星号之前的部分开头。你可以在使用“全局”建议器中找到使用此功能的例子。 - singleton: 不管getObject()方法被调用的频率如何,工厂是否应该返回一个对象。有几个FactoryBean实现提供了这样的方法。默认值为true。
基于JDK和CGLIB的代理
ProxyFactoryBean如何选择为特定目标对象(要代理的对象)创建基于JDK的代理或基于CGLIB的代理的最终文档。
如果要代理的目标对象的类(以下简称为目标类)没有实现任何接口,则创建一个基于cglib的代理。这是最简单的场景,因为JDK代理是基于接口的,没有接口意味着JDK代理根本不可能。你可以插入目标bean并通过设置interceptorNames属性来指定拦截器列表。注意,即使将ProxyFactoryBean的proxyTargetClass属性设置为false,也会创建一个基于cglib的代理。
如果目标类实现了一个(或多个)接口,则创建的代理类型取决于ProxyFactoryBean的配置。
如果ProxyFactoryBean的proxyTargetClass属性被设置为true,则会创建一个基于cglib的代理。这是有道理的,也符合最小意外原则。即使ProxyFactoryBean的proxyInterfaces属性被设置为一个或多个完全限定的接口名,proxyTargetClass属性被设置为true这一事实也会导致基于cglib的代理生效。
如果将ProxyFactoryBean的proxyInterfaces属性设置为一个或多个完全限定的接口名,则创建一个基于jdk的代理。创建的代理实现了在proxyInterfaces属性中指定的所有接口。如果目标类碰巧实现了比proxyInterfaces属性中指定的更多的接口,那也没什么问题,但返回的代理不会实现这些额外的接口。
如果ProxyFactoryBean的proxyInterfaces属性没有被设置,但是目标类确实实现了一个(或多个)接口,那么ProxyFactoryBean会自动检测到目标类实际上至少实现了一个接口,并且创建了一个基于jdk的代理。实际被代理的接口是目标类实现的所有接口。实际上,这相当于为目标类的proxyInterfaces属性提供一个列表,其中包含目标类实现的所有接口。然而,这样做工作量大大减少,也不容易出现排版错误。
代理接口
考虑一个使用ProxyFactoryBean的简单例子。这个例子涉及:
- 考虑一个使用ProxyFactoryBean的简单例子。这个例子涉及:
- 顾问(Advisor)和拦截器(Interceptor)用来提供通知。
- 一个AOP代理bean定义,用来指定目标对象(personTarget bean)、代理的接口和要应用的建议。
注意,interceptorNames属性接受一个字符串列表,其中包含当前工厂中拦截器或顾问的bean名称。你可以使用顾问,拦截器,返回之前,之后,并抛出建议对象。
前面给出的person bean定义可以代替person实现,如下所示:
同一个IoC上下文中的其他bean可以表达对它的强类型依赖,就像普通Java对象一样。如下面的例子所示:
这个例子中的PersonUser类公开了一个Person类型的属性。就它所关心的而言,AOP代理可以透明地代替“真实的”人实现。但是,它的类将是一个动态代理类。
代理类
如果需要代理一个类,而不是一个或多个接口,该怎么办?
想象一下,在我们前面的例子中,没有Person接口。我们需要建议一个名为Person的类,它没有实现任何业务接口。在这种情况下,你可以配置Spring使用CGLIB代理,而不是动态代理。为此,将前面给出的ProxyFactoryBean的proxyTargetClass属性设置为true。虽然最好是针对接口而不是类进行编程,但在处理遗留代码时,能够建议不实现接口的类可能很有用。(一般来说,Spring不是规定性的。虽然它使应用良好的实践变得容易,但它避免了强制使用特定的方法。)
如果你愿意,你可以在任何情况下强制使用CGLIB,即使你有接口。
CGLIB代理通过在运行时生成目标类的子类来工作。Spring将这个生成的子类配置为将方法调用委托给原始目标。子类用于实现装饰器模式,在建议中编织。
CGLIB代理一般应该对用户透明。但是,有一些问题需要考虑:
- Final方法无法提供建议,因为它们不能被覆盖
- 不需要将CGLIB添加到类路径中。从Spring 3.2开始,CGLIB被重新打包并包含在Spring -core JAR中。换句话说,基于cglib的AOP可以“开箱即用”,JDK的动态代理也是如此
使用“全局”顾问(Advisor)
通过在拦截器名称后面附加一个星号,所有bean名称与星号之前的部分匹配的顾问都被添加到顾问链中。如果你需要添加一组标准的“全局”顾问,这可以派上用场。下面的例子定义了两个全局advisor: