1. 程式人生 > >Spring IOC和Spring AOP的實現原理(原始碼主線流程)

Spring IOC和Spring AOP的實現原理(原始碼主線流程)

寫在前面

      正本文參考了《spring技術內幕》和spring 4.0.5原始碼。本文只描述原理流程的主線部分,其他比如驗證,快取什麼可以具體參考原始碼理解。

Spring IOC

一、容器初始化

      容器的初始化首先是在對應的構造器中進行,在applicationContext的實現類構造器中,首先對引數路徑中的${}進行了處理,用系統變數替換(setConfigLocations方法)然後呼叫refresh方法(這個就是最核心的容器初始化方法)。
ioc容器初始化流程圖

1、Resource定位:

      在refresh方法中呼叫obtainFreshBeanFactory方法告訴子類重新整理beanfactory(其中是呼叫refreshBeanFactory重新整理後getBeanFactory獲取重新整理後的factory返回)。在重新整理過程refreshBeanFactory中如果factory已經有了要消除再新建factory,其中loadBeanDefinitions是載入bean定義的方法。


      在loadBeanDefinitions方法中建立了BeanDefinitionReader的實現類呼叫其loadBeanDefinitions方法(這個方法是過載方法,引數有為Resource的也有為String路徑的,getConfigResources方法(預設返回null,子類重寫,如ClassPathXmlApplicationContext類)和getConfigLocations方法獲得Resource集合和資源路徑集合(一般一個為空,一般是將容器的引數path設定為configLocations,ClassPathXmlApplicationContext有一種構造器是不設定configLocations而是直接用引數path生成ClassPathResource集合設定為configResources)分別進行load,實際上以路徑為引數的過載方法在定位完Resource也會呼叫以resource為引數的loadBeanDefinitions來解析載入BeanDefinition,這個是第二步在下面介紹)。


      在BeanDefinitionReader的loadBeanDefinitions(path引數)方法中根據ResourceLoader型別以兩種方式載入(如果是ant正則表示式方式的(如PathMatchingResourcePatternResolver)一個路徑定位多個resource或者預設方式(applicationContext繼承的是DefaultResourceLoader實現方式)定位一個resource),分別呼叫ResourceLoader的getResource(以/開頭的構建ClassPathContextResource,以classpath開頭的去掉classpath構建ClassPathResource,如果都不是的嘗試構建UrlResource,如果構建失敗就呼叫getResourceByPath這個具體applicationContext實現類裡重寫的方法構建特定Resource,如FileSystemXmlApplicationContext就是FileSystemResource)或
getResources(PathMatchingResourcePatternResolver的正則方式這裡不詳細描述)完成Resource定位。

2、從Resource中解析和載入BeanDefinition:

      同樣在BeanDefinitionReader的loadBeanDefinitions中呼叫完resourceLoader的getResource獲取Resource後將resource作為引數呼叫自己(BeanDefinitionReader)的loadBeanDefinitions(是一個介面方法給子類實現,因為不同的reader載入resource的方式不同)載入BeanDefinition。

      例如XmlBeanDefinitionReader是對XML檔案的IO操作,(將現在要處理的Resource加入當前執行緒正在處理(ThreadLocal)的Resource集合中)首先從resource中拿出InputStream封裝成InputSource呼叫自身的doLoadBeanDefinitions方法。

      doLoadBeanDefinitions方法中呼叫doLoadDocument方法封裝成Document-----是用validationMode(預設是自動校驗方式,意思是如果沒有顯示定義校驗的方式就用XSD方式)和DocumentLoader(XmlBeanDefinitionReader中預設的是DefaultDocumentLoader)等引數呼叫DocumentLoader的loadDocument方法將Resource封裝成Document類(具體封裝方式不做介紹,有興趣的可以自己瞭解一下,用的是builder模式做的)呼叫registerBeanDefinitions方法解析載入bean。

      registerBeanDefinitions方法是用BeanDefinitionDocumentReader的registerBeanDefinitions具體解析Document(樹形結構,從root(就是beans標籤)開始往下解析)中每個element各個標籤的解析和載入。其中如果是bean標籤BeabDefinitionParserDelegate的parseBeanDefinitionElement方法對XML元素的資訊按照spring的bean的規則進行解析(property的解析,當中value和ref解析方式不同,如果是value構建TypedStringValue, 如果ref的話構建RuntimeBeanReference,這個在之後依賴注入的時候用到,還有id,name,等屬性的解析)得到的BeanDefinition的封裝BeanDefinitionHolder(包括BeanDefinition,beanName(這裡是識別符號的意思,如果有id,id做識別符號,沒有id,name屬性中第一個別名做識別符號)和別名列表(name屬性中的內容,如果沒有id,name中第一個不作為別名而是識別符號))來進行下一步bean的註冊(BeanDefinitionReaderUtils.registerBeanDefinition)。

      其他如import,alias等標籤自行看原始碼理解。

