此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 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 属性
与大多数人共同FactoryBean
Spring 提供的实现,则ProxyFactoryBean
class 本身就是一个 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
忽略通知的单例设置。您可以在拦截器名称中附加星号 ()。这样做会导致应用程序所有名称以星号前部分开头的顾问 Bean应用。您可以在使用“全局”顾问中找到使用此功能的示例。
*
-
singleton:工厂是否应该返回单个对象,无论经常
getObject()
方法被调用。 几个FactoryBean
实现提供这样的方法。默认值为true
. 如果要使用有状态建议 - 对于例如,对于有状态混合 - 使用原型建议以及单例值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。这是
personTarget
bean 定义 例子。 -
一
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
. 如果我们想要以防止应用程序上下文的用户获取对非建议的对象的引用,或者需要避免 Spring IoC 自动布线的任何歧义。还有一个可以说,一个优点是ProxyFactoryBean
定义是独立的。但是,有时能够从factory 获取非建议目标实际上可能是一个优势(例如,在某些测试场景中)。
代理类
如果您需要代理一个类,而不是一个或多个接口,该怎么办?
想象一下,在我们前面的示例中,没有Person
接口。 我们需要建议一个叫做Person
没有实现任何业务接口。在这种情况下,您可以将 Spring 配置为使用 CGLIB 代理而不是动态代理。为此,请将proxyTargetClass
属性ProxyFactoryBean
前面显示为true
. 虽然最好是编程到接口而不是类,但建议不这样做的类的能力实现接口在处理遗留代码时很有用。(一般来说,Spring不是规范性的。虽然它使应用良好实践变得容易,但它避免了强制特定的方法。
如果你愿意,你可以在任何情况下强制使用 CGLIB,即使你确实有 接口。
CGLIB 代理的工作原理是在运行时生成目标类的子类。 Spring 配置此生成的子类,以将方法调用委托给原始目标。 这 子类用于实现装饰器模式,并编织建议。
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"/>