Spring學習筆記 —— AOP標籤詳解()
引言
但是,除了面向切面程式設計之外,AOP的名字空間中還包含了一些重要的標籤,比如”scoped-proxy”。這篇文章就會詳細介紹這個標籤的作用,以及它的實現方式分析。
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方法了。