1. 程式人生 > >Spring Ioc 之 Bean的載入(三):各個 scope 的 Bean 建立

Spring Ioc 之 Bean的載入(三):各個 scope 的 Bean 建立

在Spring中Bean有許多不同的作用域,例如:singleton、prototype、request等等,本篇文章就來分析一下各個scope的Bean是怎麼建立的

一、singleton

程式碼:

// Create bean instance.
	//建立單例Bean
	if (mbd.isSingleton()) {
	//這裡使用了一個匿名內部類,建立Bean例項物件,並且註冊給所依賴的物件
	sharedInstance = getSingleton(beanName, () -> {
		try {
			//建立一個指定Bean例項物件,如果有父級繼承,則合併子類和父類的定義
			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.
			//顯式地從容器單例模式Bean快取中清除例項物件
			destroySingleton(beanName);
			throw ex;
		}
	});
	//獲取給定Bean的例項物件
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
	}
  • 這裡使用了匿名內部類,先通過createBean(beanName, mbd, args)方法獲取一個 ObjectFactory

  • 把 ObjectFactory 作為引數傳入 getSingleton(beanName,objectFactory)方法

  • 使用 getSingleton(beanName,objectFactory) 方法返回的 sharedInstance 作為引數傳入 getObjectForBeanInstance(sharedInstance, name, beanName, mbd)來回去最終的Bean例項(詳情見Spring Ioc 之 Bean的載入(一)

createBean(beanName, mbd, args)方法比較複雜,在之後的文章中會詳細分析,這裡就先略過,直接看 getSingleton(beanName,objectFactory)方法。

// DefaultSingletonBeanRegistry.java

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(beanName, "Bean name must not be null");
//全域性加鎖
synchronized (this.singletonObjects) {
	// 從快取中獲取單例bean
	// 因為 singleton 模式其實就是複用已經建立的 bean 所以這步驟必須檢查
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null) {
		//是否正在銷燬該bean
		if (this.singletonsCurrentlyInDestruction) {
			throw new BeanCreationNotAllowedException(beanName,
					"Singleton bean creation not allowed while singletons of this factory are in destruction " +
					"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
		}
		// 載入前置處理
		beforeSingletonCreation(beanName);
		boolean newSingleton = false;
		boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
		if (recordSuppressedExceptions) {
			this.suppressedExceptions = new LinkedHashSet<>();
		}
		try {
			// 初始化 bean
			// 這個過程其實是呼叫 createBean() 方法
			singletonObject = singletonFactory.getObject();
			newSingleton = true;
		}
		catch (IllegalStateException ex) {
			// Has the singleton object implicitly appeared in the meantime ->
			// if yes, proceed with it since the exception indicates that state.
			singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				throw ex;
			}
		}
		catch (BeanCreationException ex) {
			if (recordSuppressedExceptions) {
				for (Exception suppressedException : this.suppressedExceptions) {
					ex.addRelatedCause(suppressedException);
				}
			}
			throw ex;
		}
		finally {
			if (recordSuppressedExceptions) {
				this.suppressedExceptions = null;
			}
			//後置處理
			afterSingletonCreation(beanName);
		}
		if (newSingleton) {
			//加入快取中
			addSingleton(beanName, singletonObject);
		}
	}
	return singletonObject;
	}
	}

在這段程式碼中,其實主要是做了一些準備和預處理步驟,真正建立Bean是在singletonFactory.getObject()方法實現的,而 singletonFactory 是由createBean()方法建立後回撥的引數。
那麼這段程式碼主要做的事情是什麼呢?

  • 嘗試從快取中獲取單例Bean
    如果已經載入了則直接返回,否則開始載入過程

  • 載入前置處理

  • 獲取Bean例項

  • 後置處理

  • 加入快取

1.1、載入前置處理

beforeSingletonCreation(beanName)是個標記方法,我們來看程式碼:

// 用於新增標誌,當前 bean 正處於建立中
	protected void beforeSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			//新增失敗,丟擲異常
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

把 beanName 新增到 singletonsCurrentlyInCreationmap中,用來表示該單例bean正在建立,如果新增失敗,丟擲異常。

1.2、獲取Bean例項

通過createBean(beanName)方法返回的 singletonFactory 獲取Bean。

1.3、後置處理

afterSingletonCreation(beanName)同樣是個表示方法:

// 用於移除標記,當前 Bean 不處於建立中
	protected void afterSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
			//移除失敗,丟擲異常
			throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
		}
	}

建立Bean之後移除建立標示。
前置處理和後置處理的這個建立標示,會在呼叫isSingletonCurrentlyInCreation(String beanName)時用到,該方法用來判斷當前bean是否已經在建立中。

