这是在Spring AOP中出现的一个类。作用是:可以在代理bean运行过程中,动态更新实际bean对象。HotSwappableTargetSource类实现了TargetSource接口。对外暴露getTarget方法,提供真正的target对象。再说的明白一点,HotSwappableTargetSourc是对真正target对象的封装。在Spring中的源码中,体现在JdkDynamicAopProxy中的invoke方法中。如下图:
TargetSource targetSource = this.advised.targetSource;
target = targetSource.getTarget();
//...省略无关代码...
Class<?> targetClass = (target != null ? target.getClass() : null);
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
//...省略无关代码...
MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
Spring为什么要这么设计呢?直接用target对象不行吗?为什么要封装一层呢?
仔细读读源码就可以知道,这么做的好处是:我们将target封装起来,方便我们对target对象进行相关操作。同时,我们可以提供多种封装的实现。对外统一暴露TargetSource接口。这么做的灵活性显然是非常的高。
好了,说回HotSwappableTargetSource。上面提到这个TargetSource可以动态更新bean的实现。那它是怎么做到的呢?
下面是动态更新的底层实现,其实非常简单。就是简单的将新对象赋予了target引用。
/**
* Swap the target, returning the old target object.
* @param newTarget the new target object
* @return the old target object
* @throws IllegalArgumentException if the new target is invalid
*/
public synchronized Object swap(Object newTarget) throws IllegalArgumentException {
Assert.notNull(newTarget, "Target object must not be null");
Object old = this.target;
this.target = newTarget;
return old;
}
如何使用这个HotSwappableTargetSource呢?我们举个例子
//Spring的配置文件
<bean id="sayHello" class="cn.org.bjca.advice.SayHelloMethodInterceptor"/>
<bean id="targetImpl1" class="cn.org.bjca.advice.SayHelloImpl1"/>
<bean id="targetImpl2" class="cn.org.bjca.advice.SayHelloImpl2"/>
<bean id="swappableTargetSource" class="org.springframework.aop.target.HotSwappableTargetSource">
<constructor-arg ref="targetImpl1"/>
</bean>
<bean id="proxyFactory" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyTargetClass" value="false"/>
<property name="interfaces" value="cn.org.bjca.advice.SayHello"/>
<property name="targetSource" ref="swappableTargetSource"/>
<property name="interceptorNames" value="sayHello"/>
</bean>
如图,我们建立了一个SayHelloMethodInterceptor类,用来进行环绕增强。
源码如下:
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class SayHelloMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("方法执行前");
Object proceed = methodInvocation.proceed();
System.out.println("方法执行后");
return proceed;
}
}
然后,又新增了一个接口SayHello
public interface SayHello {
void sayHello();
}
该接口有两个实现类:
//SayHelloImpl1
public class SayHelloImpl1 implements SayHello {
@Override
public void sayHello(){
System.out.println("hello everyone1");
}
}
//SayHelloImpl2
public class SayHelloImpl2 implements SayHello {
@Override
public void sayHello(){
System.out.println("hello everyone2");
}
}
然后我们定义HotSwappableTargetSource对象。并且,将SayHelloImpl1作为初始target通过构造器传递到HotSwappableTargetSource对象中。
之后,定义ProxyFactoryBean,用来对SayHelloImpl1以及SayHelloImpl2进行环绕增强。
ProxyFactoryBean在Spring中是一个非常重要的概念,就是常说的FactoryBean。FactoryBean和BeanFactory的关系是一个很常见的面试题。读者有兴趣的话,自行查看一下Spring的源码。本文不再赘述。
经过以上配置后,我们新建两个定时任务类,来演示一下HotSwappableTargetSource的使用。
//定时任务类1
@Component
public class HotSwapTask implements ApplicationContextAware{
private ApplicationContext applicationContext;
//初始化延迟时间故意设置的时间久一点,以求可以更好的看到变化
@Scheduled(fixedDelayString = "5000",initialDelayString = "5000")
public void run() throws Exception {
SayHello proxyBean2 = applicationContext.getBean("targetImpl2", SayHello.class);
HotSwappableTargetSource swappableTargetSource = applicationContext.getBean("swappableTargetSource", HotSwappableTargetSource.class);
swappableTargetSource.swap(proxyBean2);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
//定时任务类2
@Component
public class CheckTask implements ApplicationContextAware{
private ApplicationContext applicationContext;
//每秒执行一次
@Scheduled(cron="0/1 * * * * ?")
public void checkTask(){
SayHello proxyBean = applicationContext.getBean("proxyFactory", SayHello.class);
proxyBean.sayHello();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
我们启动工程。观察一下控制台
方法执行前
hello everyone1
方法执行后
方法执行前
hello everyone1
方法执行后
方法执行前
hello everyone2
方法执行后
方法执行前
hello everyone2
方法执行后
可以看到,我们的swap动态更换target对象,效果实现了。target,开始是SayHelloImpl1的实现,然后通过HotSwappableTargetSource的swap方法,将target实现换成了SayHelloImpl2。这个类其实非常有用,大家可以通过源码再了解一下其他的TargetSource实现