Spring原始碼:Bean生命週期(三)

2023-05-05 06:01:11

前言

在之前的文章中,我們已經對 bean 的準備工作進行了講解,包括 bean 定義和 FactoryBean 判斷等。在這個基礎上,我們可以更加深入地理解 getBean 方法的實現邏輯,並在後續的學習中更好地掌握createBean 方法的實現細節。

getBean用法

講解getBean方法之前,我們先來看看他有幾種常見的用法:

// 建立一個Spring容器  
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);  
UserService bean1 = applicationContext.getBean(UserService.class);  
UserService bean2 = (UserService)applicationContext.getBean("userService");  
UserService bean3 = applicationContext.getBean("userService",UserService.class);  
UserService bean4 = (UserService) applicationContext.getBean("userService",new OrderService());  
bean1.test();  
bean2.test();  
bean3.test();  
bean4.test();

關於獲取 bean 的方法,前兩種方法應該比較常見,這裡就不再贅述。第三種方法實際上是在獲取 bean 的時候,會先判斷是否符合指定的型別,如果符合,則進行型別轉換並返回對應的 bean 範例。第四種方法則是在建立 bean 範例時,通過推斷構造方法的方式來選擇使用帶有引數的構造方法進行範例化。

如果我們想要讓第四種方法生效,可以考慮使用多例的形式,即通過設定 scope 屬性為 prototype 來實現。這樣,每次獲取 bean 時,都會建立新的 bean 範例,從而可以觸發使用帶有引數的構造方法進行範例化,比如這樣:

@Component  
@Scope("prototype")  
public class UserService {  
  
     public UserService(){  
      System.out.println(0);  
   }  
     public UserService(OrderService orderService){  
      System.out.println(1);  
   }  
   public void test(){  
      System.out.println(11);  
   }  
}

getBean大體流程

由於方法程式碼太多,我就不貼程式碼了,我這邊只貼一些主要的虛擬碼,方便大家閱讀,然後我在對每個流程細講下:

	protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {

		// name有可能是 &xxx 或者 xxx,如果name是&xxx,那麼beanName就是xxx
		// name有可能傳入進來的是別名,那麼beanName就是id
		String beanName = transformedBeanName(name);
		Object beanInstance;

		// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			
			// 如果sharedInstance是FactoryBean,那麼就呼叫getObject()返回物件			
		}

		else {	
			//檢查是否本beanfactory沒有當前bean定義,檢視有父容器,如果有,則呼叫父容器的getbean方法				  
			try {				 
				RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				// 檢查BeanDefinition是不是Abstract的
				checkMergedBeanDefinition(mbd, beanName, args);
				// Guarantee initialization of beans that the current bean depends on.				 
				//檢視是否有dependsOn註解,如果存在迴圈依賴則報錯
				// Create bean instance.
				if (mbd.isSingleton()) {
					//呼叫createBean方法
					//如果是FactoryBean則呼叫getObject
				}
				else if (mbd.isPrototype()) {
					//呼叫createBean方法,與單例只是前後邏輯不一樣
					//如果是FactoryBean則呼叫getObject
				}
				else {
					Scope不同型別有不同實現
					//呼叫createBean方法,與單例只是前後邏輯不一樣
					//如果是FactoryBean則呼叫getObject
				}
			}catch{
				......
			}
		}

		// 檢查通過name所獲得到的beanInstance的型別是否是requiredType
		return adaptBeanInstance(name, beanInstance, requiredType);
	}

單例快取池

在 Spring 中,不管傳入的 beanName 是多例的還是單例的,都會先從單例快取池中獲取。有些人可能會覺得這樣做會浪費一些效能,但實際上 Spring 考慮到了大部分託管的 bean 都是單例的情況,因此忽略了這一點效能。實際上,這樣的效能消耗並不大。可以將其類比於 Java 的雙親委派機制,都會先檢視本載入器是否有快取,如果沒有再向父載入器去載入。

parentBeanFactory

在分析 bean 定義是如何建立的時,我們可以不考慮單例快取池中獲取物件的情況,而是逐步分析 bean 定義是如何建立的。在這個過程中,即使存在 parentBeanFactory,我們也可以跳過它,因為我們的啟動容器並沒有設定任何父容器。原始碼也很簡單,如果本容器沒有 bean 定義,就直接呼叫父容器的 getBean 相關方法:

BeanFactory parentBeanFactory = getParentBeanFactory();
            if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                // Not found -> check parent.
                // &&&&xxx---->&xxx
                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 if (requiredType != null) {
                    // No args -> delegate to standard getBean method.
                    return parentBeanFactory.getBean(nameToLookup, requiredType);
                }
                else {
                    return (T) parentBeanFactory.getBean(nameToLookup);
                }
            }

dependsOn

在呼叫 getBean 方法之前,已經將合併的 bean 定義存入了容器中。因此,我們可以直接獲取已經合併好的 bean 定義,並解析 bean 定義上的 dependsOn 註解。具體的原始碼邏輯如下:

RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

                // 檢查BeanDefinition是不是Abstract的
                checkMergedBeanDefinition(mbd, beanName, args);

                // Guarantee initialization of beans that the current bean depends on.
                String[] dependsOn = mbd.getDependsOn();
                if (dependsOn != null) {
                    // dependsOn表示當前beanName所依賴的,當前Bean建立之前dependsOn所依賴的Bean必須已經建立好了
                    for (String dep : dependsOn) {
                        // beanName是不是被dep依賴了,如果是則出現了迴圈依賴
                        if (isDependent(beanName, dep)) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                        }
                        // dep被beanName依賴了,存入dependentBeanMap中,dep為key,beanName為value
                        registerDependentBean(dep, beanName);

                        // 建立所依賴的bean
                        try {
                            getBean(dep);
                        }
                        catch (NoSuchBeanDefinitionException ex) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                        }
                    }
                }

這裡的邏輯還是相對簡單的。如果當前 bean 被 dependsOn 註解所依賴,那麼會先去建立所依賴的 bean。但是這種方式是解決不了迴圈依賴的問題的。在實現上,只使用了兩個 Map 進行判斷:

// 某個Bean被哪些Bean依賴了
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
// 某個Bean依賴了哪些Bean
private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);

isSingleton

sharedInstance = getSingleton(beanName, () -> {
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            // Explicitly remove instance from singleton cache: It might have been put there
                            // eagerly by the creation process, to allow for circular reference resolution.
                            // Also remove any beans that received a temporary reference to the bean.
                            destroySingleton(beanName);
                            throw ex;
                        }
                    });
                    beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

在這個階段,我們可以看到程式碼已經在準備建立單例 bean 範例了,因此我們可以不去深入理解這部分的原始碼邏輯。反正,在 getSingleton 方法中,會呼叫 createBean 方法。這裡使用了 lambda 表示式,如果有不太瞭解的讀者,可以參考下之前發的文章進行學習:

isPrototype

  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);
			}
			beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
		}

在這個階段,我們發現建立 bean 的方法已經改變了,直接呼叫了 createBean 方法,而不是通過 getSingleton 方法進行呼叫。至於 beforePrototypeCreation 和 afterPrototypeCreation,我們可以不用管它們,因為它們只是儲存一些資訊,對我們建立 bean 並沒有太大的影響。

其他Scope

講解這部分原始碼之前,我們先來看看還有哪些Scope域:

//@RequestScope
@SessionScope
public class User {
}

現在我們來看一下 RequestScope 和 SessionScope,它們與其他作用域類似,只是一個組合註解。它們的元註解資訊如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

	/**
	 * Alias for {@link Scope#proxyMode}.
	 * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
	 */
	@AliasFor(annotation = Scope.class)
	ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

然後我們再來看下Spring對其他Scope註解的邏輯判斷:

String scopeName = mbd.getScope();
					if (!StringUtils.hasLength(scopeName)) {
						throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
					}
					Scope scope = this.scopes.get(scopeName);
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {  // session.getAttriute(beaName)  setAttri
						Object scopedInstance = scope.get(beanName, () -> {
							beforePrototypeCreation(beanName);
							try {
								return createBean(beanName, mbd, args);
							}
							finally {
								afterPrototypeCreation(beanName);
							}
						});
						beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
						throw new ScopeNotActiveException(beanName, scopeName, ex);
					}

其實他主要用的getAttriute方法,我們看下scope.get主要的邏輯判斷:

	public Object get(String name, ObjectFactory<?> objectFactory) {
		RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
		Object scopedObject = attributes.getAttribute(name, getScope());
		if (scopedObject == null) {
			scopedObject = objectFactory.getObject();
			attributes.setAttribute(name, scopedObject, getScope());
			// Retrieve object again, registering it for implicit session attribute updates.
			// As a bonus, we also allow for potential decoration at the getAttribute level.
			Object retrievedObject = attributes.getAttribute(name, getScope());
			if (retrievedObject != null) {
				// Only proceed with retrieved object if still present (the expected case).
				// If it disappeared concurrently, we return our locally created instance.
				scopedObject = retrievedObject;
			}
		}
		return scopedObject;
	}

在這個階段,我們可以看到,通過 objectFactory.getObject() 方法,會呼叫外層定義的 lambda 表示式,也就是 createBean 方法的邏輯。假設這個過程成功地建立了 bean 範例,並返回了它,那麼 Spring 會呼叫 setAttribute 方法,將這個 bean 範例以及其 scope 值放入以 beanName 為 key 的屬性中。這樣,當需要獲取這個 bean 範例時,Spring 就可以直接從作用域中獲取了。

結語

getBean 方法主要包含以下幾個步驟:

  1. 首先,從單例快取池中獲取 bean 範例。如果沒有,Spring 會建立新的 bean 範例,並將其新增到單例快取池中。
  2. 接著,Spring 會檢查當前容器是否有指定名稱的 bean 定義。如果沒有,Spring 會呼叫父容器的 getBean 方法,直到找到為止。
  3. 一旦找到了 bean 定義,Spring 會根據不同的作用域型別,建立對應的 bean 範例,並將其儲存在作用域中。
  4. 最後,Spring 會返回建立好的 bean 範例。

非常好,這樣我們對 getBean 方法的邏輯判斷有了一個大體的瞭解,有助於我們更好地理解 createBean 方法的實現細節。如果在後續的學習中有任何問題或疑問,可以隨時聯絡我進行諮詢。

公眾號