曹工說Spring Boot原始碼(18)-- Spring AOP原始碼分析三部曲,終於快講完了 (aop:config完整解析【下】)
寫在前面的話
相關背景及資源:
曹工說Spring Boot原始碼(1)-- Bean Definition到底是什麼,附spring思維導圖分享
曹工說Spring Boot原始碼(2)-- Bean Definition到底是什麼,咱們對著介面,逐個方法講解
曹工說Spring Boot原始碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,我們來試一下
曹工說Spring Boot原始碼(4)-- 我是怎麼自定義ApplicationContext,從json檔案讀取bean definition的?
曹工說Spring Boot原始碼(5)-- 怎麼從properties檔案讀取bean
曹工說Spring Boot原始碼(6)-- Spring怎麼從xml檔案裡解析bean的
曹工說Spring Boot原始碼(7)-- Spring解析xml檔案,到底從中得到了什麼(上)
曹工說Spring Boot原始碼(8)-- Spring解析xml檔案,到底從中得到了什麼(util名稱空間)
曹工說Spring Boot原始碼(9)-- Spring解析xml檔案,到底從中得到了什麼(context名稱空間上)
曹工說Spring Boot原始碼(10)-- Spring解析xml檔案,到底從中得到了什麼(context:annotation-config 解析)
曹工說Spring Boot原始碼(11)-- context:component-scan,你真的會用嗎(這次來說說它的奇技淫巧)
曹工說Spring Boot原始碼(12)-- Spring解析xml檔案,到底從中得到了什麼(context:component-scan完整解析)
曹工說Spring Boot原始碼(13)-- AspectJ的執行時織入(Load-Time-Weaving),基本內容是講清楚了(附原始碼)
曹工說Spring Boot原始碼(14)-- AspectJ的Load-Time-Weaving的兩種實現方式細細講解,以及怎麼和Spring Instrumentation整合
曹工說Spring Boot原始碼(15)-- Spring從xml檔案裡到底得到了什麼(context:load-time-weaver 完整解析)
曹工說Spring Boot原始碼(16)-- Spring從xml檔案裡到底得到了什麼(aop:config完整解析【上】)
曹工說Spring Boot原始碼(17)-- Spring從xml檔案裡到底得到了什麼(aop:config完整解析【中】)
工程程式碼地址 思維導圖地址
工程結構圖:
概要
本篇是接著前兩篇講的,為了避免不必要的重複,請大家先看下。
曹工說Spring Boot原始碼(16)-- Spring從xml檔案裡到底得到了什麼(aop:config完整解析【上】)
曹工說Spring Boot原始碼(17)-- Spring從xml檔案裡到底得到了什麼(aop:config完整解析【中】)
本篇主要講解spring如何在bean生成過程中,完成“狸貓換太子”操作的。(利用代理物件,替換ioc容器中的原有bean)。
回顧前文
spring解析的xml如下:
<!--目標物件-->
<bean id="performer" class="foo.Performer"/>
<!--切面-->
<bean id="performAspect" class="foo.PerformAspect"/>
<!--配置切入點-->
<aop:config>
<aop:pointcut id="mypointcut" expression="execution(public * foo.Perform.sing(..))"/>
<aop:aspect id="myAspect" ref="performAspect">
<aop:after method="afterPerform" pointcut-ref="mypointcut"/>
</aop:aspect>
</aop:config>
經前文的理解,總共生成了如下幾個bean definition。
bean definition 中bean class | 備註 |
---|---|
PerformAspect | 通知 |
Performer | 要切的目標 |
AspectJExpressionPointcut | 切點,即那一行 |
org.springframework.aop.aspectj.AspectJPointcutAdvisor | advisor,解析 這一行得到 |
AspectJAwareAdvisorAutoProxyCreator | 實現了BeanPostProcessor介面,解析<aop:config> 得到 |
spring大致啟動流程
解析xml、註解,通過各種渠道,得到bean definition;
從bean definition集合中,找出bean class實現了BeanFactoryPostProcessor介面的子集,然後通過getBean來獲取這部分特殊的bean,然後依次呼叫其postProcessBeanFactory方法
來對其餘的bean definition進行處理;
public interface BeanFactoryPostProcessor { /** * Modify the application context's internal bean factory after its standard * initialization. All bean definitions will have been loaded, but no beans * will have been instantiated yet. This allows for overriding or adding * properties even to eager-initializing beans. * @param beanFactory the bean factory used by the application context * @throws org.springframework.beans.BeansException in case of errors */ void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory); }
從bean definition集合中,找出bean class實現了BeanPostProcessor介面的子集,然後通過getBean來獲取這部分特殊的bean,然後儲存起來。
找出不具有特殊功能的(沒實現BeanFactoryPostProcessor,也沒實現BeanPostProcessor)bean definition 集合,再過濾出單例bean,且lazy-init屬性為false(說明要在spring容器初始化過程中,例項化的bean)的這部分,作為子集;然後依次呼叫其getBean方法。
以上就是spring比較粗的流程,當然,很多細節沒說,不過核心流程是這樣的。
spring aop如何生效
1. 例項化AspectJAwareAdvisorAutoProxyCreator
在前面的spring流程中,第三步,就會去查詢實現了BeanPostProcessor的bean definition集合,其中,就會包含AspectJAwareAdvisorAutoProxyCreator 這個bean definition;然後通過getBean方法,將這個bean definition例項化為bean。(這個過程,類似於通過class來建立物件)
這個步驟的具體實現入口在:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
/**
* 配置beanFactory
*/
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// 入口在這裡!這裡面會去 getBean
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
/**
* 這裡對單例bean、且lazy-init=false進行例項化
*/
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
}
}
具體跟進去後,在如下位置,809行,就通過getBean,獲取到了AspectJAwareAdvisorAutoProxyCreator這個bean了:
2.AspectJAwareAdvisorAutoProxyCreator對target bean進行後置處理,生成代理
在“spring大致啟動流程”中的第四步,去預先例項化bean時,會進入以下邏輯:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
...
try {
...
// Check for listener beans and register them.
registerListeners();
/**
* 入口在這裡。這裡對單例bean、且lazy-init=false進行例項化
*/
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
}
}
然後會進入以下邏輯:
org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
public void preInstantiateSingletons() throws BeansException {
if (this.logger.isInfoEnabled()) {
this.logger.info("Pre-instantiating singletons in " + this);
}
// 其中 this.beanDefinitionNames 儲存了所有的bean definition名稱
for (String beanName : this.beanDefinitionNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 例項化那些:非抽象、單例、非lazy-init的bean definition
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
...
if (isEagerInit) {
getBean(beanName);
}
}
else {
getBean(beanName);
}
}
}
}
所以,這裡就是對那些:非抽象、單例、非lazy-init的bean definition 進行例項化。
我們這裡,大家看下當前容器中包含的全部的bean definition:
我這邊的demo,第一個要例項化的bean,就是performer。在getBean()處理performer這個bean的過程中,會經歷以下的流程:
其中,AspectJAwareAdvisorAutoProxyCreator 作為beanPostProcessor,在例項化performer這個bean時,有兩個時間點參與進來。
哪兩個時間點呢,大家再看看,AspectJAwareAdvisorAutoProxyCreator 的類圖:
其實它不止實現了BeanPostProcessor
介面,它還實現了InstantiationAwareBeanPostProcessor
介面。這個多出來的介面,幹嘛的?大家看看它的方法就知道了:
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
// 注意,before Instantiation的意思是,例項化之前呼叫
Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;
// 這個,例項化之後呼叫
boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;
...無關方法
}
所以,InstantiationAwareBeanPostProcessor擴充套件了兩個時間點出來,一個是例項化前,一個是例項化後。
什麼叫例項化?把一個物件new出來,通俗來講,就叫做例項化。
在spring的getBean流程裡,例項化肯定是早於初始化的。所以,一個bean,會經歷如下順序(大家直接看前面點的圖,更清晰):
InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation;
根據bean definition來例項化,假設bean class為A,bean definition中的constructorArgumentValues引數,如果為空,則使用A的預設建構函式;如果constructorArgumentValues有值,表示:在此之前,需要先獲取到相應的建構函式值,才能去反射通過構造器來建立物件
這裡面,會涉及到bean的遞迴呼叫。
比如,以前一篇和開頭提到的,AspectJPointcutAdvisor 這個bean definition來說,其中它的建構函式,就要求一個AbstractAspectJAdvice 物件:
public AspectJPointcutAdvisor(AbstractAspectJAdvice advice) { Assert.notNull(advice, "Advice must not be null"); this.advice = advice; this.pointcut = advice.buildSafePointcut(); }
那AbstractAspectJAdvice 這個物件要怎麼生成,這個在我們的場景下,是被抽象成一個內部bean的,bean class為AspectJAfterAdvice。AspectJAfterAdvice這個類呢,建構函式又是下面這樣的:
public AspectJAfterAdvice( Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) { super(aspectJBeforeAdviceMethod, pointcut, aif); }
又依賴了一堆其他的引數,這三個引數,其中2個也是被定義為了
內部bean definition
,一個為bean 引用。(具體請參照前一篇)。所以,這個bean的例項化過程就相對繁瑣,涉及到bean的遞迴生成。
InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation;
屬性注入,此時是autowired等發揮作用的地方;
BeanPostProcessor的postProcessBeforeInitialization
BeanPostProcessor#postProcessAfterInitialization
這裡面6個步驟,AspectJAwareAdvisorAutoProxyCreator 在其中兩個地方,實現了自己的業務邏輯。
2.1 InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation
具體實現在:
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
Object cacheKey = getCacheKey(beanClass, beanName);
if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
// 入口在這:shouldSkip
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
// Create proxy here if we have a custom TargetSource.
// Suppresses unnecessary default instantiation of the target bean:
// The TargetSource will handle target instances in a custom fashion.
if (beanName != null) {
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
this.targetSourcedBeans.add(beanName);
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
}
return null;
}
以上邏輯中,我們需要進入到shouldSkip方法,重點是下面的findCandidateAdvisors方法:
@Override
protected boolean shouldSkip(Class beanClass, String beanName) {
// TODO: Consider optimization by caching the list of the aspect names
/**
* 這裡呼叫了父類中實現的findCandidateAdvisors,獲取候選的advisor bean;這裡也是真正根據bean
* definition去生成advisor bean的地方
*/
List<Advisor> candidateAdvisors = **findCandidateAdvisors()**;
for (Advisor advisor : candidateAdvisors) {
/**
* 如果當前要檢查的bean,就是advisor裡的通知類,則跳過
*/
if (advisor instanceof AspectJPointcutAdvisor) {
if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) {
return true;
}
}
}
return super.shouldSkip(beanClass, beanName);
}
下面會找出ioc容器中,實現了Advisor介面的bean definition,並全部例項化。Advisor是什麼?
我們前面提到的AspectJPointcutAdvisor,就實現了這個介面。
public List<Advisor> findAdvisorBeans() {
// Determine list of advisor bean names, if not cached already.
String[] advisorNames = null;
synchronized (this) {
advisorNames = this.cachedAdvisorBeanNames;
if (advisorNames == null) {
// 1.從spring容器查詢Advisor型別的bean definition
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Advisor.class, true, false);
this.cachedAdvisorBeanNames = advisorNames;
}
}
if (advisorNames.length == 0) {
return new LinkedList<Advisor>();
}
List<Advisor> advisors = new LinkedList<Advisor>();
for (String name : advisorNames) {
if (isEligibleBean(name) && !this.beanFactory.isCurrentlyInCreation(name)) {
// 遍歷那些bean definition,通過getBean,來獲取bean
advisors.add(this.beanFactory.getBean(name, Advisor.class));
}
}
return advisors;
}
再囉嗦一句,advisor差不多是aop的核心資料結構,你通過Aspect註解方式,最終也是解析為一個個的Advisor。
一個切點+一個切面方法 基本就等於一個Advisor物件。比如,一個before方法,算一個;在一個after,又算一個。
2.2 BeanPostProcessor的postProcessAfterInitialization
這是第二個時間點,在這裡,檢查bean要不要被攔截,生成代理。大家可以簡單理解,因為每個bean的建立過程,都要被它處理,它呢,就會檢查,這個bean,是不是匹配切點,如果匹配,就生成代理。
舉個例子,以前看過一個小說,是京官去很偏遠的地方上任,路上被人殺了,並被另外一個人拿了印章,到了地方,靠著印章,招搖撞騙,當了地方官。假設切點是:每個過路人;切面:偷天換日。那這個PostProcessor,就是那夥人,攔截每個過路人,並判斷是不是那個倒黴的京官,如果是,就殺了並且換個人拿了印章去當地方官。
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
// 返回生成的代理物件
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
這裡的下面這句,bean是原始bean,wrapIfNecessary返回的,就作為最終的bean。如果你不需要切這個bean,那你就返回它本身;如果要代理,你就返回另一個代理物件即可。
return wrapIfNecessary(bean, beanName, cacheKey);
我們仔細看這個方法內部:
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// 獲取前面2.1章節裡的Advisor物件,並儲存到Object[] specificInterceptors 裡
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != null) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 建立代理,注意,Advisor陣列,已經被傳進去了。
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
大家看上面的註釋吧,這裡獲取了ioc容器裡的Advisor物件,如果advisor陣列不為null,則建立代理,並返回代理。大家看下面的debug圖就理解了。
建立代理的過程就簡單了,基本就是:有介面就搞個jdk 動態代理,否則就cglib代理。
我做了個小測試,
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (bean.getClass().getName().equals("foo.Performer")) {
return "hahha";
}
...其他程式碼省略,主要加了上面那個if
return bean;
}
public final class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
"context-namespace-test-aop.xml");
Perform performer = (Perform) ctx.getBean(Perform.class);
performer.sing();
}
}
然後在倒數第二行,獲取Perform型別的bean時,報錯了。為啥呢?因為本來foo.Performer實現了Perform介面,但現在,我用一個string作為代理返回去了,所以就沒有實現Perform介面的bean存在了
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [foo.Perform] is defined
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:296)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1196)
at foo.Main.main(Main.java:23)
總結
spring aop這個東西,還是不簡單,我本來打算這一講把全部內容說清楚;但現在發現,建立代理這部分,還是得放到下一篇。
至於原始碼那些,在第16篇裡給了地址的,大家可以看看。有啥問題,及時聯絡我