此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10spring-doc.cadn.net.cn

使用ProxyFactoryBean创建 AOP 代理

如果您使用 Spring IoC 容器(ApplicationContextBeanFactory) 为您的 业务对象(你应该这样做!),你想使用 Spring 的 AOP 之一FactoryBean实现。(请记住,工厂 bean 引入了一层间接层,让 它创建了不同类型的对象。spring-doc.cadn.net.cn

Spring AOP 支持还在盖子下使用工厂 bean。

在 Spring 中创建 AOP 代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean.这样可以完全控制 切入点、任何适用的建议及其顺序。但是,有更简单的 如果您不需要此类控制,则更可取的选项。spring-doc.cadn.net.cn

基本

ProxyFactoryBean,像其他弹簧一样FactoryBean实现,引入了一个 间接级别。如果定义ProxyFactoryBeanfoo,对象 参考foo看不到ProxyFactoryBean实例本身,但是一个对象 由实现getObject()方法ProxyFactoryBean.这 方法创建一个包装目标对象的 AOP 代理。spring-doc.cadn.net.cn

使用ProxyFactoryBean或其他 IoC 感知 类创建 AOP 代理是建议和切入点也可以是 由 IoC 管理。这是一个强大的功能,支持某些难以实现的方法 与其他 AOP 框架一起实现。例如,建议本身可以引用 应用程序对象(除了目标,它应该在任何 AOP 中可用 框架),受益于 Dependency Injection 提供的所有可插拔性。spring-doc.cadn.net.cn

JavaBean 属性

与大多数人共同FactoryBeanSpring 提供的实现,则ProxyFactoryBeanclass 本身就是一个 JavaBean。它的属性用于:spring-doc.cadn.net.cn

一些关键属性继承自org.springframework.aop.framework.ProxyConfig(Spring 中所有 AOP 代理工厂的超类)。这些关键属性包括 以下内容:spring-doc.cadn.net.cn

  • proxyTargetClass:true如果要代理目标类,而不是 目标类的接口。如果此属性值设置为true,然后是 CGLIB 代理 创建(但另请参阅基于 JDK 和 CGLIB 的代理)。spring-doc.cadn.net.cn

  • optimize:控制是否对代理应用积极优化 通过 CGLIB 创建。除非您完全 了解相关 AOP 代理如何处理优化。目前使用 仅适用于 CGLIB 代理。它对 JDK 动态代理没有影响。spring-doc.cadn.net.cn

  • frozen:如果代理配置为frozen,对配置的更改是 不再允许。这既可以作为轻微的优化,也可以用于这些情况 当您不希望调用方能够作代理时(通过Advised接口)创建代理后。此属性的默认值为false,因此允许更改(例如添加其他建议)。spring-doc.cadn.net.cn

  • exposeProxy:确定当前代理是否应在ThreadLocal以便目标可以访问它。如果目标需要获得 代理和exposeProxy属性设置为true,目标可以使用AopContext.currentProxy()方法。spring-doc.cadn.net.cn

特定于的其他属性ProxyFactoryBean包括以下内容:spring-doc.cadn.net.cn

  • proxyInterfaces:数组String接口名称。如果未提供此值,则 CGLIB 使用目标类的代理(但另请参阅基于 JDK 和 CGLIB 的代理)。spring-doc.cadn.net.cn

  • interceptorNames:一个String数组Advisor、拦截器或其他建议名称 应用。 顺序很重要,先到先得。也就是说列表中的第一个拦截器是第一个能够拦截 调用。spring-doc.cadn.net.cn

    这些名称是当前工厂中的 bean 名称,包括祖先中的 bean 名称 工厂。 您不能在此处提及 bean 引用,因为这样做会导致ProxyFactoryBean忽略通知的单例设置。spring-doc.cadn.net.cn

    您可以在拦截器名称中附加星号 ()。这样做会导致应用程序所有名称以星号前部分开头的顾问 Bean应用。您可以在使用“全局”顾问中找到使用此功能的示例。*spring-doc.cadn.net.cn

  • singleton:工厂是否应该返回单个对象,无论经常getObject()方法被调用。 几个FactoryBean实现提供这样的方法。默认值为true. 如果要使用有状态建议 - 对于例如,对于有状态混合 - 使用原型建议以及单例值false.spring-doc.cadn.net.cn

基于 JDK 和 CGLIB 的代理

本节是关于如何ProxyFactoryBean选择为特定目标创建基于 JDK 的代理或基于 CGLIB 的代理对象(要代理)。spring-doc.cadn.net.cn

的行为ProxyFactoryBean关于创建基于 JDK 或 CGLIB 的代理在 Spring 版本 1.2.x 和 2.0 之间发生了变化。 这ProxyFactoryBean现在 在自动检测接口方面表现出与TransactionProxyFactoryBean类。

如果要代理的目标对象的类(以下简称为 目标类)不实现任何接口,基于 CGLIB 的代理是 创建。这是最简单的场景,因为 JDK 代理是基于接口的,没有 接口意味着 JDK 代理甚至不可能。您可以插入目标 Bean 并通过设置interceptorNames财产。请注意,一个 创建基于 CGLIB 的代理,即使proxyTargetClass属性的ProxyFactoryBean已设置为false.(这样做没有意义,而且是最好的 从 bean 定义中删除,因为它充其量是多余的,最坏的情况是多余的 令人困惑。spring-doc.cadn.net.cn

如果目标类实现一个(或多个)接口,则代理类型为 created 取决于ProxyFactoryBean.spring-doc.cadn.net.cn

如果proxyTargetClass属性的ProxyFactoryBean已设置为true, 创建基于 CGLIB 的代理。这是有道理的,并且符合 最小惊喜原则。即使proxyInterfaces属性的ProxyFactoryBean已设置为一个或多个完全限定的接口名称,事实 该proxyTargetClass属性设置为true导致基于 CGLIB 代理生效。spring-doc.cadn.net.cn

如果proxyInterfaces属性的ProxyFactoryBean已设置为一个或多个 完全限定的接口名称,则创建基于 JDK 的代理。创建的 proxy 实现在proxyInterfaces财产。如果目标类恰好实现了比 中指定的proxyInterfaces财产,这一切都很好,但那些 返回的代理不会实现其他接口。spring-doc.cadn.net.cn

如果proxyInterfaces属性的ProxyFactoryBean还没有设定,但是 目标类确实实现了一个(或多个)接口,即ProxyFactoryBean自动检测目标类实际执行的事实 实现至少一个接口,并创建一个基于 JDK 的代理。接口 实际代理的是目标类的所有接口 实现。实际上,这与提供每个 目标类实现到proxyInterfaces财产。然而 它的工作量明显减少,也不易出现印刷错误。spring-doc.cadn.net.cn

代理接口

考虑一个简单的例子ProxyFactoryBean在行动。此示例涉及:spring-doc.cadn.net.cn

以下列表显示了示例:spring-doc.cadn.net.cn

<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,其中包含 当前工厂的拦截器或顾问。您可以使用顾问、拦截器、之前、之后 返回,并抛出建议对象。顾问的顺序很重要。spring-doc.cadn.net.cn

您可能想知道为什么该列表不包含 bean 引用。原因是 如果ProxyFactoryBean设置为false,它必须能够 返回独立的代理实例。如果任何顾问本身是原型,则 独立实例需要返回,因此必须能够获得 出厂时原型的实例。持有推荐信是不够的。

person前面显示的 bean 定义可以用来代替Person实现,作为 遵循:spring-doc.cadn.net.cn

Person person = (Person) factory.getBean("person");
val person = factory.getBean("person") as Person

同一 IoC 上下文中的其他 bean 可以表达对它的强类型依赖关系,如 使用普通的 Java 对象。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

<bean id="personUser" class="com.mycompany.PersonUser">
	<property name="person"><ref bean="person"/></property>
</bean>

PersonUser此示例中的 class 公开了 typePerson.就 它担心,AOP 代理可以透明地代替“真实”人使用 实现。但是,它的类将是一个动态代理类。这是可能的 将其转换为Advised界面(稍后讨论)。spring-doc.cadn.net.cn

您可以使用匿名 内豆。只有ProxyFactoryBean定义不同。这 仅为完整起见,包含建议。以下示例演示如何使用 匿名内豆:spring-doc.cadn.net.cn

<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 获取非建议目标实际上可能是一个优势(例如,在某些测试场景中)。spring-doc.cadn.net.cn

代理类

如果您需要代理一个类,而不是一个或多个接口,该怎么办?spring-doc.cadn.net.cn

想象一下,在我们前面的示例中,没有Person接口。 我们需要建议一个叫做Person没有实现任何业务接口。在这种情况下,您可以将 Spring 配置为使用 CGLIB 代理而不是动态代理。为此,请将proxyTargetClass属性ProxyFactoryBean前面显示为true. 虽然最好是编程到接口而不是类,但建议不这样做的类的能力实现接口在处理遗留代码时很有用。(一般来说,Spring不是规范性的。虽然它使应用良好实践变得容易,但它避免了强制特定的方法。spring-doc.cadn.net.cn

如果你愿意,你可以在任何情况下强制使用 CGLIB,即使你确实有 接口。spring-doc.cadn.net.cn

CGLIB 代理的工作原理是在运行时生成目标类的子类。 Spring 配置此生成的子类,以将方法调用委托给原始目标。 这 子类用于实现装饰器模式,并编织建议。spring-doc.cadn.net.cn

CGLIB 代理通常应该对用户透明。但是,存在一些问题要考虑:spring-doc.cadn.net.cn

无需将 CGLIB 添加到类路径中。CGLIB 被重新打包并包含在内 在spring-core罐。换句话说,基于 CGLIB 的 AOP 可以“开箱即用”,也是如此 JDK 动态代理。

CGLIB 代理和动态代理之间的性能差异很小。 在这种情况下,性能不应成为决定性的考虑因素。spring-doc.cadn.net.cn

使用“全球”顾问

通过将星号附加到拦截器名称,所有具有匹配 bean 名称的顾问 星号之前的部分将添加到顾问链中。这可以派上用场 如果您需要添加一组标准的“全球”顾问。以下示例定义 两位全球顾问:spring-doc.cadn.net.cn

<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"/>