前言
說是Java web,Spring已經成為了事實標準,Spring原理的深入學習,無論是在工作中,還是在面試中,都尤為重要。
Spring的兩個核心概念是IOC(控制反轉)和AOP(面向切面編程)。想了解Spring的工作原理,毫無疑問,首先要從這兩個概念的Spring實現入手。但是Spring源碼浩如煙海,里面摻雜了太多的實現細節,入門可謂極其困難。當我正苦于難以入門時,好友介紹了tiny-spring這個開源項目,這個項目用了不到千行的代碼,就將Spring的IOC、AOP的核心流程實現完畢,真是居家旅行、吹逼面試之必備呀!
廢話少說,我們開始吧!
目錄結構
在github上clone下項目來之后,我們關注src文件夾,其余的是一些愛好者提的注釋PR,恰巧被作者merge了,不必理會。目錄結構是這樣的:
-
aop
包,顧名思義,實現了Spring的AOP功能,可以通過bean的自動AOP切入,文件稍多,暫時先不展開。 -
bean.factory
包,通過BeanFactory
、AbstractBeanFactory
、AutowireCapableBeanFactory
三個類,實現了BeanFactory
的核心功能,詳情稍后講解。 -
bean.io
包定義了資源加載相關的抽象概念,這里的資源包括xml配置文件等。 -
bean.xml
包中只包含一個類:XmlBeanDefinitionReader
,主要負責在xml配置文件中讀取bean定義。 -
bean
包其他類,定義了BeanDefinition等核心概念,詳情后講。 -
context
包定義了ApplicationContext
的核心概念。 -
BeanReference
指的是引用類型的Bean,而不是實體類。
IOC--浮沙筑臺之根基
IOC(控制翻轉)是一種編程范式,可以在一定程度上解決復雜系統對象耦合度太高的問題,并不是Spring的專利。IOC最常見的方式是DI(依賴注入),可以通過一個容器,將Bean維護起來,方便在其他地方直接使用,而不是重新new。可以說,IOC是Spring最基本的概念,沒有IOC就沒有Spring。
為什么DI可以起到解耦的作用?
一個軟件系統包含了大量的對象,每個對象之間都有依賴關系,在普通的軟件編寫過程中,對象的定義分布在各個文件之中,對象之間的依賴,只能通過類的構造器傳參,方法傳參的形式來完成。當工程變大之后,復雜的邏輯會讓對象之間的依賴梳理變得異常困難。
在Spring IOC中,一般情況,我們可以在XML文件之中,統一的編寫bean的定義,bean與bean之間的依賴關系,從而增加邏輯的清晰度。而且,bean的創建是由Spring來完成的,不需要編程人員關心,編程人員只需要將精力放到業務的邏輯上面,減輕了思維的負擔。
在tiny-spring
里面,整個beans
和context
包都是用來實現IOC
的。
beans
包實現的核心關注點是BeanFactory
,BeanFactory
也叫作Bean容器,顧名思義,是用來盛放、管理bean的。
context
包實現的核心關注是ApplicationContext
,ApplicationContext
也是用來獲取Bean的,但是它更高層,它的面向用戶是Spring的使用者,而BeanFactory面向的用戶更多是Spring開發者。BeanFactory定義了Bean初始化的流程,ApplicationContext定義了從XML讀取,到Bean初始化,再到使用的過程。
Bean在哪定義?
剛才有說到,Spring通常通過xml文件,來統一的描述bean,bean與bean的依賴關系。所以說,bean的定義表述,發生在xml配置文件之中。這個XML文件就是我們需要讀取的資源文件。
因此,首要任務就是研究與讀取XML資源文件相關的類。
bean.io
中存放的是讀取資源文件的抽象概念。其中包含了三個類或者接口:
-
Resource
接口,這個接口只有一個方法,InputStream getInputStream() throws IOException;
。實現這個接口的類就是一個抽象的資源,可以獲取這個資源的輸入流,從而獲取其中的內容。 -
UrlResource
類,這個類實現了Resource
接口,通過構造器傳入一個url地址,代表的是這個url所對應的文件。 -
ResourceLoader
類,只有一個方法,public Resource getResource(String location)
。輸入url的文件地址(并不是真正的URL格式地址),來獲取Resource。
通過分析上面三個類、接口,我們知道,這個包完成了一個任務:通過ResourceLoader
這個類,獲取某一個地址的Resource
,從而獲取這個文件的輸入流。因為使用了Resource概念,可以使用網絡文件或者本地文件。
Bean如何定義?
-
BeanDefinition
是Bean定義的核心概念,BeanDefinition
包含了:bean的對象、bean的類類型、bean的名字,bean的所有屬性。這個類對bean的基本信息做好一個包裝。 -
BeanDefinitionReader
接口,只有一個方法:void loadBeanDefinitions(String location) throws Exception;
,實現這個接口的類,具有將某一個文件中的所有bean定義載入的功能。所以BeanDefinitionReader
定義了,在哪載入bean定義,至于載入到哪里、如何載入,稍后看具體實現。 -
AbstractBeanDefinitionReader
抽象類,上面剛說了實現了BeanDefinitionReader
接口的類,具有將某一個文件中描述的bean定義載入的功能,AbstractBeanDefinitionReader
就實現了這樣一個抽象功能。它的作用就是定義,載入到哪和如何載入的問題。在這個類里面,有兩個屬性:Map<String,BeanDefinition> registry;
和ResourceLoader resourceLoader;
。registry
是一個注冊表,他保存的就是所有的Bean定義,Map結構,key是bean的名字,value就是BeanDefinition。resourceLoader
描述了如何載入。 -
XmlBeanDefinitionReader
這是beans.xml
包里面的唯一一個方法,也是最重要的方法之一。它繼承了AbstractBeanDefinitionReader
,實現了所有方法,解決了bean定義中:在哪載入、如何載入、載入到哪的三個大問題。這個類面向用戶的方法有兩個,一個是loadBeanDefinitions
,毫無疑問,這個是必須的。另一個是getRegistry
用來獲取bean注冊表,得到所有bean的信息,registry是bean們在內存中實際的家。但是這個getRegistry
方法并不是面向用戶的,而是面向ApplicationContext的。 -
PropertyValue
和PropertyValue
代表一種抽象概念,在xml中,bean的屬性包括屬性名和屬性對象,PropertyValue
就是這么一個實體。 -
BeanReference
代表的是Bean的屬性不是真實對象,而是另一個bean的引用。
Bean的組裝全過程
上面兩部分是鋪墊,而BeanFactory才是重點對象。beans.factory
包中有三個類用來定義BeanFactory相關的概念。
-
BeanFactory
接口,只有一個方法:Object getBean(String name) throws Exception;
,實現這個接口的類,就具有了得到一個bean的能力。 -
AbstractBeanFactory
類,較為復雜。詳情后講。 -
AutowireCapableBeanFactory
繼承了AbstractBeanFactory
,實現了applyPropertyValues方法,通過反射,將bean的所有屬性,通過set方法注入進去。
AbstractBeanFactory
有三大屬性:
-
beanDefinitionMap
,類似于registry,但是他是BeanFactory里面私有的,代表的是這個BeanFactory里面暫時有哪些bean定義。 -
beanDefinitionNames
代表里面,這個BeanFactory里面有哪些bean(名字)。 -
beanPostProcessors
,代理處理器,AOP會用到,詳情后講。
AbstractBeanFactory
實現了幾大功能:
-
getBean
,這是主要功能,可以獲取一個Bean對象。 -
registerBeanDefinition
,面向ApplicationContext,用來將XML配置文件里面的bean定義注冊到BeanFactory里面。 -
preInstantiateSingletons
,面向ApplicationContext,用來在開始的時候,將所有的bean都初始化完畢,避免懶加載。 -
addBeanPostProcessor
添加代理處理器。 -
getBeansForType
,在BeanFactory里面,獲取某一個類型的所有bean。
經過上面的分析,我們可以知道BeanFactory完成了Bean初始化的整個流程。BeanFactory的工作流程如下:
- getBean, 在beanDefinitionMap里面得到bean,如果沒有的話,先初始化。(為什么會沒有,因為ApplicationContext讀取xml文件時候,只是給BeanDefinition服了類類型,并沒有賦值對象,這個對象還是需要BeanFactory通過反射生成的)。
- createBeanInstance,通過反射,根據BeanDefinition的類對象新建實體對象->將得到的bean對象賦值給beandefinition,然后將BeanDefinition里面的屬性都注入到Bean里面,這就完成了doCreateBean。
- initializeBean就是調用BeanPostProcessor的postProcessBeforeInitilizztion方法和postProcessAfterIntilizatin方法,獲取新的bean,這里會在aop中用到。
好了,到這BeanFactory就講完了,下面是更重要的ApplicationContext。
ApplicationContext-用戶與BeanFactory之間的橋梁
beans.context
包有三個類、接口,完成了ApplicationContext的基本功能。
- ApplicationContext接口,沒有任何方法,只是繼承了BeanFactory接口,暗示ApplicationContext與BeanFactory都是獲取Bean的地方。
-
AbstractApplicationContext
抽象類,首先,它的構造函數接收入參BeanFactory,所以說ApplicationContext內部具有一個BeanFactory。類似于一種裝飾器模式,但不是裝飾器模式,類似于代理模式,但也不是代理模式。fresh方法分為三個步驟:1.loadBeanDefinitions,這個是一個模板方法,需要子類實現,它的作用就是從某一個地方讀取BeanDefinition,然后寫入到ApplicationContext自己的BeanFactory里面,這就是ApplicationContext與BeanFactory之間的聯系,也就是ApplicationContext還負責了讀取定義。2. registerBeanPostProcessors,這個就是在BeanFactory里面找到BeanPostProcessor,然后將他們放到BeanFactory的beanPostProcessors容器里面,方便BeanFactory初始化使用。3. onRefresh初始化一遍所有的bean。 -
ClassPathXmlApplicationContext
實現了loadBeanDefinitions的方法,將xml文件和BeanFactory結合在一起。
總結-ApplicationContext初始化流程
總結-ApplicationContext獲取bean流程
AOP--移花接木之魔法
上一節,講完了Spring IOC的整個流程,也就是bean從定義獲取,到得到bean之間的整個流程。本節,我們接觸一下Spring另一個重要概念,AOP。AOP用途十分廣泛,其中Spring內部的聲明式事務和攔截器都是利用了AOP的強大威力,才得以優雅的實現。
AOP是什么呢,簡單來說,它可以讓編程人員在不修改對象代碼的情況下,為這個對象添加額外的功能或者限制。
很熟悉吧,這就是代理模式!
Java中存在兩種代理模式:
一種叫靜態代理,就是通過接口繼承復用的方式來完成的, 代理類與被代理對象實現相同的接口,然后代理類里面會擁有一個被代理對象,代理類與被代理對象相同的方法,活調用被代理對象的方法,不過中間會加以限制,您翻開任何一本設計模式相關的書,翻到代理模式這一節,講的就是它了。
另一種叫做動態代理,動態代理就是允許我們在程序運行過程中,為動態生成的對象,動態的生成代理。顯然,這比靜態代理靈活太多了。
Java默認提供了動態代理的實現方式,但是有限制,它要求被代理對象必須實現某一個接口。為了突破這一限制,為普通類也可以提供代理,CGLib這個庫橫空出世。
因為AOP涉及的知識較為復雜,所以我先將背景知識介紹一下。
- Java動態代理,就是Java本身提供的代理實現,要求對象必須實現某一個接口。
- CGLib庫,為Java提供了,為普通類提供代理的功能。
- aopalliance,aop聯盟包,統一類aop編程的一些概念,這個包里沒有具體的類實現,而是定義了幾個重要的概念接口,具體的aop實現,要遵從這些接口編程,才能達到一定的通用性。
- aspectj包,實現了,通過一種固定的編程語言,通過這種簡單的編程語言,我們可以定位到被代理的類,自動完成代理。
在aopallicance里面,定義了幾個核心概念:
-
Advice
增強,說明這是一個,實現這個接口,說明這個類負責某一種形式的增強。 -
Joinpoint
連接點,表示切點與目標方法連接處的信息。 -
MethodInterceptor
繼承了Interceptor接口,而Interceptor繼承了Advice接口,是一種Advice,但是有一個方法invoke。這個方法需要一個參數MethodInvocation。 -
MethodInvocation
表示的是連接點的信息以及連接點函數的調用。
結合上面的信息,我們發現,其實MethodInterceptor的invoke方法,調用的就是MethodInvocation的proceed方法,而這個proceed方法呢,應該調用的肯定是Method.invoke方法。所以,這是一種變相調用method.invoke的方式。為什么這樣做呢,猜一猜的話,肯定是為了代碼的復用,哈哈哈,這是廢話。
在Spring中,還定義了幾個核心概念: -
Pointcut
,切點,可以定位類以及方法 -
Advisor
,可以獲取一個增強。 -
PointcutAdvisor
,定義了哪些方法,具有什么類型的增強。 -
MethodMatcher
表示某一個方法是否符合條件 -
ClassFilter
定義了某個類是否符合條件。 -
TargetSource
被代理的對象,包括對象本身,類類型、所有接口 -
AdvisedSupport
代理相關的元數據,包括被代理的對象,增強等。 -
ProxyFactory
,代理工廠,可以獲得一個代理對象,同時具有AdvisedSupport的所有特性。 -
Cglib2AopProxy
,使用cglib實現的動態代理類,繼承了AbstractAopProxy抽象類,這個類的主要方法就是getProxy,通過什么呢,通過AdvisorSupport。 -
ReflectiveMethodInvocation
可以獲取連接點的信息,代理的對象等。 -
JdkDynamicAopProxy
,和Cglib2AopProxy類一個作用,通過AdvisorSupport來getProxy,不過是使用Java自帶的動態代理實現的。
其中,ProxyFactory是獲取一個代理對象的直接工廠,而這個代理對象,可以通過Cglib2AopProxy產生,也可以通過JdkDynamicAopProxy產生。
Spring AOP之所以能夠為動態生成的Bean提供代理,得益于PostProcessor接口。我們會議IOC初始化流程中,最后一部,就是得到BeanFactory之中所有繼承了PostProcessor接口的bean,調用它們的postProcessBeforeInitilization、postProcessAfterInitilization方法,來代理bean,生成新的bean。
基于這個突破口,我們只需要在xml配置文件中,放入PostProcessor對象,Spring就會自動的用這寫對象,來代理真正的對象。
在這里,我們的對象是AspectJAwareAdvisorAutoProxyCreator。
在這個對象的方法中,邏輯是這樣的,找到xml里面所有切面bean,然后在這些bean里面,找到符合被代理類的切面bean,找到切面bean之后,就可以獲得增強,切點等,于是可有構造一個AdvisorSupport,知道了AdvisorSupport,我們就能夠通過proxyFactory來獲取代理了。
至于如何這個類切面是用來切入代理類的,這個就要交給PointCut來實現了,pointcut有很多實現方式,這里我們用的是aspectj。具體這個類我就不細講了。
到目前位置,我自己已經將整個AOP的流程搞清楚了,下面通過流程圖的形式展示出來: