文章摘要(AI生成)
本章介绍了Spring AOP API的低级别内容。在Spring AOP中,切入点用于定位切面的特定类和方法。Pointcut接口是中央接口,用于将切面定位到类和方法。Pointcut接口分为两个部分:ClassFilter接口用于将切入点限制为一组目标类,MethodMatcher接口用于确定切入点是否匹配目标类的给定方法。可以通过调用matches()方法来测试切入点是否匹配目标类的给定方法。这样可以在创建AOP代理时进行评估,以避免对每个方法调用进行测试。
6. Spring AOP API
上一章描述了 Spring 使用 @AspectJ 和基于模式的方面定义对 AOP 的支持。在本章中,我们将讨论较低级别的 Spring AOP API。对于常见的应用程序,我们切面使用 Spring AOP 和 AspectJ 切入点,如前一章所述。
6.1. Spring中的切入点API
本节描述 Spring 如何处理关键的切入点概念。
6.1.1. 概念
Spring 的切入点模型使切入点重用独立于通知类型。您可以使用相同的切入点定位不同的切面。
org.springframework.aop.Pointcut
接口是中央接口,用于将切面定位到特定的类和方法。完整的接口如下:
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
将Pointcut
接口分成两部分允许重用类和方法匹配部分以及细粒度的组合操作(例如与另一个方法匹配器执行“联合”)。
ClassFilter
接口用于将切入点限制为给定的一组目标类。如果该matches()
方法始终返回 true,则所有目标类都匹配。以下清单显示了ClassFilter
接口定义:
public interface ClassFilter {
boolean matches(Class clazz);
}
MethodMatcher
接口通常更重要。完整的接口如下:
public interface MethodMatcher {
boolean matches(Method m, Class<?> targetClass);
boolean isRuntime();
boolean matches(Method m, Class<?> targetClass, Object... args);
}
matches(Method, Class)
方法用于测试此切入点是否曾经匹配目标类上的给定方法。可以在创建 AOP 代理时执行此评估,以避免需要对每个方法调用进行测试。如果给定方法的两参数matches
方法返回true
,并且 MethodMatcher 的isRuntime()
方法返回true
,则在每次方法调用时都会调用三参数matches
方法。这让切入点在目标通知开始之前立即查看传递给方法调用的参数。
大多数MethodMatcher
实现都是静态的,这意味着它们的isRuntime()
方法返回false
. 在这种情况下,永远不会调用三参数的matches
方法。
如果可能,尽量使切入点静态化,允许 AOP 框架在创建 AOP 代理时缓存切入点评估的结果。
6.1.2. 切入点操作
Spring 支持切入点上的操作(特别是联合和交集)。
联合表示任一切入点匹配的方法。交集是指两个切入点匹配的方法。联合通常更有用。您可以使用类中的静态方法 org.springframework.aop.support.Pointcuts
或使用同一包中的ComposablePointcut
类来组合切入点。然而,使用 AspectJ 切入点表达式通常是一种更简单的方法。
6.1.3. AspectJ 表达式切入点
从 2.0 开始,Spring 使用的最重要的切入点类型是 org.springframework.aop.aspectj.AspectJExpressionPointcut
. 这是一个使用 AspectJ 提供的库来解析 AspectJ 切入点表达式字符串的切入点。
有关受支持的 AspectJ 切入点原语的讨论,请参见前一章。
6.1.4. 切入点的快捷实现
Spring 提供了几个方便的切入点实现。您可以直接使用其中的一些;其他的旨在在特定于应用程序的切入点中进行子类化。
静态切入点
静态切入点基于方法和目标类,不能考虑方法的参数。对于大多数用途来说,静态切入点就足够了——而且是最好的。当第一次调用方法时,Spring 只能评估一次静态切入点。之后,无需在每次方法调用时再次评估切入点。
本节的其余部分描述了 Spring 中包含的一些静态切入点实现。
正则表达式切入点
指定静态切入点的一种明显方法是正则表达式。除了 Spring 之外的几个 AOP 框架使这成为可能。 org.springframework.aop.support.JdkRegexpMethodPointcut
是一个通用的正则表达式切入点,它使用 JDK 中的正则表达式支持。
使用JdkRegexpMethodPointcut
类,您可以提供模式字符串列表。如果其中任何一个匹配,则切入点计算为true
。(因此,生成的切入点实际上是指定模式的并集。)
下面的例子展示了如何使用JdkRegexpMethodPointcut
:
<bean id="settersAndAbsquatulatePointcut"
class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="patterns">
<list>
<value>.*set.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>
Spring 提供了一个名为RegexpMethodPointcutAdvisor
的便利类,它让我们还可以引用 Advice
(请记住,Advice
可以是拦截器,在通知之前,抛出通知等)。在幕后,Spring 使用JdkRegexpMethodPointcut
. 使用RegexpMethodPointcutAdvisor
简化了织入,因为一个 bean 封装了切入点和切面,如下例所示:
<bean id="settersAndAbsquatulateAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="beanNameOfAopAllianceInterceptor"/>
</property>
<property name="patterns">
<list>
<value>.*set.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>
您可以使用带有任何Advice
类型的RegexpMethodPointcutAdvisor
。
属性驱动切入点
一种重要的静态切入点是元数据驱动的切入点。这使用元数据属性的值(通常是源级元数据)。
动态切入点
动态切入点比静态切入点的评估成本更高。它们考虑了方法参数以及静态信息。这意味着必须在每次方法调用时对它们进行评估,并且结果不能被缓存,因为参数会有所不同。
主要的例子是control flow
切入点。
控制流切入点
Spring 控制流切入点在概念上类似于 AspectJcflow
切入点,但功能较弱。(目前无法指定一个切入点在与另一个切入点匹配的连接点下方运行。)控制流切入点匹配当前调用堆栈。例如,如果连接点被com.mycompany.web
包中的方法或SomeCaller
类调用,它可能会触发。控制流切入点是通过使用org.springframework.aop.support.ControlFlowPointcut
类来指定的。
与其他动态切入点相比,控制流切入点在运行时的评估成本要高得多。在 Java 1.4 中,成本大约是其他动态切入点的五倍。
6.1.5. 切入点超类
Spring 提供了有用的切入点超类来帮助您实现自己的切入点。
因为静态切入点是最有用的,所以你应该子类化 StaticMethodMatcherPointcut
. 这只需要实现一个抽象方法(尽管您可以覆盖其他方法来自定义行为)。以下示例显示了如何子类化StaticMethodMatcherPointcut
:
class TestStaticPointcut extends StaticMethodMatcherPointcut {
public boolean matches(Method m, Class targetClass) {
// return true if custom criteria match
}
}
还有用于动态切入点的超类。您可以将自定义切入点与任何切面类型一起使用。
6.1.6. 自定义切入点
因为 Spring AOP 中的切入点是 Java 类而不是语言特性(如在 AspectJ 中),所以您可以声明自定义切入点,无论是静态的还是动态的。Spring 中的自定义切入点可以任意复杂。但是,如果可以,我们切面使用 AspectJ 切入点表达式语言。
Spring 的更高版本可能会提供对 JAC 提供的“语义切入点”的支持——例如,“更改目标对象中实例变量的所有方法”。
6.2. Spring 中的 Advice API
现在我们可以检查 Spring AOP 如何处理通知。
6.2.1. 切面生命周期
每个切面都是一个 Spring bean。切面实例可以在所有切面对象之间共享,或者对于每个切面对象都是唯一的。这对应于每个类或每个实例的切面。
每类切面最常使用。它适用于通用切面,例如事务Advisor。这些不依赖于代理对象的状态或添加新状态。它们仅作用于方法和参数。
每个实例的切面适用于介绍,以支持 mixins。在这种情况下,切面将状态添加到代理对象。
您可以在同一个 AOP 代理中混合使用共享切面和实例切面。
6.2.2. spring的切面类型
Spring 提供了几种切面类型,并且可以扩展以支持任意切面类型。本节介绍基本概念和标准通知类型。
环绕切面
Spring 中最基本的通知类型是围绕通知的拦截。
Spring 与使用方法拦截的环绕通知的 AOP Alliance
接口兼容。实现MethodInterceptor
和围绕通知实现的类也应该实现以下接口:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
invoke()
方法的参数MethodInvocation
暴露了被调用的方法、目标连接点、AOP 代理和方法的参数。 invoke()
方法应该返回调用的结果:连接点的返回值。
以下示例显示了一个简单的MethodInterceptor
实现:
public class DebugInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
Object rval = invocation.proceed();
System.out.println("Invocation returned");
return rval;
}
}
注意对MethodInvocation
的proceed()
方法的调用。这沿着拦截器链向连接点前进。大多数拦截器调用此方法并返回其返回值。但是, MethodInterceptor
与任何周围的切面一样,可以返回不同的值或抛出异常,而不是调用proceed 方法。但是,您不想在没有充分理由的情况下执行此操作。
MethodInterceptor
实现提供与其他符合 AOP 联盟的 AOP 实现的互操作性。本节其余部分讨论的其他通知类型实现了常见的 AOP 概念,但以特定于 Spring 的方式。虽然使用最具体的通知类型有优势,但如果您可能希望在另一个 AOP 框架中运行方面,请坚持使用MethodInterceptor
的环绕通知。请注意,切入点目前在框架之间不能互操作,AOP 联盟目前没有定义切入点接口。
前置通知
更简单的通知类型是之前的通知。这不需要MethodInvocation
对象,因为它只在进入方法之前被调用。
before 通知的主要优点是不需要调用proceed()
方法,因此不会因疏忽而未能沿拦截器链继续执行。
以下清单显示了该MethodBeforeAdvice
接口:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
(Spring 的 API 设计允许在通知之前使用字段,尽管通常的对象适用于字段拦截,而且 Spring 不太可能实现它。)
请注意,返回类型是void
. 之前通知可以在连接点运行之前插入自定义行为,但不能更改返回值。如果之前的通知抛出异常,它会停止拦截器链的进一步执行。异常会沿拦截器链向上传播。如果未选中或在调用方法的签名上,则直接将其传递给客户端。否则,它会被 AOP 代理包装在未经检查的异常中。
以下示例显示了 Spring 中的 before 通知,它计算所有方法调用:
public class CountingBeforeAdvice implements MethodBeforeAdvice {
private int count;
public void before(Method m, Object[] args, Object target) throws Throwable {
++count;
}
public int getCount() {
return count;
}
}
前置通知可以与任何切入点一起使用。
异常通知
如果连接点抛出异常,则在连接点返回后调用 Throws 通知。Spring 提供了类型化的 throws 切面。请注意,这意味着该 org.springframework.aop.ThrowsAdvice
接口不包含任何方法。它是一个标签接口,标识给定对象实现了一个或多个类型化的 throws 切面方法。这些应采用以下形式:
afterThrowing([Method, args, target], subclassOfThrowable)
只有最后一个参数是必需的。方法签名可能有一个或四个参数,这取决于通知方法是否对方法和参数感兴趣。接下来的两个清单显示了作为 throws 切面示例的类。
如果抛出RemoteException
(包括来自子类),则会调用以下切面:
public class RemoteThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
}
与前面的通知不同,下一个示例声明了四个参数,以便它可以访问调用的方法、方法参数和目标对象。如果抛出ServletException
,则调用以下切面:
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}
最后一个示例说明了如何在处理RemoteException
和ServletException
的单个类中使用这两种方法。任意数量的 throws 切面方法可以组合在一个类中。以下清单显示了最后一个示例:
public static class CombinedThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}
如果 throws-advice 方法本身抛出异常,它会覆盖原始异常(即,它会更改抛出给用户的异常)。覆盖异常通常是 RuntimeException,它与任何方法签名兼容。但是,如果 throws-advice 方法抛出检查异常,它必须匹配目标方法声明的异常,因此在某种程度上与特定目标方法签名耦合。不要抛出与目标方法的签名不兼容的未声明的检查异常!
抛出的切面可以与任何切入点一起使用。
返回通知
Spring 中的后返回通知必须实现该 org.springframework.aop.AfterReturningAdvice
接口,如下清单所示:
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
返回后的通知可以访问返回值(它不能修改)、调用的方法、方法的参数和目标。
返回通知后的以下内容计算所有未引发异常的成功方法调用:
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
private int count;
public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable {
++count;
}
public int getCount() {
return count;
}
}
此切面不会更改执行路径。如果它抛出异常,它会被抛出拦截器链而不是返回值。
返回后的通知可以与任何切入点一起使用。
引入通知
Spring 将引入通知视为一种特殊的拦截通知。
简介需要实现以下接口的 IntroductionAdvisor
和 IntroductionInterceptor
:
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
从AOP联盟MethodInterceptor
接口继承的invoke()
方法必须实现引入。也就是说,如果被调用的方法在引入的接口上,则引入拦截器负责处理方法调用——它不能调用proceed()
。
引入通知不能与任何切入点一起使用,因为它仅适用于类,而不是方法级别。您只能将引入通知与IntroductionAdvisor
一起使用 ,它具有以下方法:
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
ClassFilter getClassFilter();
void validateInterfaces() throws IllegalArgumentException;
}
public interface IntroductionInfo {
Class<?>[] getInterfaces();
}
没有MethodMatcher
,因此,没有Pointcut
与介绍切面相关联。只有类过滤是合乎逻辑的。
该getInterfaces()
方法返回此Advisor引入的接口。
该validateInterfaces()
方法用于内部查看引入的接口是否可以被IntroductionInterceptor
配置的.
考虑一个来自 Spring 测试套件的示例,假设我们想要为一个或多个对象引入以下接口:
public interface Lockable {
void lock();
void unlock();
boolean locked();
}
这说明了一个混合。我们希望能够将切面对象转换为Lockable
,无论它们的类型如何,并调用锁定和解锁方法。如果我们调用该lock()
方法,我们希望所有的 setter 方法都抛出一个LockedException
. 因此,我们可以添加一个方面,该方面提供了使对象不可变的能力,而他们对此一无所知:AOP 的一个很好的例子。
首先,我们需要一个可以完成繁重工作的IntroductionInterceptor
。在这种情况下,我们扩展了org.springframework.aop.support.DelegatingIntroductionInterceptor
便利类。我们可以直接实现IntroductionInterceptor
,但在大多数情况下使用DelegatingIntroductionInterceptor
是最好的。
DelegatingIntroductionInterceptor
旨在将介绍委托给所引入接口的实际实现,隐藏使用拦截来做到这一点。您可以使用构造函数参数将委托设置为任何对象。默认委托(使用无参数构造函数时)是this
. 因此,在下一个示例中,委托是DelegatingIntroductionInterceptor
的子类LockMixin
。给定一个委托(默认情况下,是它本身),一个DelegatingIntroductionInterceptor
实例会查找委托实现的所有接口(除了 IntroductionInterceptor
)并支持对其中任何一个的介绍。诸如子类LockMixin
可以调用该suppressInterface(Class intf)
方法来抑制不应该暴露的接口。然而,无论IntroductionInterceptor
准备支持多少接口,都应该使用 IntroductionAdvisor
控制实际暴露的接口。引入的接口隐藏了目标对同一接口的任何实现。
因此,LockMixin
扩展DelegatingIntroductionInterceptor
并自身实现Lockable
了。超类会自动选择可以支持引入的Lockable
,所以我们不需要指定。我们可以通过这种方式引入任意数量的接口。
注意locked
实例变量的使用。这有效地为目标对象中保存的状态添加了额外的状态。
以下示例显示了LockMixin
类:
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {
private boolean locked;
public void lock() {
this.locked = true;
}
public void unlock() {
this.locked = false;
}
public boolean locked() {
return this.locked;
}
public Object invoke(MethodInvocation invocation) throws Throwable {
if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
throw new LockedException();
}
return super.invoke(invocation);
}
}
通常,您不需要重写该invoke()
方法。DelegatingIntroductionInterceptor
的实现(如果方法被引入,则调用 delegate
方法,否则向连接点接入)通常就足够了。在本例中,我们需要添加一个检查:如果处于锁定模式,则不能调用任何 setter 方法。
所需的引入只需要保存一个不同的 LockMixin
实例并指定引入的接口(在这种情况下,只有 Lockable
)。一个更复杂的示例可能会引用引入拦截器(将被定义为原型)。在这种情况下,没有与LockMixin
相关的配置,因此我们使用new
. 下面的例子展示了我们的LockMixinAdvisor
类:
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
public LockMixinAdvisor() {
super(new LockMixin(), Lockable.class);
}
}
我们可以非常简单地应用这个切面,因为它不需要配置。(但是,没有IntroductionAdvisor
是不可能使用IntroductionInterceptor
的 。)与引入一样,Advisor必须是每个实例的,因为它是有状态的。对于每个被切入的对象,我们需要一个不同的LockMixinAdvisor
实例,因此需要 LockMixin
。 Advisor
包含被切入对象状态的一部分。
我们可以通过使用 Advised.addAdvisor()
方法或(推荐的方式)在 XML 配置中以编程方式应用此advisor 程序,就像任何其他advisor 程序一样。下面讨论的所有代理创建选项,包括“自动代理创建器”,都可以正确处理引入和有状态混合。
6.3. Spring 中的 Advisor API
在 Spring 中,Advisor 是一个仅包含一个与切入点表达式关联的切面对象的切面。
除了介绍的特殊情况外,任何Advisor都可以与任何切面一起使用。 org.springframework.aop.support.DefaultPointcutAdvisor
是最常用的Advisor类。它可以与MethodInterceptor
、BeforeAdvice
或 ThrowsAdvice
一起使用。
可以在同一个 AOP 代理中混合 Spring 中的Advisor和切面类型。例如,您可以在一个代理配置中对通知、抛出通知和通知之前使用拦截。Spring 自动创建必要的拦截器链。
6.4. 使用ProxyFactoryBean
创建 AOP 代理
如果您将 Spring IoC 容器(ApplicationContext
或BeanFactory
)用于您的业务对象(您应该这样做!),您希望使用 Spring 的 AOP FactoryBean
实现之一。(请记住,工厂 bean 引入了一个间接层,让它创建不同类型的对象。)
Spring AOP 支持也在幕后使用了工厂 bean。
|
在 Spring 中创建 AOP 代理的基本方法是使用 org.springframework.aop.framework.ProxyFactoryBean
. 这可以完全控制切入点、任何适用的切面及其顺序。但是,如果您不需要此类控制,则可以使用更简单的选项。
6.4.1. 基本
与其他 SpringFactoryBean
实现一样,ProxyFactoryBean
引入了间接级别。如果定义名为 foo
的 ProxyFactoryBean
,则引用 foo
的对象不会看到 ProxyFactoryBean
实例本身,而是看到由 ProxyFactoryBean
中的getObject()
方法的实现创建的对象。此方法创建一个包装目标对象的 AOP 代理。
使用一个ProxyFactoryBean
或另一个 IoC 感知类来创建 AOP 代理的最重要的好处之一是切面和切入点也可以由 IoC 管理。这是一个强大的特性,可以实现其他 AOP 框架难以实现的某些方法。例如,一个通知本身可能引用应用程序对象(除了目标,它应该在任何 AOP 框架中都可用),受益于依赖注入提供的所有可插入性。
6.4.2. JavaBean 属性
与 Spring 提供的大多数FactoryBean
实现一样, ProxyFactoryBean
该类本身就是一个 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。您可以在使用“全局”Advisor中找到使用此功能的示例。 -
单例:工厂是否应该返回单个对象,无论
getObject()
方法被调用的频率如何。几个FactoryBean
实现提供了这样的方法。默认值为true
。如果你想使用有状态的切面——例如,对于有状态的 mixins——使用原型切面和false
.
6.4.3. 基于 JDK 和 CGLIB 的代理
本节是关于如何ProxyFactoryBean
选择为特定目标对象(将被代理)创建基于 JDK 的代理或基于 CGLIB 的代理的权威文档。
在 Spring 的 1.2.x 和 2.0 版本之间,创建基于 JDK 或 CGLIB 的代理 ProxyFactoryBean
的行为发生了变化。现在ProxyFactoryBean
在自动检测接口方面表现出与TransactionProxyFactoryBean
类相似的语义 。
如果要代理的目标对象的类(以下简称目标类)没有实现任何接口,则创建基于CGLIB的代理。这是最简单的场景,因为 JDK 代理是基于接口的,没有接口意味着 JDK 代理甚至是不可能的。您可以插入目标 bean 并通过设置interceptorNames
属性来指定拦截器列表。请注意,即使 ProxyFactoryBean
的proxyTargetClass
属性已设置为false
,也会创建基于 CGLIB 的代理。(这样做毫无意义,最好从 bean 定义中删除,因为它充其量是多余的,最坏的情况是令人困惑。)
如果目标类实现一个(或多个)接口,则创建的代理类型取决于ProxyFactoryBean
.
如果 ProxyFactoryBean
的proxyTargetClass
属性已设置为true
,则创建基于 CGLIB 的代理。这是有道理的,并且符合最小意外原则。即使ProxyFactoryBean
的proxyInterfaces
属性 已设置为一个或多个完全限定的接口名称,该proxyTargetClass
属性设置为true
这一事实也会导致基于 CGLIB 的代理生效。
如果 ProxyFactoryBean
的proxyInterfaces
属性已设置为一个或多个完全限定的接口名称,则会创建一个基于 JDK 的代理。proxyInterfaces
创建的代理实现了属性中指定的所有接口。如果目标类碰巧实现了比proxyInterfaces
属性中指定的接口多得多的接口,那很好,但是返回的代理不会实现这些额外的接口。
如果 ProxyFactoryBean
的 proxyInterfaces
属性尚未设置,但目标类确实实现了一个(或多个)接口,则 ProxyFactoryBean
会自动检测目标类确实至少实现了一个接口,并且基于 JDK 的代理被建造。实际被代理的接口是目标类实现的所有接口。实际上,这与向 proxyInterfaces
属性提供目标类实现的每个接口的列表相同。然而,它的工作量明显减少,并且不太容易出现印刷错误。
6.4.4. 代理接口
考虑一个简单的例子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
属性需要一个 String
列表,其中包含当前工厂中拦截器或Advisor的 bean 名称。您可以在返回之前、之后使用Advisor、拦截器和抛出切面对象。Advisor的顺序很重要。
您可能想知道为什么该列表不包含 bean 引用。这样做的原因是,如果ProxyFactoryBean
的单例属性设置为false
,它必须能够返回独立的代理实例。如果任何Advisor本身是原型,则需要返回一个独立的实例,因此必须能够从工厂获取原型的实例。持有参考资料是不够的。
前面显示的person
bean 定义可以用来代替Person
实现,如下所示:
Person person = (Person) factory.getBean("person");
同一个 IoC 上下文中的其他 bean 可以表达对它的强类型依赖,就像普通的 Java 对象一样。以下示例显示了如何执行此操作:
<bean id="personUser" class="com.mycompany.PersonUser">
<property name="person"><ref bean="person"/></property>
</bean>
此示例中的PersonUser
类公开了一个类型为Person
的属性。就它而言,可以透明地使用 AOP 代理来代替“真实”的人员实现。但是,它的类将是一个动态代理类。可以将其转换为Advised
接口(稍后讨论)。
您可以使用匿名内部 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
。如果我们想要阻止应用程序上下文的用户获取对不切面的对象的引用,或者需要避免 Spring IoC 自动装配的任何歧义,这很有用。可以说,还有一个优点是ProxyFactoryBean
定义是独立的。但是,有时能够从工厂获得不切面的目标实际上可能是一种优势(例如,在某些测试场景中)。
6.4.5. 代理类
如果您需要代理一个类而不是一个或多个接口怎么办?
想象一下,在我们前面的示例中,没有 Person
接口。我们需要建议一个名为Person
的类,它没有实现任何业务接口。在这种情况下,您可以将 Spring 配置为使用 CGLIB
代理而不是动态代理。为此,请将前面显示的 ProxyFactoryBean
上的 proxyTargetClass
属性设置为 true
。虽然最好对接口而不是类进行编程,但在处理遗留代码时,建议未实现接口的类的能力可能会很有用。 (一般来说,Spring 不是规定性的。虽然它使应用良好实践变得容易,但它避免了强制采用特定的方法。)
如果你愿意,你可以在任何情况下强制使用 CGLIB,即使你有接口。
CGLIB 代理通过在运行时生成目标类的子类来工作。Spring 将这个生成的子类配置为将方法调用委托给原始目标。子类用于实现装饰器模式,编织在通知中。
CGLIB 代理通常应该对用户透明。但是,有一些问题需要考虑:
Final
类不能被代理,因为他们无法拓展final
方法不能被切面,因为他们无法被覆写private
方法不能被切面,因为他们无法被覆写
无需将 CGLIB 添加到您的类路径中。从 Spring 3.2 开始,CGLIB 被重新打包并包含在 spring-core JAR 中。换句话说,基于 CGLIB 的 AOP 像 JDK 动态代理一样“开箱即用”。
CGLIB 代理和动态代理之间几乎没有性能差异。在这种情况下,性能不应成为决定性的考虑因素。
6.4.6. 使用“全局”Advisor
通过将星号附加到拦截器名称,所有具有与星号之前的部分匹配的 bean 名称的Advisor都将添加到Advisor链中。如果您需要添加一组标准的“全局”Advisor,这会派上用场。以下示例定义了两个全局Advisor:
<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"/>
6.5. 简洁的代理定义
尤其是在定义事务代理时,您最终可能会得到许多类似的代理定义。使用父 bean 和子 bean 定义以及内部 bean 定义,可以产生更清晰和更简洁的代理定义。
首先,我们为代理创建一个父、模板、bean定义,如下:
<bean id="txProxyTemplate" abstract="true"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
这本身永远不会被实例化,因此它实际上可能是不完整的。然后,需要创建的每个代理都是一个子 bean 定义,它将代理的目标包装为内部 bean 定义,因为目标永远不会单独使用。以下示例显示了这样一个子 bean:
<bean id="myService" parent="txProxyTemplate">
<property name="target">
<bean class="org.springframework.samples.MyServiceImpl">
</bean>
</property>
</bean>
您可以覆盖父模板中的属性。在以下示例中,我们覆盖了事务传播设置:
<bean id="mySpecialService" parent="txProxyTemplate">
<property name="target">
<bean class="org.springframework.samples.MySpecialServiceImpl">
</bean>
</property>
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="store*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
请注意,在父 bean 示例中,我们通过将abstract
属性设置为true
明确地将父 bean 定义标记为抽象, 如前所述,因此它实际上可能不会被实例化。默认情况下,应用程序上下文(但不是简单的 bean 工厂)预先实例化所有单例。因此,重要的是(至少对于单例 bean),如果您有一个(父)bean 定义打算仅用作模板,并且此定义指定了一个类,则必须确保将abstract
属性设置为true
. 否则,应用程序上下文实际上会尝试预先实例化它。
6.6. 以编程方式创建 AOP 代理ProxyFactory
使用 Spring 以编程方式创建 AOP 代理很容易。这使您可以在不依赖 Spring IoC 的情况下使用 Spring AOP。
目标对象实现的接口被自动代理。以下清单显示了为目标对象创建代理,其中包含一个拦截器和一个Advisor:
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();
第一步是构造一个类型为 org.springframework.aop.framework.ProxyFactory
的对象 。您可以像前面的示例一样使用目标对象创建它,或者指定要在备用构造函数中代理的接口。
您可以添加切面(使用拦截器作为一种特殊的建议)和/或切面程序,并在ProxyFactory
的生命周期中操纵它们。如果添加 IntroductionInterceptionAroundAdvisor
,则可以使代理实现其他接口。
ProxyFactory
(从AdvisedSupport
继承 )还有一些方便的方法,可以让您添加其他切面类型,例如 before 和 throws 切面。 AdvisedSupport
是ProxyFactory
和ProxyFactoryBean
的超类。
在大多数应用程序中,将 AOP 代理创建与 IoC 框架集成是最佳实践。我们切面您使用 AOP 从 Java 代码外部化配置,就像您通常应该做的那样。
6.7. 操作切面对象
无论您如何创建 AOP 代理,您都可以使用 org.springframework.aop.framework.Advised
接口来操作它们。任何 AOP 代理都可以转换为该接口,无论它实现了哪些其他接口。该接口包括以下方法:
Advisor[] getAdvisors();
void addAdvice(Advice advice) throws AopConfigException;
void addAdvice(int pos, Advice advice) throws AopConfigException;
void addAdvisor(Advisor advisor) throws AopConfigException;
void addAdvisor(int pos, Advisor advisor) throws AopConfigException;
int indexOf(Advisor advisor);
boolean removeAdvisor(Advisor advisor) throws AopConfigException;
void removeAdvisor(int index) throws AopConfigException;
boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;
boolean isFrozen();
该getAdvisors()
方法为已添加到工厂的每个Advisor、拦截器或其他切面类型返回一个Advisor
。如果您添加了Advisor
,则在此索引处返回的Advisor就是您添加的对象。如果您添加了拦截器或其他通知类型,Spring 会将其包装在一个Advisor中,并带有一个始终返回的切入点true
。因此,如果您添加了 aMethodInterceptor
,则为该索引返回的Advisor是 DefaultPointcutAdvisor
,它返回您的 MethodInterceptor
和匹配所有类和方法的切入点。
这些addAdvisor()
方法可用于添加任何Advisor
. 通常,持有切入点和切面的Advisor是通用DefaultPointcutAdvisor
的,您可以将其与任何切面或切入点一起使用(但不能用于介绍)。
默认情况下,即使已创建代理,也可以添加或删除Advisor或拦截器。唯一的限制是不可能添加或删除介绍Advisor,因为工厂的现有代理不显示界面更改。(您可以从工厂获得一个新的代理来避免这个问题。)
以下示例显示了将 AOP 代理强制转换为Advised
接口并检查和操作其切面:
Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");
// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(new DebugInterceptor());
// Add selective advice using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));
assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
在生产中修改关于业务对象的切面是否可取(不是双关语)是值得怀疑的,尽管毫无疑问,有合法的使用案例。但是,它在开发中非常有用(例如,在测试中)。我们有时发现能够以拦截器或其他切面的形式添加测试代码非常有用,进入我们想要测试的方法调用。(例如,通知可以进入为该方法创建的事务中,可能在将事务标记为回滚之前运行 SQL 以检查数据库是否正确更新。)
根据您创建代理的方式,您通常可以设置一个frozen
标志。在这种情况下,该Advised
isFrozen()
方法返回true
,并且任何通过添加或删除来修改切面的尝试都会导致AopConfigException
. 冻结切面对象状态的能力在某些情况下很有用(例如,防止调用代码删除安全拦截器)。
6.8. 使用“自动代理”工具
到目前为止,我们已经考虑过使用ProxyFactoryBean
或类似的工厂 bean 显式创建 AOP 代理。
Spring 还允许我们使用“自动代理”bean 定义,它可以自动代理选定的 bean 定义。这是建立在 Spring 的“bean 后处理器”基础设施之上的,它可以在容器加载时修改任何 bean 定义。
在这个模型中,您在 XML bean 定义文件中设置了一些特殊的 bean 定义来配置自动代理基础设施。这使您可以声明符合自动代理条件的目标。你不需要使用ProxyFactoryBean
.
有两种方法可以做到这一点:
- 通过使用在当前上下文中引用特定 bean 的自动代理创建者。
- 一个值得单独考虑的自动代理创建的特殊情况:由源级元数据属性驱动的自动代理创建。
6.8.1. 自动代理 Bean 定义
本节介绍 org.springframework.aop.framework.autoproxy
包提供的自动代理创建者。
BeanNameAutoProxyCreator
BeanNameAutoProxyCreator
类是一个BeanPostProcessor
,会自动为名称与文字值或通配符匹配的 bean 创建 AOP 代理。以下示例显示了如何创建BeanNameAutoProxyCreator
bean:
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="jdk*,onlyJdk"/>
<property name="interceptorNames">
<list>
<value>myInterceptor</value>
</list>
</property>
</bean>
与 ProxyFactoryBean
一样,有一个interceptorNames
属性而不是拦截器列表,以允许原型Advisor的正确行为。命名为“拦截器”可以是Advisor或任何切面类型。
与一般的自动代理一样,使用BeanNameAutoProxyCreator
的主要目的是将相同的配置一致地应用于多个对象,并且配置量最少。将声明性事务应用于多个对象是一种流行的选择。
名称匹配的 Bean 定义(例如前面示例中的 jdkMyBean 和 onlyJdk)是带有目标类的普通旧式 Bean 定义。 AOP 代理由 BeanNameAutoProxyCreator 自动创建。 相同的切面适用于所有匹配的 bean。请注意,如果使用了Advisor(而不是前面示例中的拦截器),则切入点可能会以不同的方式应用于不同的 bean。
DefaultAdvisorAutoProxyCreator
一个更通用且极其强大的自动代理创建者是 DefaultAdvisorAutoProxyCreator
. 这会自动在当前上下文中应用符合条件的Advisor,而无需在自动代理Advisor的 bean 定义中包含特定的 bean 名称。它提供了与 BeanNameAutoProxyCreator
相同的配置一致和避免重复的优点。
使用此机制涉及:
- 指定
DefaultAdvisorAutoProxyCreator
bean 定义。 - 在相同或相关的上下文中指定任意数量的Advisor。请注意,这些必须是Advisor,而不是拦截器或其他切面。这是必要的,因为必须有一个切入点来评估,以检查每个切面对候选 bean 定义的资格。
DefaultAdvisorAutoProxyCreator
自动评估每个Advisor中包含的切入点,以查看它应该对每个业务对象应用什么(如果有)切面(例如businessObject1
和businessObject2
示例中的)。
这意味着可以将任意数量的Advisor自动应用于每个业务对象。如果任何Advisor中没有切入点与业务对象中的任何方法匹配,则不会代理该对象。当为新业务对象添加 bean 定义时,它们会在必要时自动代理。
自动代理通常具有使调用者或依赖项无法获得不切面的对象的优点。调用ApplicationContext
的getBean("businessObject1")
会返回一个 AOP 代理,而不是目标业务对象。(前面显示的“inner bean”习语也提供了这个好处。)
以下示例创建一个DefaultAdvisorAutoProxyCreator
bean 和本节中讨论的其他元素:
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>
<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>
<bean id="businessObject1" class="com.mycompany.BusinessObject1">
<!-- Properties omitted -->
</bean>
<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>
如果您想将相同的切面一致地应用于许多业务对象,DefaultAdvisorAutoProxyCreator
非常有用。一旦基础设施定义到位,您就可以添加新的业务对象,而无需包括特定的代理配置。您还可以轻松地添加其他方面(例如,跟踪或性能监控方面),而只需对配置进行最少的更改。
DefaultAdvisorAutoProxyCreator
提供对过滤(通过使用命名约定以便仅评估某些Advisor,这允许在同一工厂中使用多个不同配置的 AdvisorAutoProxyCreators)和排序的支持。如果这是一个问题,Advisor可以实现该org.springframework.core.Ordered
接口以确保正确排序。前面示例中使用的TransactionAttributeSourceAdvisor
具有可配置的 order 值。默认设置是无序的。
6.9. 使用TargetSource
实现
Spring 提供了TargetSource
的概念,在 org.springframework.aop.TargetSource
接口中表达。该接口负责返回实现连接点的“目标对象”。TargetSource
每次 AOP 代理处理方法调用时,都会要求实现提供目标实例。
使用 Spring AOP 的开发人员通常不需要直接使用TargetSource
实现,但这提供了支持池、热插拔和其他复杂目标的强大方法。例如,通过使用TargetSource
池来管理实例,池可以为每次调用返回不同的目标实例。
如果不指定 TargetSource
,则使用默认实现来包装本地对象。每次调用都返回相同的目标(如您所料)。
本节的其余部分描述了 Spring 提供的标准目标源以及如何使用它们。
使用自定义目标源时,您的目标通常需要是原型而不是单例 bean 定义。这允许 Spring 在需要时创建新的目标实例。
6.9.1. 热插拔目标源
org.springframework.aop.target.HotSwappableTargetSource
存在让 AOP 代理的目标被切换,同时让调用者保留对它的引用。
更改目标源的目标会立即生效。HotSwappableTargetSource
是线程安全的。
您可以使用HotSwappableTargetSource 上的swap()
方法更改目标,如以下示例所示:
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
以下示例显示了所需的 XML 定义:
<bean id="initialTarget" class="mycompany.OldTarget"/>
<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
<constructor-arg ref="initialTarget"/>
</bean>
<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="swapper"/>
</bean>
前面的swap()
调用更改了可交换 bean 的目标。持有对该 bean 的引用的客户端不知道更改,但会立即开始命中新目标。
虽然这个例子没有添加任何切面(使用 TargetSource
不需要添加切面),但任何TargetSource
可以与任意切面一起使用。
6.9.2. 合并目标源
使用池化目标源提供了与无状态会话 EJB 类似的编程模型,其中维护了相同实例的池,方法调用将释放池中的对象。
Spring pooling 和 SLSB pooling 的一个关键区别是 Spring pooling 可以应用于任何 POJO。与一般的 Spring 一样,此服务可以以非侵入方式应用。
Spring 提供对 Commons Pool 2.2 的支持,它提供了相当高效的池化实现。您需要commons-pool
应用程序类路径中的 Jar 才能使用此功能。您还可以子类化 org.springframework.aop.target.AbstractPoolingTargetSource
以支持任何其他池 API。
Commons Pool 1.5+ 也受支持,但自 Spring Framework 4.2 起已弃用。
以下清单显示了一个示例配置:
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
scope="prototype">
... properties omitted
</bean>
<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
<property name="maxSize" value="25"/>
</bean>
<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="poolTargetSource"/>
<property name="interceptorNames" value="myInterceptor"/>
</bean>
请注意,目标对象(在前面的示例中的businessObjectTarget
)必须是原型。这使PoolingTargetSource
实现可以创建目标的新实例以根据需要增加池。有关其属性的信息,请参阅您希望使用的AbstractPoolingTargetSource
具体子类的javadoc 。。maxSize
是最基本的,并且始终保证存在。
在这种情况下,需要在同一 IoC 上下文中定义拦截器myInterceptor
的名称。但是,您无需指定拦截器即可使用池化。如果您只想要池而不需要其他切面,则根本不要设置该 interceptorNames
属性。
您可以将 Spring 配置为能够将任何池化对象强制转换为 org.springframework.aop.target.PoolingConfig
接口,该接口通过介绍公开有关池的配置和当前大小的信息。您需要定义类似于以下内容的Advisor:
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="poolTargetSource"/>
<property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>
该Advisor是通过调用 AbstractPoolingTargetSource
类上的便利方法获得的,因此使用MethodInvokingFactoryBean
. 此Advisor的名称(poolConfigAdvisor
此处为 )必须在ProxyFactoryBean
公开池对象的拦截器名称列表中。
示例如下:
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
通常不需要池化无状态服务对象。我们不认为它应该是默认选择,因为大多数无状态对象自然是线程安全的,如果资源被缓存,实例池是有问题的。
使用自动代理可以实现更简单的池化。您可以设置TargetSource
任何自动代理创建者使用的实现。
6.9.3. 原型目标源
设置“原型”目标源类似于设置TargetSource
池。在这种情况下,每次方法调用都会创建一个新的目标实例。尽管在现代 JVM 中创建新对象的成本并不高,但连接新对象(满足其 IoC 依赖性)的成本可能会更高。因此,如果没有充分的理由,您不应该使用这种方法。
为此,您可以修改前面显示的poolTargetSource
定义,如下所示(为了清楚起见,我们还更改了名称):
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>
唯一的属性是目标 bean 的名称。在 TargetSource
实现中使用继承来确保一致的命名。与池化目标源一样,目标 bean 必须是原型 bean 定义。
6.9.4.ThreadLocal
目标来源
如果您需要为每个传入请求(即每个线程)创建一个对象,则ThreadLocal
目标源很有用。ThreadLocal
的概念提供了一个 JDK 范围的工具,可以透明地在线程旁边存储资源。设置 aThreadLocalTargetSource
与为其他类型的目标源解释的几乎相同,如以下示例所示:
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
</bean>
ThreadLocal
当在多线程和多类加载器环境中错误地使用它们时,实例会出现严重的问题(可能导致内存泄漏)。您应该始终考虑将 threadlocal 包装在其他类中,并且永远不要直接使用ThreadLocal
本身(包装类除外)。此外,您应该始终记住正确设置和取消设置(后者仅涉及对 ThreadLocal.set(null)
的调用)线程本地的资源。在任何情况下都应该取消设置,因为不取消设置可能会导致问题行为。Spring 的 ThreadLocal
支持为您执行此操作,并且应该始终考虑支持在 ThreadLocal
没有其他适当处理代码的情况下使用实例。
6.10. 定义新的切面类型
Spring AOP 被设计为可扩展的。虽然目前在内部使用了拦截实现策略,但除了在通知周围、之前、抛出通知和返回通知之后的拦截之外,还可以支持任意通知类型。
该org.springframework.aop.framework.adapter
包是一个 SPI 包,它允许在不更改核心框架的情况下添加对新的自定义切面类型的支持。自定义类型的唯一约束Advice
是它必须实现 org.aopalliance.aop.Advice
标记接口。
有关详细信息,请参阅org.springframework.aop.framework.adapter
javadoc。
评论区