精品專欄
(此圖來自《Spring 揭秘》)
Spring IOC 容器所起的作用如上圖所示,它會以某種方式載入 Configuration Metadata,將其解析註冊到容器內部,然後回根據這些資訊系結整個系統的物件,最終組裝成一個可用的基於輕量級容器的應用系統。
Spring 在實現上述功能中,將整個流程分為兩個階段:容器初始化階段和載入bean 階段。
-
容器初始化階段:首先透過某種方式載入 Configuration Metadata (主要是依據 Resource、ResourceLoader 兩個體系),然後容器會對載入的 Configuration MetaData 進行解析和分析,並將分析的資訊組裝成 BeanDefinition,並將其儲存註冊到相應的 BeanDefinitionRegistry 中。至此,Spring IOC 的初始化工作完成。
-
載入 bean 階段:經過容器初始化階段後,應用程式中定義的 bean 資訊已經全部載入到系統中了,當我們顯示或者隱式地呼叫
getBean()
時,則會觸發載入 bean 階段。在這階段,容器會首先檢查所請求的物件是否已經初始化完成了,如果沒有,則會根據註冊的 bean 資訊實體化請求的物件,併為其註冊依賴,然後將其傳回給請求方。至此第二個階段也已經完成。
第一個階段前面已經用了 10 多篇部落格深入分析了(總結參考【死磕 Spring】—– IOC 之 IOC 初始化總結)。所以從這篇開始分析第二個階段:載入 bean 階段。當我們顯示或者隱式地呼叫 getBean()
時,則會觸發載入 bean 階段。如下:
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
內部呼叫 doGetBean()
方法,其接受四個引數:
-
name:要獲取 bean 的名字
-
requiredType:要獲取 bean 的型別
-
args:建立 bean 時傳遞的引數。這個引數僅限於建立 bean 時使用
-
typeCheckOnly:是否為型別檢查
這個方法的程式碼比較長,各位耐心看下:
protected
T doGetBean( final String name, @Nullable final ClassrequiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 獲取 beanName,這裡是一個轉換動作,將 name 轉換Wie beanName
final String beanName = transformedBeanName(name);
Object bean;
// 從快取中或者實體工廠中獲取 bean
// *** 這裡會涉及到解決迴圈依賴 bean 的問題
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
// 因為 Spring 只解決單例樣式下得迴圈依賴,在原型樣式下如果存在迴圈依賴則會丟擲異常
// **關於迴圈依賴後續會單獨出文詳細說明**
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// 如果容器中沒有找到,則從父類容器中載入
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else {
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
// 如果不是僅僅做型別檢查則是建立bean,這裡需要記錄
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
// 從容器中獲取 beanName 相應的 GenericBeanDefinition,並將其轉換為 RootBeanDefinition
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 檢查給定的合併的 BeanDefinition
checkMergedBeanDefinition(mbd, beanName, args);
// 處理所依賴的 bean
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
// 若給定的依賴 bean 已經註冊為依賴給定的b ean
// 迴圈依賴的情況
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
// 快取依賴呼叫
registerDependentBean(dep, beanName);
try {
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
// bean 實體化
// 單例樣式
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// 顯示從單利快取中刪除 bean 實體
// 因為單例樣式下為瞭解決迴圈依賴,可能他已經存在了,所以銷毀它
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// 原型樣式
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
// 從指定的 scope 下建立 bean
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; consider " +
"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// 檢查需要的型別是否符合 bean 的實際型別
if (requiredType != null && !requiredType.isInstance(bean)) {
try {
T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
if (convertedBean == null) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
return convertedBean;
}
catch (TypeMismatchException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to convert bean '" + name + "' to required type '" +
ClassUtils.getQualifiedName(requiredType) + "'", ex);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
}
return (T) bean;
}
程式碼是相當長,處理邏輯也是相當複雜,下麵將其進行拆分闡述。
1.獲取 beanName
final String beanName = transformedBeanName(name);
這裡傳遞的是 name,不一定就是 beanName,可能是 aliasName,也有可能是 FactoryBean,所以這裡需要呼叫 transformedBeanName()
方法對 name 進行一番轉換,主要如下:
protected String transformedBeanName(String name) {
return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
// 去除 FactoryBean 的修飾符
public static String transformedBeanName(String name) {
Assert.notNull(name, "'name' must not be null");
String beanName = name;
while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
}
return beanName;
}
// 轉換 aliasName
public String canonicalName(String name) {
String canonicalName = name;
// Handle aliasing...
String resolvedName;
do {
resolvedName = this.aliasMap.get(canonicalName);
if (resolvedName != null) {
canonicalName = resolvedName;
}
}
while (resolvedName != null);
return canonicalName;
}
主要處理過程包括兩步:
-
去除 FactoryBean 的修飾符。如果 name 以 “&” 為字首,那麼會去掉該 "&",例如,
name="&studentService;"
,則會是name="studentService"
。 -
取指定的 alias 所表示的最終 beanName。主要是一個迴圈獲取 beanName 的過程,例如別名 A 指向名稱為 B 的 bean 則傳回 B,若 別名 A 指向別名 B,別名 B 指向名稱為 C 的 bean,則傳回 C。
2.從單例 bean 快取中獲取 bean
對應程式碼段如下:
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
我們知道單例樣式的 bean 在整個過程中只會被建立一次,第一次建立後會將該 bean 載入到快取中,後面在獲取 bean 就會直接從單例快取中獲取。如果從快取中得到了 bean,則需要呼叫 getObjectForBeanInstance()
對 bean 進行實體化處理,因為快取中記錄的是最原始的 bean 狀態,我們得到的不一定是我們最終想要的 bean。
3.原型樣式依賴檢查與 parentBeanFactory
對應程式碼段
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
Spring 只處理單例樣式下得迴圈依賴,對於原型樣式的迴圈依賴直接丟擲異常。主要原因還是在於 Spring 解決迴圈依賴的策略有關。對於單例樣式 Spring 在建立 bean 的時候並不是等 bean 完全建立完成後才會將 bean 新增至快取中,而是不等 bean 建立完成就會將建立 bean 的 ObjectFactory 提早加入到快取中,這樣一旦下一個 bean 建立的時候需要依賴 bean 時則直接使用 ObjectFactroy。但是原型樣式我們知道是沒法使用快取的,所以 Spring 對原型樣式的迴圈依賴處理策略則是不處理(關於迴圈依賴後面會有單獨文章說明)。
如果容器快取中沒有相對應的 BeanDefinition 則會嘗試從父類工廠(parentBeanFactory)中載入,然後再去遞迴呼叫 getBean()
。
3. 依賴處理
對應原始碼如下:
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName);
try {
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
每個 bean 都不是單獨工作的,它會依賴其他 bean,其他 bean 也會依賴它,對於依賴的 bean ,它會優先載入,所以在 Spring 的載入順序中,在初始化某一個 bean 的時候首先會初始化這個 bean 的依賴。
作用域處理
Spring bean 的作用域預設為 singleton,當然還有其他作用域,如prototype、request、session 等,不同的作用域會有不同的初始化策略。
型別轉換
在呼叫 doGetBean()
方法時,有一個 requiredType 引數,該引數的功能就是將傳回的 bean 轉換為 requiredType 型別。當然就一般而言我們是不需要進行型別轉換的,也就是 requiredType 為空(比如 getBean(Stringname)
),但有可能會存在這種情況,比如我們傳回的 bean 型別為 String,我們在使用的時候需要將其轉換為 Integer,那麼這個時候 requiredType 就有用武之地了。當然我們一般是不需要這樣做的。
至此 getBean()
過程講解完了。後續將會對該過程進行拆分,更加詳細的說明,弄清楚其中的來龍去脈,所以這篇部落格只能算是 Spring bean 載入過程的一個概覽。拆分主要是分為三個部分:
-
分析從快取中獲取單例 bean,以及對 bean 的實體中獲取物件
-
如果從單例快取中獲取 bean,Spring 是怎麼載入的呢?所以第二部分是分析 bean 載入,以及 bean 的依賴處理
-
bean 已經載入了,依賴也處理完畢了,第三部分則分析各個作用域的 bean 初始化過程。
【死磕 Spring】----- IOC 之 IOC 初始化總結
【死磕 Spring】----- IOC 之解析 bean 標簽:解析自定義標簽
【死磕 Spring】----- IOC 之解析 bean 標簽:constructor-arg、property 子元素
【死磕 Spring】—– IOC 之解析 bean 標簽:meta、lookup-method、replace-method)
【死磕 Spring】----- IOC 之解析 bean 標簽:開啟解析行程
【死磕 Spring】----- IOC 之 獲取 Document 物件
【死磕 Spring】----- IOC 之 註冊 BeanDefinition
【死磕 Spring】----- IOC 之 Spring 統一資源載入策略
【死磕 Spring】----- IOC 之深入理解 Spring IoC
END
我是 Java 技術驛站,感謝有你
>>>>>> 加群交流技術 <<<<<<