3、BeanDefinition在IOC容器的註冊

      BeanDefinitionReaderUtils.registerBeanDefinition用BeanDefinitionRegistry(DefaultListableBeanFactory)的registerBeanDefinition方法註冊beanName和BeanDefinition(就是把beanName加入到一個已經註冊的bean的beanName的Set中,然後put到beanName對應BeanDefinition的map中,其中如果不允許覆蓋並且有同名beanName要報錯)。再用BeanDefinitionRegistry的registerAlias方法註冊beanName和別名列表(put到一個beanName對應alias的map中,其中如果有alias跟beanName相同的要移除)。

二、IOC容器依賴注入
依賴注入

1、getBean第一次呼叫lazy-init的bean

      是以BeanFactory的getBean方法為入口觸發的(實現在AbstractBeanFactory實現類中)。如果是單例會快取起來只加載一次,如果是FactoryBean這種特殊的bean會把這個bean的例項傳入getObjectForBeanInstance方法獲得FactoryBean產生的bean(呼叫FactoryBean的getObject方法,這就是自定義的FactoryBean要重寫的方法,AOP也是這個原理,詳情見下方AOP分析)。在第一次載入時要先判斷這個BeanDefinition在當前BeanFactory有沒有,沒有就從雙親BeanFactory中找,一直遞迴。

      找到後要驗證是否存在遞迴依賴,有則報錯無則設定當前bean依賴bean的依賴關係到兩個map中(一個是被依賴map,一個是依賴map),其中:
      (1)如果是單例第一次載入就呼叫getSingleton方法(方法中回調了引數中ObjectFactory的getObject方法,這裡重寫了這個方法呼叫createBean)獲得例項用getObjectForBeanInstance獲得FactoryBean產生的bean(如果它是FactoryBean的話)。
      (2)如果是prototype載入呼叫createBean後呼叫getObjectForBeanInstance。
      (3)如果是其他scope型別:request、session和global session,這三種就用scope.get獲取例項(和getSingleton類似回撥重寫的getObject也就是呼叫createBean)後呼叫getObjectForBeanInstance。

      最後如果getBean指定了requiredType要檢驗獲取的bean能不能轉化成指定的型別不能的話就報錯。

      createBean方法就是生成bean的方法並對一些比如init-method屬性、後置處理器等一些初始化進行了處理。方法中在例項化之前判斷是否有post-processor,如果有這樣的processor則短路指定bean的建立,直接返回一個proxy而不是指定的bean(這種processor可以指定生成一個其他型別的物件)沒有的話用doCreateBean建立bean返回。

      doCreateBean是用createBeanInstance生成BeanWrapper(包裝bean)之後用populateBean向其中的bean完成依賴bean的注入(autowire等)

      createBeanInstance建立beanWrapper時分三類進行處理:
      (1)如果有工廠方法,呼叫instantiateUsingFactoryMethod建立。
      (2)如果是構造器注入的方式呼叫autowireConstructor。
      (3)簡單方式呼叫instantiateBean。呼叫的是策略類(預設SimpleInstantiationStrategy)的instantiate而其中又是通過bean方法是否有跟IOC容器同名的(會被覆蓋)來分兩類處理(沒同名方法的從BeanDefinition中拿出class直接用jdk的反射拿構造器來newinstance一個例項,如果有同名的則是用CGLIB的方式來new一個例項)。

      populateBean為生成的bean依賴注入,先對非簡單型別屬性有autowire的進行處理,判斷這個屬性在之前解析載入beanDefinition時property裡有沒有,有的話進行getBean初始化後放入PropertyValue集合中(這個就是propertyname和value的封裝),然後更新依賴map,再對非autowire的或一般屬性進行注入,有要轉化的要經過valueResolver的轉化(如果是RuntimeBeanReference之前載入時XML中配置是ref的就getBean(如果在雙親BeanFactory中就從雙親中取)獲得後也放到PropertyValue集合中,也要更新依賴map)。最後再注入到bean中,這裡說的注入其實真實發生在最後的BeanWraper的setPropertyValue(propertyValue集合)方法,具體實現就是通過反射的方式獲得setter方法賦值。

