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