1.4、加入快取

直接看程式碼:

protected void addSingleton(String beanName, Object singletonObject) {
	synchronized (this.singletonObjects) {
		this.singletonObjects.put(beanName, singletonObject);
		this.singletonFactories.remove(beanName);
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	    }
	}

一個 put、一個 add、兩個 remove 操作。

  • 【put】singletonObjects 屬性,單例 bean 的快取。

  • 【remove】singletonFactories 屬性,單例 bean Factory 的快取。

  • 【remove】earlySingletonObjects 屬性,“早期”建立的單例 bean 的快取。

  • 【add】registeredSingletons 屬性,已經註冊的單例快取。

二、Prototype

程式碼:

        //建立多例Bean
	else if (mbd.isPrototype()) {
		// It's a prototype -> create a new instance.
		//原型模式(Prototype)是每次都會建立一個新的物件
		Object prototypeInstance = null;
		try {
			//載入前置處理,預設的功能是註冊當前建立的原型物件
			beforePrototypeCreation(beanName);
			//建立指定Bean物件例項
			prototypeInstance = createBean(beanName, mbd, args);
		}
		finally {
			//載入後置處理,預設的功能告訴IOC容器指定Bean的原型物件不再建立
			afterPrototypeCreation(beanName);
		}
		//獲取給定Bean的例項物件
		bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
	}

原型模式很簡單,直接建立一個新的例項就好了,不再從快取中去獲取。
beforePrototypeCreation(beanName)前置處理,將當前bean標記為正在建立的原型。
afterPrototypeCreation(beanName)後置處理,取消當前bean的正在建立標示。
呼叫getObjectFrBeanInstance()方法獲取最終bean。(詳情見Spring Ioc 之 Bean的載入(一)

三、其他作用域

//要建立的Bean既不是Singleton也不是Prototype
	//如:request、session、application等生命週期
	else {
		String scopeName = mbd.getScope();
		final Scope scope = this.scopes.get(scopeName);
		//Bean定義資源中沒有配置生命週期範圍,則Bean定義不合法
		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的例項物件
			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);
		}
	}

分為以下幾個步驟:

  • 從Scope註解中獲取scope名稱

  • 前置處理

  • createBean()

  • 後置處理

  • scope.get()獲取bean

  • getObjectForBeanInstance()方法獲取Bean

核心流程與原型模式一樣,只不過這裡呼叫了scope.get()來獲取bean。

Object get(String name, ObjectFactory<?> objectFactory);

scope.get()是一個介面,它有多種實現類:

我們看一下spring自帶的一個實現 SimpleThreadScope:

//SimpleThreadScope.java

private final ThreadLocal<Map<String, Object>> threadScope =
			new NamedThreadLocal<Map<String, Object>>("SimpleThreadScope") {
				@Override
				protected Map<String, Object> initialValue() {
					return new HashMap<>();
				}
			};


	//獲取bean的例項
	@Override
	public Object get(String name, ObjectFactory<?> objectFactory) {
		// 獲取 scope 快取
		Map<String, Object> scope = this.threadScope.get();
		Object scopedObject = scope.get(name);
		if (scopedObject == null) {
			scopedObject = objectFactory.getObject();
			// 加入快取
			scope.put(name, scopedObject);
		}
		return scopedObject;
	}

其他scope的實現就不一一去看了,感興趣的朋友可以自己看一下。

總結

上面的程式碼中有2個重要方法:

  • createBean(beanName, mbd, args)

  • getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd)

這2個方法在3個程式碼分支中都用到了,createBean下篇文章會詳細分析,getObjectForBeanInstance方法在Spring Ioc 之 Bean的載入(一)中已經分析過了。
這裡再引用下《Spring 原始碼深度解析》對該方法的分析:

這個方法主要是驗證以下我們得到的 bean 的正確性,其實就是檢測當前 bean 是否是 FactoryBean 型別的 bean 。
如果是,那麼需要呼叫該 bean 對應的 FactoryBean 例項的 getObject() 方法,作為返回值。
無論是從快取中獲得到的 bean 還是通過不同的 scope 策略載入的 bean 都只是最原始的 bean 狀態,並不一定就是我們最終想要的 bean。
舉個例子,假如我們需要對工廠 bean 進行處理,那麼這裡得到的其實是工廠 bean 的初始狀態,但是我們真正需要的是工廠 bean 中定義 factory-method 方法中返回的 bean,而 getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd) 方法,就是完成這個工作的。

參考:
《Spring 原始碼深度解析》- 郝佳<