2、lazy-init==false初始化(只對singleton,也是預設方式)

      在refresh方法中的finishBeanFactoryInitialization方法中進行初始化(實際也是呼叫getBean方法)。

Spring AOP

      ProxyFacotryBean是FacotryBean的一種實現,FacotryBean要產生bean都要重寫getObject方法,而ProxyFacotryBean這裡的這個getObject正是為代理做了準備並返回代理物件。首先用initializeAdvisorChain(第一次去取代理物件時初始化一遍)初始化Advisor鏈後對於singleton和prototype進行區分生成對應的proxy。
aop

1、初始化Advisor鏈

      initializeAdvisorChain初始化Advisor鏈是遍歷ProxyFacotryBean中配置的interceptorNames,如果結尾有萬用字元只能是ListableBeanFacotory來載入否則報錯,去掉結尾萬用字元*後呼叫addGlobalAdvosor(這個是獲取ListableBeanFacotory的所有globalAdvisorNames和globalInterceptorNames,分別遍歷用getBean(beanName)獲取advice,把其中符合萬用字元格式的advice呼叫addAdvisorOnChainCreation封裝成advicsor後新增到Advisor鏈,如果結尾沒有萬用字元的情況下無論是singleton還是prototype在獲得advice後都要用addAdvisorOnChainCreation方法註冊到advisor鏈上。

    addAdvisorOnChainCreation用namedBeanToAdvisor方法把advice包裝成advisor,判斷如果advice是單例singleton的話是用AdvisorAdapterRegistry(預設DefaultAdvisorAdapterRegistry單例)wrap方法判斷如果這個advice是MethodInterceptor或者AdvisorAdapterRegistry三種固定的adapter(before,afterreturning,throws)如果任一adapter支援的話(支援不支援就是在具體的adapter中判斷advice是不是這個adapter對應具體的advice類的子類)就封裝成DefaultPointcutAdvisor返回。如果是prototype的話不獲取getBean,而是直接用name包裝成PrototypePlaceholderAdvisor。

2、生成代理類

      以singleton為例,singleton代理的生成getSingletonInstance方法。是用AopProxyFactory(在構造器中設定了預設的DefaultAopProxyFactory)的createAopProxy方法根據ProxyFacotryBean中配置的target判斷是否是個介面(實際上不是這麼簡單的區分,具體看原始碼瞭解)來建立不同AopProxy的子類(JdkDynamicAopProxy或者ObjenesisCglibAopProxy(CglibAopProxy的子類,增加了ObjenesisStd))呼叫他們各自的getProxy方法以不同的方式建立代理物件返回。

      JdkDynamicAopProxy就是以動態代理的方式構建代理物件返回(具體動態代理原理自行了解哦)。

      CglibAopProxy就是以Cglib的方式進行代理,Cglib採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。具體細節超出這文章的範圍拉。

      prototype代理的方式大致相同有些許的差別也不做介紹,可以參考原始碼。

3、呼叫時攔截

      在呼叫目標類的方法時因為代理呼叫的是invoke(jdk動態代理)或者intercept(cglib)。在invoke(jdk動態代理)或者intercept(cglib)中根據目標類被呼叫方法分別處理。

      如果是hashCode和equals方法直接呼叫代理類中重寫了的hashCode和equals方法(具體參考原始碼)。
      如果是Adviced介面中定義的方法(ProxyFactoryBean就是Adviced介面實現類)直接以反射的方式拿到method呼叫方法(AopUtils的invokeJoinpointUsingReflection方法)。
      其他情況就是拿到攔截器鏈(只初始化一次,每次呼叫時有個currentInterceptorIndex記錄處理到第幾個攔截器)呼叫攔截器的proceed方法前進呼叫

      proceed前進呼叫不是遞迴,其中用matcher進行匹配,如果匹配上呼叫攔截器的invoke方法,匹配不上就直接繼續前進呼叫,攔截器interceptor的invoke方法就是通知方法(自己實現的如afterReturning等)對目標方法(實際是攔截器鏈的proceed前進呼叫)的具體加強,就是順序問題等等。

      直到攔截器鏈前進到底呼叫target目標類的對應方法(jdk反射獲取method呼叫)。

初始化攔截器鏈是通過遍歷之前IOC容器getBean獲取到advisor鏈中的Advisor,通過AdvisorAdapterRegistry當中設定的3種adapter(before,afterreturning,throws)的supportsAdvice判斷是否支援該advisor,如果支援就將advisor中的advice註冊成不同的AdviceInterceptor列表(一個advisor可以被多個adapter支援,因為只要自己寫的通知類實現多種advice介面即可)都加入到攔截器鏈。