1. 程式人生 > >Spring作用域 (Scope:Request,Session,Thread,Refresh) 的代理機制原始碼解析

Spring作用域 (Scope:Request,Session,Thread,Refresh) 的代理機制原始碼解析

Spring有很多Scope,比如Singleton,Prototype,Request,Session,SpringCloud又新增了Thread,Refresh。預設的Scope是Singleton,Spring容器內最多的就是Singleton型別的Bean了,但其他不同型別的Scope在特定的場合也有特殊的作用。
Spring對非Singleton的Scode都使用了代理機制,這篇部落格主要是講解針對Scope的代理機制和不同Scope的Bean是如何具備不同的特性的。

Spring在解析標識@Scope註解的Bean時,會執行如下程式碼:

public abstract class ScopedProxyUtils
{ /** * 先定義一個ScopedProxyFactoryBean型別的BeanDefinition,其是一個FactoryBean,getObject() 方法返回的是一個Cglib生成的代理物件 * 通過Cglib為Scope的Bean生成代理物件,這個ProxyBean是Singleton型別的Bean,容器內其他Bean依賴得到的就是這個代理物件 * 這樣Spring容器內的Bean就能在初始化時就依賴非Singleton型別的Bean了,而不需要每次都用BeanFactory去獲取 */ public static BeanDefinitionHolder createScopedProxy
(BeanDefinitionHolder definition, BeanDefinitionRegistry registry, boolean proxyTargetClass) { String originalBeanName = definition.getBeanName(); BeanDefinition targetDefinition = definition.getBeanDefinition(); String targetBeanName = getTargetBeanName(originalBeanName); // Create a scoped proxy definition for the original bean name,
// "hiding" the target bean in an internal target definition. // 使用ScopedProxyFactoryBean包裝原始的BeanClass RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); ...... // Register the target bean as separate bean in the factory. registry.registerBeanDefinition(targetBeanName, targetDefinition); // Return the scoped proxy definition as primary bean definition // (potentially an inner bean). return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases()); } }

下面看看ScopedProxyFactoryBean內部關鍵程式碼:

public class ScopedProxyFactoryBean extends ProxyConfig implements FactoryBean<Object>, BeanFactoryAware {

	/** The TargetSource that manages scoping,關鍵程式碼,AOP動態代理時被代理物件的來源類 */
	private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource();

	/** The cached singleton proxy */
	private Object proxy;
	
	@Override
	public void setBeanFactory(BeanFactory beanFactory) {
		if (!(beanFactory instanceof ConfigurableBeanFactory)) {
			throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
		}
		ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
		
		// 將BeanFactory設定入TargetSource,也就是動態代理時被代理物件的來源
		this.scopedTargetSource.setBeanFactory(beanFactory);

		ProxyFactory pf = new ProxyFactory();
		pf.copyFrom(this);
		pf.setTargetSource(this.scopedTargetSource);
		......
		this.proxy = pf.getProxy(cbf.getBeanClassLoader());
	}


	@Override
	public Object getObject() {
		if (this.proxy == null) {
			throw new FactoryBeanNotInitializedException();
		}
		return this.proxy;  // 其他Bean依賴Scope Bean時得到的是被代理物件
	}

}

通過對一個@RequestScope標識的Class,例項化後生成的Bean格式如下:

RequestScope型別的Bean

targetSource代表這個物件代理的Bean;
advisorArray標識當前代理物件織入了多少個切面,當前Bean只有一個,DefaultIntroductionAdvisor;
這個切面的作用主要是將BeanName設定入Joinpoint中:

public abstract class ExposeBeanNameAdvisors {

	 /**
	 * Create a new advisor that will expose the given bean name, introducing
	 * the NamedBean interface to make the bean name accessible without forcing
	 * the target object to be aware of this Spring IoC concept.
	 * @param beanName the bean name to expose
	 */
	public static Advisor createAdvisorIntroducingNamedBean(String beanName) {
		// 對外暴露BeanName的Advisor
		return new DefaultIntroductionAdvisor(new ExposeBeanNameIntroduction(beanName));
	}

}
 

之後就直接執行被代理物件的方法。

當然我們也可以對非Singleton型別的Bean做AOP代理,這樣advisorArray裡面就會生成額外的切面物件,感興趣的可以自行嘗試。

AOP代理裡還有一個重要元件,TargetSource,也就是被代理物件的來源:

// 動態的切面攔截器,AOP代理物件都會走進其 #intercept 方法,織入Advisor
private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
	private final AdvisedSupport advised;
	
	//  獲取被代理物件
	protected Object getTarget() throws Exception {
		return this.advised.getTargetSource().getTarget();
	}

}

之前在看ScopedProxyFactoryBean原始碼時,其內部的TargetSource例項是:SimpleBeanTargetSource,看其原始碼:

public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {

	@Override
	public Object getTarget() throws Exception {
		// 每次執行代理物件的方法時,都從BeanFactory處獲取指定名稱的Scope Bean
		return getBeanFactory().getBean(getTargetBeanName());
	}

}

每次通過代理物件執行原始Bean的方法時,都會從BeanFactory處獲取Scope Bean(注意不是Scope Bean 內部呼叫),接下來看看BeanFactory對非Singleton型別的Bean的獲取方式程式碼:

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {

	protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
			throws BeansException {
				......
				// Create bean instance.
				if (mbd.isSingleton()) {
					......
				}

				else if (mbd.isPrototype()) {     // 如果是Prototype型別的Bean,每次getBean( ) 都new一個
					// 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 {       // 非Singleton,Prototype型別的Bean
					String scopeName = mbd.getScope();
					final Scope scope = this.scopes.get(scopeName);    // 獲取對應Scope的處理器
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {
						// 通過Scope處理器的get( ) 方法獲取Bean例項,同時將new Bean的操作封裝成一個回撥函式,
						// 由Scope處理器來決定是否建立一個新的Bean
						Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
							@Override
							public Object getObject() throws BeansException {
								beforePrototypeCreation(beanName);
								try {
									return createBean(beanName, mbd, args);
								}
								finally {
									afterPrototypeCreation(beanName);
								}
							}
						});
						bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
				}
		......
}

到了這裡,結合之前的程式碼和講解,應該能很容易的看明白不同非Singleton型別的Bean的代理機制和內部實現了,Scope型別有不少,我就不一一去詳細的解釋原始碼了, 接下來我針對每個Scope做個總結:

