SpringAOP框架高级特性的使用
立即解锁
发布时间: 2025-08-19 02:32:41 阅读量: 2 订阅数: 11 


Spring框架下的Java开发实践与进阶
### Spring AOP框架高级特性的使用
#### 1. 目标源(TargetSources)
通常,拦截器链通过反射调用目标对象上的适当方法来结束。一般在创建代理时就已知目标对象,使用Spring AOP的目的通常是在目标对象周围添加声明式行为。
不过,Spring终止拦截器链的方式更为复杂。Spring在调用适当方法之前才获取目标对象引用,该目标对象从`TargetSource`接口的实现中获取,该接口定义如下:
```java
public interface TargetSource {
Class getTargetClass();
boolean isStatic();
Object getTarget() throws Exception;
void releaseTarget(Object target) throws Exception;
}
```
在最简单、最常见的情况下,当预先知道目标对象时,Spring会创建`TargetSource`接口的实现,开发者无需关注。这个实现(`SingletonTargetSource`)在调用`getTarget()`方法时,在所有情况下都会返回相同的实例。
然而,`TargetSource`接口也支持更复杂的行为,例如从池或工厂获取目标实例。Spring自带了一些`TargetSource`实现,开发者也可以轻松实现自己的`TargetSource`。
#### 2. 可热交换目标源(HotSwappableTargetSource)
除了用于“单例”目标的默认实现外,最重要的`TargetSource`实现是`HotSwappableTargetSource`。它使我们能够以线程安全的方式在代理下切换目标对象,这在依赖注入框架中尤为重要。
默认情况下,热交换功能未启用,Spring会使用普通的POJO来满足依赖,这样能提供最佳性能,适用于大多数情况。要启用热交换,需要进行显式配置。
`HotSwappableTargetSource`类提供了一个新方法:
```java
Object swap(Object o);
```
该方法返回旧的目标对象。
以下是`HotSwappableTargetSource`的典型使用示例:
```xml
<bean id="target1" class="com.mycompany.MyInterfaceImpl">
</bean>
<bean id="target2" class="com.mycompany.MyInterfaceImpl">
</bean>
<bean id="swapper"
class="org.springframework.aop.target.HotSwappableTargetSource">
<constructor-arg><ref local="target1"/></constructor-arg>
</bean>
<bean id="swappable"
class="org.springframework.aop.framework.ProxyFactoryBean"
>
<property name="targetSource"><ref local="swapper"/></property>
</bean>
```
编程式交换的代码如下:
```java
MyInterface target1 = (MyInterface) beanFactory.getBean("target1");
MyInterface target2 = (MyInterface) beanFactory.getBean("target2");
MyInterface proxied = (MyInterface) beanFactory.getBean("swappable");
// 现在对proxied的调用会命中target1
HotSwappableTargetSource swapper = (HotSwappableTargetSource)
beanFactory.getBean("swapper");
Object old = swapper.swap(target2);
// old将是target1
// 现在对proxied的调用会命中target2
```
在这个例子中,两个目标都来自bean定义。很可能初始目标来自bean定义,而新目标可能来自其他源。
`HotSwappableTargetSource`非常适合构建基础设施,例如支持某些频繁更改的应用程序对象的动态重新配置。
#### 3. 池化目标源(Pooling Target Source)
`TargetSource`接口的另一个重要用途是支持池化。在这种情况下,需要的不是单个目标,而是目标池,拦截器链末尾的实际调用将指向空闲的目标。
拦截器实例通常是共享的,因为拦截器通常是线程安全的,如事务拦截器。代理本身是单例实例,位于目标对象池的前面。
##### 3.1 开箱即用的使用
Spring开箱即用地支持Apache Commons Pool(http://jakarta.apache.org/commons/pool/),这是一个开源的池实现,性能可接受,在负载下很健壮。
可以按以下方式为任何POJO配置池:
```xml
<bean id="prototypeTest" class="org.springframework.aop.interceptor.SideEffectBean"
singleton="false">
<property name="count"><value>10</value></property>
</bean>
<bean id="poolTargetSource"
class="org.springframework.aop.target.CommonsPoolTargetSource">
<property name="targetBeanName"><value>prototypeTest</value></property>
<property name="maxSize"><value>25</value></property>
</bean>
<bean id="pooled"
class="org.springframework.aop.framework.ProxyFactoryBean"
>
<property name="targetSource"><ref local="poolTargetSource"/></property>
<property name="interceptorNames"><value>whatever...</value></property>
</bean>
```
需要注意的是,虽然`pooled`是单例bean定义,但目标`prototypeTest`必须是非单例定义,以便Spring配置的池可以根据需要实例化独立的实例。
可以池化的实际类没有限制,虽然池化模型与无状态会话EJB的模型类似,但它适用于任何POJO。
##### 3.2 使用自定义池化实现
和Spring的其他功能一样,池化`TargetSource`支持自定义。可以扩展`org.springframework.aop.target.AbstractPoolingTargetSource`来使用除Commons Pool之外的池提供程序,需要实现以下方法:
```java
protected final void createPool(BeanFactory beanFactory);
public Object getTarget() throws Exception;
public void releaseTarget(Object target) throws Exception;
public void destroy();
```
任何池API通常都需要一个回调来为池构造新对象,可以使用继承的`newPrototypeInstance()`方法来实现,如Commons Pool实现中的以下示例:
```java
public Object makeObject() {
return newPrototypeInstance();
}
```
基本的池配置参数(如`maxSize`)继承自`org.springframework.aop.target.AbstractPoolingTargetSource`,应该为任何特定于池的配置添加属性。
##### 3.3 何时使用池化
Spring基于AOP的池化功能通常用作本地无状态会话EJB的替代方案,它提供了相同的基本模型:一个无状态服务对象池,任何对象都可以由给定的方法调用调用。
然而,无状态服务对象通常很容易实现线程安全,通常不需要读写实例变量,或者至少在配置完成后不需要读写实例变量。而且通常拥有对象的单个实例比池更方便,例如在需要维护对象缓存的情况下。
不要过于急切地使用池化,它通常是不必要的。考虑到现代JVM的高效垃圾回收,在J2EE架构中,池化是一个被高估的概念。
##### 3.4 暴露池统计信息
如果想暴露池化对象的池统计信息,可以按以下方式操作:
```xml
<bean id="poolConfigAdvisor"
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject"><ref local="poolTargetSource" /></property>
<property name="targetMethod"><value>getPoolingConfigMixin</value></property>
</bean>
<bean id="pooled"
class="org.springframework.aop.framework.ProxyFactoryBean"
>
<property name="targetSource"><ref local="poolTargetSource"/></property>
<property name="interceptorNames"><value>poolConfigAdvisor</value></property>
<!-- 由于有一个混合器,并且想避免丢失类,因为没有目标 -->
<property name="proxyTargetClass"><value>true</value></property>
</bean>
```
这使用了引入(在后面讨论)来使池前面的AOP代理实现`PoolingConfig`接口。
现在可以将从池中获取的任何对象强制转换为`PoolingConfig`并查询其统计信息,假设池化对象是池前面的AOP代理的引用:
```java
PoolingConfig conf = (PoolingConfig) pooled;
assertEquals(1, conf.getInvocations());
assertEquals("Correct target source", 25, conf.getMaxSize());
```
这是引入潜力的一个有趣示例。
#### 4. 自定义目标源(Custom TargetSources)
开发者也可以自由实现自己的`TargetSource`。例如,有用户需要使用多个数据源,并根据上下文在运行时选择一个。
传统的面向对象方法是实现一个`DataSource`装饰器,根据状态委托给一个或另一个`DataSource`。由于`DataSource`接口上的方法数量不多,这不会太麻烦,但代码会很冗长,并且是一个很好的横切关注点示例。
可以通过实现一个`TargetSource`来满足这个需求,该`TargetSource`根据上下文(其他应用程序对象的状态、当前用户或任何其他上下文信息)选择正确的`DataSource`。无论`TargetSource`在运行时选择哪个目标,任何拦截器链都将被共享。
也可以使用核心Spring IoC容器的方法注入功能来解决这个问题,这种方法的优点是对象本身可以调用`getDataSource()`方法,在这种情况下需要实现一个自定义的`MethodReplacer`。
#### 5. 以编程方式访问目标源(Programmatic Access to the TargetSource)
可以通过将任何AOP代理强制转换为`Advised`并使用`TargetSource`属性来获取甚至修改`TargetSource`。这对于允许以编程方式进行配置更改的`TargetSource`实现(如`HotSwappableTargetSource`)最为有用。
在大多数情况下,不会显式设置`TargetSource`,`TargetSource`将是`SingletonTargetSource`类型。需要注意的是,即使在没有目标对象的特殊情况下,`TargetSource`也永远不会为`null`。
#### 6. 无目标对象的情况(Doing Without a Target Object)
偶尔会没有目标对象,在这种情况下,拦截器将负责返回结果。`TargetSource`将是`org.springframework.aop.target.EmptyTargetSource`的规范实例,其`getTarget()`方法始终返回`null`。
当所有行为都由拦截器提供时(可能包括通常自身持有目标的引入拦截器),可以不使用目标对象。最常见的情况是,终端拦截器使用除反射调用目标对象之外的策略来跳出拦截器栈。
例如,Spring EJB代理(如`org.springframework.ejb.access.LocalSlsbInvokerInterceptor`)就采用了这种方法:它们不调用`proceed()`(这会导致AOP基础设施反射调用从`TargetSource`获取的目标),而是在本地或远程EJB上调用`MethodInvocation`中指定的方法,远程技术也使用相同的技术。
需要记住,设计用于跳出拦截器链而不是调用`proceed`的拦截器通常应该是拦截器链中的最后一个。
也有可能(但更不常见)让拦截器本身实现方法,链中的任何拦截器都可以选择返回一个值(或抛出异常)而不是调用`proceed()`方法。需要注意的是,使用CGLIB代理时不能没有目标对象。
#### 7. 提前终止拦截器链(Terminating the Interceptor Chain Early)
当没有目标对象或出于其他原因时,可能希望链中的通知提前终止。这可以通过两种方式发生:抛出异常或返回一个值而不是调用`proceed`。
任何通知都可以抛出异常,该异常将报告给客户端。只有方法拦截器可以返回而不是调用`proceed()`,其他通知类型(如`MethodBeforeAdvice`)没有能力改变返回值。
如果`MethodInterceptor`或其他通知抛出异常,情况如下:
- 如果不是受检异常,客户端将看到该异常。
- 如果是方法签名上的受检异常,客户端将看到该异常。
- 如果是方法签名上没有的受检异常,它将被包装在`java.lang.reflect.UndeclaredThrowableException`中并报告给客户端。
#### 8. 使用引入(Using Introduction)
引入意味着使被通知的对象(在这种情况下是Spring AOP代理)实现目标对象未实现的额外接口,甚至可以创建一个仅由引入组成、没有目标对象的代理。
引入有很多用途:
- 创建混合器,添加对象中保存的状态,这可能是最重要的用途。
- 暴露与特殊`TargetSource`关联的额外状态,例如在Spring的脚本支持中使用。
- 以不同的方式暴露对象或对象图,例如使对象图实现XML DOM接口。
在AspectJ术语中,引入被称为“类型内声明”。
Spring提供了两种进行引入的方式:
- `IntroductionInfo`接口:任何通知都可以实现该接口,以表明它引入了一个或多个接口。这样的通知可以与任何切入点一起使用,甚至可以没有切入点。
- `IntroductionAdvisor`接口:一种特殊的引入顾问类型,不包括`MethodMatcher`,因为它与引入无关,实际上它扩展了`IntroductionInfo`接口。
第一种方法更简单,自Spring 1.1.1引入以来就被推荐使用(在早期版本的Spring中,进行引入总是需要一个`IntroductionAdvisor`)。
以下是一个实际示例,假设要使任何对象“可锁定”,使其实现`Lockable`接口以暴露和控制锁定状态,并在其状态被锁定时禁用所有设置器方法。
`Lockable`接口可能如下所示:
```java
public interface Lockable {
void lock();
void unlock();
boolean locked();
}
```
现在需要一个进行引入的通知,它必须实现`IntroductionInfo`接口,该接口包含以下方法:
```java
Class[] getInterfaces()
```
该方法用于返回顾问或通知引入的接口。
通常,在实现引入通知时会扩展Spring方便的`DelegatingIntroductionInterceptor`类,该类会自动检测子类实现的任何接口,并在`IntroductionInfo.getInterfaces()`方法中返回该接口。
因此,简单的引入可以如下所示:
```java
public class LockMixin extends DelegatingIntroductionInterceptor
implements Lockable {
private boolean locked
```
0
0
复制全文
相关推荐










