HotSwappableTargetSource的使用

这是在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实现

你可能感兴趣的:(Spring)