  • Request:從RequestContextHolder獲取ThreadLocal型別的RequestAttributes,正常情況下返回ServletRequestAttributes型別的物件,裡面封裝了HttpServletRequest,HttpServletResponse等Http請求相關的物件。當Spring接收到一個Http請求時,會將請求相關物件封裝成一個ServletRequestAttributes,設定到RequestContextHolder裡。如果獲取ServletRequestAttributes時返回null,也就是Spring還沒有接收到Http請求,比如Spring容器初始化時。那麼就會丟擲異常。如果獲取ServletRequestAttributes,但是沒有獲取到此Bean,那麼通過ObjectFactory的回撥函式new一個Bean,然後設定到ServletRequestAttributes裡,實際就是設定到HttpServletRequest裡。也就是說Request Scope 型別的物件不能在容器初始化時呼叫其方法,但是能引用到Scope的代理物件;不要非同步的獲取Request Scope型別的Bean;在一次Http請求中,多次獲取到的相同名稱的Request Scope Bean是同一個物件
  • Session:作用機制與Request類似,也是從ServletRequestAttributes裡獲取的,當不存在時new一個,設定到Session中,注意事項與Request也一致。
  • Thread:通過ThreadLocalScopeCache 來管理Thread型別的Bean,每個執行緒都持有一個ConcurrentMap<String, Object>用於儲存Thread Bean,key就是BeanName,value就是Thread Bean,當不存在時通過ObjectFactory new 一個。對於執行緒複用情況下,記得手動清理之前的ThreadLocalCache
  • Refresh:也提供了Cache機制,當第一次get時,通過ObjectFactory建立一個,然後快取起來,之後每次獲取都先從快取中獲取,如果不存在,再通過ObjectFactory建立。同時可以通過ContextRefresher 清空所有快取,這樣下次獲取的Refresh Bean就是重新生成的。當一個Bean裡的持有Spring Environment的一些資訊,且這些資料是可配置,動態重新整理的,那麼使用@RefreshScope標識Bean,同時搭配ContextRefresher就能在配置更新後動態的更新這些物件,保證配置資訊能夠實時的起作用。

相關推薦

Spring作用 (ScopeRequest,Session,Thread,Refresh) 的代理機制原始碼解析

Spring有很多Scope,比如Singleton,Prototype,Request,Session,SpringCloud又新增了Thread,Refresh。預設的Scope是Singleton,Spring容器內最多的就是Singleton型別的Bea

Spring的Bean作用 scope屬性指定Bean是否為單例物件

Bean作用域: 預設屬性scope="singleton"表示容器初始化建立這一個Bean 單例的 Hello person1 = (Hello) applicationContext.get

spring ioc---bean的作用(scope)

xsd官方文件中,對標籤bean的屬性scope的說明: The scope of this bean: typically "singleton" (one shared instance, which will be returned by all calls to getBean wi

(三)Spring 高階裝配 bean的作用@Scope

1.預設情況下,spring通過@Autowared注入的bean是單例的bean,但有些情況是不滿足的,例如:購物車,每個會話,或每個使用者登入使用的購物車都是獨立的 spring的定義的作用域: a:單例(Singleton) b:原型(prototype):每次注入的都會建立一個新的bean例項。 c:

AngularJS的作用Scope

環境 嵌套 不同 ng-repeat size 理解 microsoft ack target 1.簡介 angularjs啟動並生成視圖時,會根據ng-app元素和$RootScope進行綁定。$RootScope是所有$scope對象的最上層,是ang

使用application作用實現當用戶重復登錄時,擠掉原來的用戶

ont 必須 用戶名 使用 執行 gets quest return http 使用application作用域實現:當用戶重復登錄時,擠掉原來的用戶 一、實現思想 1.application(ServletContext)是保存在服務器端的作用域,我們在applicati

spring作用與生命週期簡介

1. spring 作用域 使用scope屬性指定bean的作用域,預設值為singleton singleton 即單例模式,每次返回都是同一個bean prototype 原型模式,每次都會重新生成一個新的bean例項 2. bean生命週期簡介

Spring 作用傳值

/** * * @param request 可以把原生servlet有的東西寫在引數裡,response,session等 * @param map 可以存在Map中 * @param model 可以存在model介面物件 中 * @ret

SpringBoot中的Bean作用————@scope

註解說明 使用註解: @scope 效果:指定Bean的作用域 ,預設的是singleton,常用的還有prototype Scope的全部可選項 singleton 全域性只有一個例項,即單例模式 prototype 每次注入Bean都是一個新的例項 r

作用 [[scope]]

每個javascript函式都是一個物件,物件中有些屬性我們可以訪問,比如name屬性,但有些不可以,這些屬性僅供javascript引擎存取,[[scope]]就是其中一個,指的就是我們所說的作用域,其中儲存了執行期上下文的集合。這個集合呈鏈式連結,我們把這種鏈式連結叫做作用域鏈

spring boot 報錯Exception in thread "main" java.lang.NoSuchMethodError 根源在pom.xml引用的包中的JAR有衝突

Exception in thread "main" java.lang.NoSuchMethodError 突然發現一個spring boot專案tomcat啟動不起來了。 目錄下:mvn dependency:tree 檢視是不是有依賴的JAR包有衝突了 重新一個

Angular系列之作用$scope(四)

其實在前幾篇的文章當中有提到過$scope,但並沒有去詳細的解釋$scope是做什麼的等一些特性;而且$scope在angular中是最為重要的知識點之一,所以要單獨抽離出來。 如果瞭解過開發模式MVC模式的話,如果沒有了解過可以先看一下《Angular系列之瞭解(一)

Angular——作用($scope)內變數的變數名是動態的

作用域內定義變數,通常是: $scope.變數名 = 變數值; 若變數名為動態的,比如:字串拼接的、動態獲取的,這是在作用域內定義變數: var 中間變數 = 動態變數名;  $scope[中間變數]

理解AngularJS的作用Scope

概敘: AngularJS中,子作用域一般都會通過JavaScript原型繼承機制繼承其父作用域的屬性和方法。但有一個例外:在directive中使用scope: { ... },這種方式建立的作用域是一個獨立的"Isolate"作用域,它也有父作用域,但父作用域不在

Spring Boot乾貨系列(七)預設日誌logback配置解析

前言 今天來介紹下Spring Boot如何配置日誌logback,我剛學習的時候,是帶著下面幾個問題來查資料的 如何引入日誌? 日誌輸出格式以及輸出方式如何配置? 程式碼中如何使用? 正文       Spring Boot在所有

Spring-Session實現Session共享實現原理以及原始碼解析

知其然,還要知其所以然 ! 本篇介紹Spring-Session的整個實現的原理。以及對核心的原始碼進行簡單的介紹! 實現原理介紹 實現原理這裡簡單說明描述: 就是當Web伺服器接收到http請求後,當請求進入對應的Filter進行過濾,

【直播預告】Java Spring Boot開發實戰系列課程【第11講】訊息中介軟體 RabbitMQ 與api原始碼解析

內容概要:mq訊息中介軟體在高併發系統架構中扮演關鍵角色,阿里雙11高併發使用了mq技術。本次課程一起學習最新Java Spring Boot 2.0、RabbitMQ中介軟體的最新特性與實戰應用,同樣會分析核心api原始碼。主講人:徐雷(阿里雲棲特邀Java專家)直播時間:2019年1月8日 週二 今晚20

Spring Boot乾貨系列(七)預設日誌logback配置解析

前言 今天來介紹下Spring Boot如何配置日誌logback,我剛學習的時候,是帶著下面幾個問題來查資料的,你呢 - 如何引入日誌? - 日誌輸出格式以及輸出方式如何配置? - 程式碼中如何使用? 正文 Spring Boot在所有

淺析RxJava 1.x&2.x版本使用區別及原理(一)Observable、Flowable等基本元素原始碼解析

RxJava開源框架的風靡程度在Github上無需多言,它帶來的響應式程式設計模式和執行緒隨意切換、巢狀請求、背壓等功能給了開發者耳目一新的體驗,更是成為了大多數APP中常用的RxJava+Okhttp/Retrofit+MVP/MVVM/Clean黃金組合中的

Redis(七)set/sadd/sismember/sinter/sdiffstore 命令原始碼解析

  上兩篇我們講了hash和list資料型別相關的主要實現方法,同時加上前面對框架服務和string相關的功能介紹,已揭開了大部分redis的實用面紗。   現在還剩下兩種資料型別: set, zset.   本篇咱們繼續來看redis中的資料型別的實現: set 相關操作實現。     研究過jd