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格式如下:
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作用域 (Scope:Request,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