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