|
对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
使用ProxyFactoryBean创建 AOP 代理
如果您使用 Spring IoC 容器(ApplicationContext或BeanFactory) 为您的
业务对象(你应该这样做!),你想使用 Spring 的 AOP 之一FactoryBean实现。(请记住,工厂 bean 引入了一层间接层,让
它创建了不同类型的对象。
| Spring AOP 支持还在盖子下使用工厂 bean。 |
在 Spring 中创建 AOP 代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean.这样可以完全控制
切入点、任何适用的建议及其顺序。但是,有更简单的
如果您不需要此类控制,则更可取的选项。
基本
这ProxyFactoryBean,像其他弹簧一样FactoryBean实现,引入了一个
间接级别。如果定义ProxyFactoryBean叫foo,对象
参考foo看不到ProxyFactoryBean实例本身,但是一个对象
由实现getObject()方法ProxyFactoryBean.这
方法创建一个包装目标对象的 AOP 代理。
使用ProxyFactoryBean或其他 IoC 感知
类创建 AOP 代理是建议和切入点也可以是
由 IoC 管理。这是一个强大的功能,支持某些难以实现的方法
与其他 AOP 框架一起实现。例如,建议本身可以引用
应用程序对象(除了目标,它应该在任何 AOP 中可用
框架),受益于 Dependency Injection 提供的所有可插拔性。
JavaBean 属性
与大多数人共同FactoryBeanSpring 提供的实现,则ProxyFactoryBeanclass 本身就是一个 JavaBean。它的属性用于:
-
指定要代理的目标。
-
指定是否使用 CGLIB(稍后将介绍,另请参阅基于 JDK 和 CGLIB 的代理)。
一些关键属性继承自org.springframework.aop.framework.ProxyConfig(Spring 中所有 AOP 代理工厂的超类)。这些关键属性包括
以下内容:
-
proxyTargetClass:true如果要代理目标类,而不是 目标类的接口。如果此属性值设置为true,然后是 CGLIB 代理 创建(但另请参阅基于 JDK 和 CGLIB 的代理)。 -
optimize:控制是否对代理应用积极优化 通过 CGLIB 创建。除非您完全 了解相关 AOP 代理如何处理优化。目前使用 仅适用于 CGLIB 代理。它对 JDK 动态代理没有影响。 -
frozen:如果代理配置为frozen,对配置的更改是 不再允许。这既可以作为轻微的优化,也可以用于这些情况 当您不希望调用方能够作代理时(通过Advised接口)创建代理后。此属性的默认值为false,因此允许更改(例如添加其他建议)。 -
exposeProxy:确定当前代理是否应在ThreadLocal以便目标可以访问它。如果目标需要获得 代理和exposeProxy属性设置为true,目标可以使用AopContext.currentProxy()方法。
特定于的其他属性ProxyFactoryBean包括以下内容:
-
proxyInterfaces:数组String接口名称。如果未提供此值,则 CGLIB 使用目标类的代理(但另请参阅基于 JDK 和 CGLIB 的代理)。 -
interceptorNames:一个String数组Advisor、拦截器或其他建议名称 应用。订购很重要,先到先得。也就是说 列表中的第一个拦截器是第一个能够拦截 调用。这些名称是当前工厂中的 bean 名称,包括祖先中的 bean 名称 工厂。您不能在这里提及 bean 引用,因为这样做会导致
ProxyFactoryBean忽略通知的单例设置。您可以在拦截器名称后附加星号 ()。这样做会导致 应用名称以星号前部分开头的所有 Advisor Bean 待应用。您可以在使用“全局”顾问中找到使用此功能的示例。
* -
singleton:工厂是否应该返回单个对象,无论如何 通常
getObject()方法被调用。几个FactoryBean实施提供 这样的方法。默认值为true.如果您想使用有状态建议 - 对于 例如,对于有状态的 mixin - 使用 prototype advice 以及单例值false.
基于 JDK 和 CGLIB 的代理
本节是关于如何ProxyFactoryBean选择为特定目标创建基于 JDK 的代理或基于 CGLIB 的代理
对象(要代理)。
的行为ProxyFactoryBean关于创建基于 JDK 或 CGLIB 的
代理在 Spring 1.2.x 和 2.0 版本之间发生了变化。这ProxyFactoryBean现在
在自动检测接口方面表现出与TransactionProxyFactoryBean类。 |
如果要代理的目标对象的类(以下简称为
目标类)不实现任何接口,基于 CGLIB 的代理是
创建。这是最简单的场景,因为 JDK 代理是基于接口的,没有
接口意味着 JDK 代理甚至不可能。您可以插入目标 Bean
并通过设置interceptorNames财产。请注意,一个
创建基于 CGLIB 的代理,即使proxyTargetClass属性的ProxyFactoryBean已设置为false.(这样做没有意义,而且是最好的
从 bean 定义中删除,因为它充其量是多余的,最坏的情况是多余的
令人困惑。
如果目标类实现一个(或多个)接口,则代理类型为
created 取决于ProxyFactoryBean.
如果proxyTargetClass属性的ProxyFactoryBean已设置为true,
创建基于 CGLIB 的代理。这是有道理的,并且符合
最小惊喜原则。即使proxyInterfaces属性的ProxyFactoryBean已设置为一个或多个完全限定的接口名称,事实
该proxyTargetClass属性设置为true导致基于 CGLIB
代理生效。
如果proxyInterfaces属性的ProxyFactoryBean已设置为一个或多个
完全限定的接口名称,则创建基于 JDK 的代理。创建的
proxy 实现在proxyInterfaces财产。如果目标类恰好实现了比
中指定的proxyInterfaces财产,这一切都很好,但那些
返回的代理不会实现其他接口。
如果proxyInterfaces属性的ProxyFactoryBean还没有设定,但是
目标类确实实现了一个(或多个)接口,即ProxyFactoryBean自动检测目标类实际执行的事实
实现至少一个接口,并创建一个基于 JDK 的代理。接口
实际代理的是目标类的所有接口
实现。实际上,这与提供每个
目标类实现到proxyInterfaces财产。然而
它的工作量明显减少,也不易出现印刷错误。
代理接口
考虑一个简单的例子ProxyFactoryBean在行动。此示例涉及:
-
代理的目标 Bean。这是
personTargetbean 定义 例子。 -
一
Advisor和Interceptor用于提供建议。 -
用于指定目标对象的 AOP 代理 Bean 定义(
personTarget豆子), 代理接口,以及应用建议。
以下列表显示了示例:
<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属性采用String,其中包含
当前工厂的拦截器或顾问。您可以使用顾问、拦截器、之前、之后
返回,并抛出建议对象。顾问的顺序很重要。
您可能想知道为什么该列表不包含 bean 引用。原因是
如果ProxyFactoryBean设置为false,它必须能够
返回独立的代理实例。如果任何顾问本身是原型,则
独立实例需要返回,因此必须能够获得
出厂时原型的实例。持有推荐信是不够的。 |
这person前面显示的 bean 定义可以用来代替Person实现,作为
遵循:
-
Java
-
Kotlin
Person person = (Person) factory.getBean("person");
val person = factory.getBean("person") as Person;
同一 IoC 上下文中的其他 bean 可以表达对它的强类型依赖关系,如 使用普通的 Java 对象。以下示例显示了如何执行此作:
<bean id="personUser" class="com.mycompany.PersonUser">
<property name="person"><ref bean="person"/></property>
</bean>
这PersonUser此示例中的 class 公开了 typePerson.就
它担心,AOP 代理可以透明地代替“真实”人使用
实现。但是,它的类将是一个动态代理类。这是可能的
将其转换为Advised界面(稍后讨论)。
您可以使用匿名
内豆。只有ProxyFactoryBean定义不同。这
仅为完整起见,包含建议。以下示例演示如何使用
匿名内豆:
<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 的引用
对象或需要避免 Spring IoC 自动布线的任何歧义。还有,
可以说,一个优势在于ProxyFactoryBean定义是独立的。
但是,有时能够从
工厂实际上可能是一个优势(例如,在某些测试场景中)。
代理类
如果您需要代理一个类,而不是一个或多个接口,该怎么办?
想象一下,在我们前面的示例中,没有Person接口。我们需要提供建议
一个名为Person没有实现任何业务接口。在这种情况下,您
可以将 Spring 配置为使用 CGLIB 代理而不是动态代理。为此,请将proxyTargetClass属性ProxyFactoryBean前面显示为true.虽然最好
程序到接口而不是类,能够建议没有
实现接口在处理遗留代码时非常有用。(一般来说,Spring
不是规定性的。虽然它使应用良好实践变得容易,但它避免了强制
特殊方法。
如果你愿意,你可以在任何情况下强制使用 CGLIB,即使你确实有 接口。
CGLIB 代理的工作原理是在运行时生成目标类的子类。Spring 配置此生成的子类,以将方法调用委托给原始目标。这 子类用于实现 Decorator 模式,编织 vice。
CGLIB 代理通常应该对用户透明。但是,也存在一些问题 要考虑:
-
final类不能代理,因为它们不能扩展。 -
final不能建议方法,因为它们不能被覆盖。 -
private不能建议方法,因为它们不能被覆盖。 -
不可见的方法,通常将私有方法打包在父类中 来自不同的软件包,无法建议,因为它们实际上是私有的。
无需将 CGLIB 添加到类路径中。CGLIB 被重新打包并包含在内
在spring-core罐。换句话说,基于 CGLIB 的 AOP 可以“开箱即用”,也是如此
JDK 动态代理。 |
CGLIB 代理和动态代理之间的性能差异很小。 在这种情况下,性能不应成为决定性的考虑因素。
使用“全球”顾问
通过将星号附加到拦截器名称,所有具有匹配 bean 名称的顾问 星号之前的部分将添加到顾问链中。这可以派上用场 如果您需要添加一组标准的“全球”顾问。以下示例定义 两位全球顾问:
<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"/>