在前两篇文章,Spring学习笔记 —— AOP(面向切面编程) 之AspectJ, Spring学习笔记 —— AOP(面向切面编程) 之使用ProxyFactoryBean实现AOP,我们介绍了AOP的概念,在Spring下两种实现AOP的方式。
但是,除了面向切面编程之外,AOP的名字空间中还包含了一些重要的标签,比如”scoped-proxy”。这篇文章就会详细介绍这个标签的作用,以及它的实现方式分析。
在 Spring学习笔记 —— 从IOC说起,我们介绍过,Spring中的Bean是有Scope属性的,代表着bean的生存周期。而Spring中默认的Scope分为”singleton”和”prototype”两种。
那么,问题就来了,如果在一个singleton的Bean中引用了一个prototype的Bean,结果会怎样呢?——在默认情况下,单例会永远持有一开始构造所赋给它的值。
所以,为了让我们在每次调用这个Bean的时候都能够得到具体scope中的值,比如prototype,那么我们希望每次在单例中调用这个Bean的时候,得到的都是一个新的prototype,Spring中AOP名字空间中引入了这个标签。
xml
<aop:scoped-proxy/>
举个例子。
PrototypeBean.java
这个类在初始化的时候会得到当前时间的时间戳,它的scope为prototype(每次获取都会重新生成一个)。
public class PrototypeBean {
private Long timeMilis;
public PrototypeBean(){
timeMilis = (new Date()).getTime();
}
public void printTime() {
System.out.println(timeMilis+"");
}
}
SingletonBean.java
这个单例的Bean持有一个PrototypeBean,同时它的printTime()
方法输出PrototypeBean的时间戳。
public class SingletonBean {
private PrototypeBean prototype;
public void printTime() {
prototype.printTime();
}
public void setPrototype(PrototypeBean prototype) {
this.prototype = prototype;
}
}
scopedProxyBean.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="protyotypBean" class="com.study.scopedProxy.PrototypeBean" scope="prototype">
<aop:scoped-proxy/>
</bean>
<bean id="singletonBean" class="com.study.scopedProxy.SingletonBean">
<property name="prototype">
<ref bean="protyotypBean" />
</property>
</bean>
</beans>
ScopedProxyMain.java
public class ScopedProxyMain {
public static void main(String args[]) {
ApplicationContext app = new ClassPathXmlApplicationContext("scopedProxyBean.xml");
SingletonBean singleton = app.getBean(SingletonBean.class);
singleton.printTime();
//1477958215532
singleton.printTime();
//1477958215571
}
}
如果不加上<aop:scoped-proxy/>
这个标签,那么两次将会输出同样的时间戳。而我们加上标签之后,每次调用这个Bean的时候,系统就会先取一遍这个Bean,确保我们得到的Bean是在当前的scope当中的。
那么具体在代码层面,是如何完成这个实现的呢?
我们还是从AopNamespaceHandler
先着手。
AopNamespaceHandler.init
里面是对各个标签进行解析类注册,我们能看到,scoped-proxy对应的注册类就是ScopedProxyBeanDefinitionDecorator
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
而再到这个类里面,发现它实现了BeanDefinitionDecorator
接口,于是查看其decorate
方法。
BeanDefinitionHolder holder =
ScopedProxyUtils.createScopedProxy(definition, parserContext.getRegistry(), proxyTargetClass);
createScopedProxy
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
String targetBeanName = getTargetBeanName(originalBeanName);
// 创建一个ScopedProxyFactoryBean,这个Bean中保存了目标Bean的名称,
// 同时在内部保存了目标Bean定义的引用。注意并没有对BeanDefinition设置scope,
//因此这个代理bean的scope就默认是singleton了。
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(targetDefinition.getRole());
proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
// 默认情况下proxyTargetClass都是True
if (proxyTargetClass) {
targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
}
else {
proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
}
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
if (targetDefinition instanceof AbstractBeanDefinition) {
proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
}
targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);
// 因为在后面还是会用到目标Bean,因此也需要将它的定义注册到Registry中
registry.registerBeanDefinition(targetBeanName, targetDefinition);
//将新的Bean返回
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
对于BeanDefintion的解析,到这里就可以看作结束了。那么,这个ScopedProxyFactoryBean,又会在什么时候创建,怎么创建呢?
关键就是要看ScopedProxyFactoryBean.setBeanFactory
@Override
public void setBeanFactory(BeanFactory beanFactory) {
if (!(beanFactory instanceof ConfigurableBeanFactory)) {
throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
}
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
this.scopedTargetSource.setBeanFactory(beanFactory);
ProxyFactory pf = new ProxyFactory();
pf.copyFrom(this);
//这个这个代理Bean设置Bean的来源,很重要。这个方法决定了,
//每次取target的时候,都会调用beanFactory.getBean。
pf.setTargetSource(this.scopedTargetSource);
Class<?> beanType = beanFactory.getType(this.targetBeanName);
if (beanType == null) {
throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName +
"': Target type could not be determined at the time of proxy creation.");
}
if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
}
// 构建一个introduction,这个introduction只实现了ScopedObject的所有接口
ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
//将scopedObject作为通知加入到proxy中,DelegatingIntroductionInterceptor作为通知的拦截器,
//实际上是使得所有在proxyBean上调用的'getTargetObject`方法都被代理到了`DefaultScopedObject`中。
//在增加通知的同时会生成DefaultIntroductionAdvisor 作为advisor,
pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
pf.addInterface(AopInfrastructureBean.class);
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
而在后面,根据advisor生成相应的代理类,在之前已经解释过了,就不再赘述了。
而关于Introduction,这里有一个很好的例子。你也可以将cglib生成的class文件保存,就能够发现最后的Bean中的确存在着getTargetObject这个方法。
个人认为注册Advice的作用并不是很大,因为我们已经为这个ProxyBean设置好它的target source了,对于scoped-proxy的目的也就达到了。
最后来看一下真正被使用的地方把。
DynamicAdvisedInterceptor.intercept
,前文也说过了,这个是CglibAOP的拦截类。所有被拦截的方法都会首先调用这里的intercept。
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Class<?> targetClass = null;
Object target = null;
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
//最重要的就是这里,getTarget,
//这会到之前设置过的TargetSource中去获取TargetObject,
//也就是SimpleBeanTarget的getTarget方法,
//到这一步,scoped的proxy已经完成。
target = getTarget();
//省略后续调用
}
在这里我们介绍了AOP的一个重要标签,scoped-proxy。它是由ScopedProxyFactoryBean进行创建的,在创建的时候,为生成代理Bean的ProxyFactory指定了TargetSource,因此在每次拦截方法,进行调用之前,首先都会到指定的TargetSouce,也就是SimpleBeanTargetSource
中获取对应Scoped下的Bean。
此外,ScopedProxyFactory还为这个Bean增加了getTargetObject的方法(使用Introduction),因此所有带上了这个标签的Bean,也就默认实现了ScopedObject
的接口,可以调用getTargetObject
方法。这个方法的意义在于,因为代理Bean的scope是默认singleton的,这也就意味着,我们每次调用applicationContext.getBean
方法,总是返回同一个代理bean,如果我们想要获得scope下真正的bean的话,就可以调用getTargetObject方法了。