SpringBoot @Scope與@RefreshScope注解使用詳解
前言
在SpringIOC中,我們熟知的BeanScope有單例(singleton)、原型(prototype), Bean的Scope影響了Bean的管理方式,例如創(chuàng)建Scope=singleton的Bean時(shí),IOC會(huì)保存實(shí)例在一個(gè)Map中,保證這個(gè)Bean在一個(gè)IOC上下文有且僅有一個(gè)實(shí)例。SpringCloud新增了一個(gè)refresh范圍的scope,同樣用了一種獨(dú)特的方式改變了Bean的管理方式,使得其可以通過(guò)外部化配置(.properties)的刷新,在應(yīng)用不需要重啟的情況下熱加載新的外部化配置的值。
那么這個(gè)scope是如何做到熱加載的呢?RefreshScope主要做了以下動(dòng)作:
單獨(dú)管理Bean生命周期創(chuàng)建Bean的時(shí)候如果是RefreshScope就緩存在一個(gè)專門(mén)管理的ScopeMap中,這樣就可以管理Scope是Refresh的Bean的生命周期了重新創(chuàng)建Bean外部化配置刷新之后,會(huì)觸發(fā)一個(gè)動(dòng)作,這個(gè)動(dòng)作將上面的ScopeMap中的Bean清空,這樣,這些Bean就會(huì)重新被IOC容器創(chuàng)建一次,使用最新的外部化配置的值注入類中,達(dá)到熱加載新值的效果下面我們深入源碼,來(lái)驗(yàn)證我們上述的講法。
@Scope注解
Spring管理的Bean默認(rèn)是單例的
@Scope (“prototype”) 通過(guò)注解可以實(shí)現(xiàn)多個(gè)實(shí)例的解決
Spring定義了多種作用域,可以基于這些作用域創(chuàng)建bean,包括:
單例( singleton):在整個(gè)應(yīng)用中,只創(chuàng)建bean的一個(gè)實(shí)例。也就是單例
原型(prototype):每次注入或者通過(guò)Spring應(yīng)用上下文獲取的時(shí)候:getBean,都會(huì)創(chuàng)建一個(gè)新的bean實(shí)例。多例,每次getBean的時(shí)候都會(huì)創(chuàng)建新的對(duì)象
request表示請(qǐng)求,即在一次http請(qǐng)求中,被注解的Bean都是同一個(gè)Bean,不同的請(qǐng)求是不同的Bean;
session表示會(huì)話,即在同一個(gè)會(huì)話中,被注解的Bean都是使用的同一個(gè)Bean,不同的會(huì)話使用不同的Bean。
創(chuàng)建一個(gè)Bean的時(shí)候,會(huì)去BeanFactory的doGetBean方法創(chuàng)建Bean,不同scope有不同的創(chuàng)建方式:
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { //.... // Create bean instance. // 單例Bean的創(chuàng)建 if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } //... }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } // 原型Bean的創(chuàng)建 else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. // ... try { prototypeInstance = createBean(beanName, mbd, args); } //... bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { // 由上面的RefreshScope注解可以知道,這里scopeName=refresh String scopeName = mbd.getScope(); // 獲取Refresh的Scope對(duì)象 final Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { // 讓Scope對(duì)象去管理Bean Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } //... } } //... } //... }
這里可以看到幾件事情:
單例和原型scope的Bean是硬編碼單獨(dú)處理的
除了單例和原型Bean,其他Scope是由Scope對(duì)象處理的
具體創(chuàng)建Bean的過(guò)程都是由IOC做的,只不過(guò)Bean的獲取是通過(guò)Scope對(duì)象
通過(guò)scopeName獲取對(duì)應(yīng)的scope實(shí)例
@RefreshScope刷新bean
這里scope.get獲取的Scope對(duì)象為RefreshScope,可以看到,創(chuàng)建Bean還是由IOC來(lái)做(createBean方法),但是獲取Bean,都由RefreshScope對(duì)象的get方法去獲取,其get方法在父類GenericScope中實(shí)現(xiàn):
public Object get(String name, ObjectFactory<?> objectFactory) { // 將Bean緩存下來(lái) BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); this.locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { // 創(chuàng)建Bean,只會(huì)創(chuàng)建一次,后面直接返回創(chuàng)建好的Bean return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } }
首先這里將Bean包裝起來(lái)緩存下來(lái)
這里scope.get獲取的Scope對(duì)象為RefreshScope,可以看到,創(chuàng)建Bean還是由IOC來(lái)做(createBean方法),但是獲取Bean,都由RefreshScope對(duì)象的get方法去獲取,其get方法在父類GenericScope中實(shí)現(xiàn)。bean的生命周期也由GenericScope控制
public Object get(String name, ObjectFactory<?> objectFactory) { // 將Bean緩存下來(lái) BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); this.locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { // 創(chuàng)建Bean,只會(huì)創(chuàng)建一次,后面直接返回創(chuàng)建好的Bean return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } }
private final ScopeCache cache; // 這里進(jìn)入上面的 BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); public BeanLifecycleWrapper put(String name, BeanLifecycleWrapper value) { return (BeanLifecycleWrapper) this.cache.put(name, value); }
這里的ScopeCache對(duì)象其實(shí)就是一個(gè)HashMap:
public class StandardScopeCache implements ScopeCache { private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<String, Object>(); //... public Object get(String name) { return this.cache.get(name); } // 如果不存在,才會(huì)put進(jìn)去 public Object put(String name, Object value) { // result若不等于null,表示緩存存在了,不會(huì)進(jìn)行put操作 Object result = this.cache.putIfAbsent(name, value); if (result != null) { // 直接返回舊對(duì)象 return result; } // put成功,返回新對(duì)象 return value; } }
這里就是將Bean包裝成一個(gè)對(duì)象,緩存在一個(gè)Map中,下次如果再GetBean,還是那個(gè)舊的BeanWrapper?;氐絊cope的get方法,接下來(lái)就是調(diào)用BeanWrapper的getBean方法:
private Object bean; public Object getBean() { if (this.bean == null) { synchronized (this.name) { if (this.bean == null) { this.bean = this.objectFactory.getObject(); } } } return this.bean; }
可以看出來(lái),BeanWrapper中的bean變量即為實(shí)際Bean,如果第一次get肯定為空,就會(huì)調(diào)用BeanFactory的createBean方法創(chuàng)建Bean,創(chuàng)建出來(lái)之后就會(huì)一直保存下來(lái)。
由此可見(jiàn),RefreshScope管理了Scope=Refresh的Bean的生命周期。
重新創(chuàng)建RefreshBean
當(dāng)配置中心刷新配置之后,有兩種方式可以動(dòng)態(tài)刷新Bean的配置變量值,(SpringCloud-Bus還是Nacos差不多都是這么實(shí)現(xiàn)的):
向上下文發(fā)布一個(gè)RefreshEvent事件
Http訪問(wèn)/refresh這個(gè)EndPoint
不管是什么方式,最終都會(huì)調(diào)用ContextRefresher這個(gè)類的refresh方法,那么我們由此為入口來(lái)分析一下,熱加載配置的原理:
// 這就是我們上面一直分析的Scope對(duì)象(實(shí)際上可以看作一個(gè)保存refreshBean的Map) private RefreshScope scope; public synchronized Set<String> refresh() { // 更新上下文中Environment外部化配置值 Set<String> keys = refreshEnvironment(); // 調(diào)用scope對(duì)象的refreshAll方法 this.scope.refreshAll(); return keys; }
我們一般是使用@Value、@ConfigurationProperties去獲取配置變量值,其底層在IOC中則是通過(guò)上下文的Environment對(duì)象去獲取property值,然后依賴注入利用反射Set到Bean對(duì)象中去的。
那么如果我們更新Environment里的Property值,然后重新創(chuàng)建一次RefreshBean,再進(jìn)行一次上述的依賴注入,是不是就能完成配置熱加載了呢?@Value的變量值就可以加載為最新的了。
這里說(shuō)的刷新Environment對(duì)象并重新依賴注入則為上述兩個(gè)方法做的事情:
Set keys = refreshEnvironment();
this.scope.refreshAll();
刷新Environment對(duì)象
刷新環(huán)境遍歷指的的是將配置替換到當(dāng)前的Environment,后面如果再根據(jù)配置創(chuàng)建對(duì)象就會(huì)使用新的配置設(shè)置屬性。
例如org.springframework.cloud.endpoint.event.RefreshEventListener進(jìn)行將配置文件刷新進(jìn)入environment中的操作。
ConfigurableApplicationContext addConfigFilesToEnvironment() { StandardEnvironment environment = copyEnvironment( this.context.getEnvironment()); SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class) .bannerMode(Mode.OFF).web(WebApplicationType.NONE) .environment(environment); // Just the listeners that affect the environment (e.g. excluding logging // listener because it has side effects) builder.application() .setListeners(Arrays.asList(new BootstrapApplicationListener(), new ConfigFileApplicationListener())); capture = builder.run(); }
可以看到,這里歸根結(jié)底就是SpringBoot啟動(dòng)上下文那種方法,新做了一個(gè)Spring上下文,因?yàn)镾pring啟動(dòng)后會(huì)對(duì)上下文中的Environment進(jìn)行初始化,獲取最新配置,所以這里利用Spring的啟動(dòng),達(dá)到了獲取最新的Environment對(duì)象的目的。然后去替換舊的上下文中的Environment對(duì)象中的配置值即可。
重新創(chuàng)建RefreshBean
經(jīng)過(guò)上述刷新Environment對(duì)象的動(dòng)作,此時(shí)上下文中的配置值已經(jīng)是最新的了。思路回到ContextRefresher的refresh方法,接下來(lái)會(huì)調(diào)用Scope對(duì)象的refreshAll方法:
public void refreshAll() { // 銷毀Bean super.destroy(); this.context.publishEvent(new RefreshScopeRefreshedEvent()); } public void destroy() { List<Throwable> errors = new ArrayList<Throwable>(); // 緩存清空 Collection<BeanLifecycleWrapper> wrappers = this.cache.clear(); // ... }
還記得上面的管理RefreshBean生命周期那一節(jié)關(guān)于緩存的討論嗎,cache變量是一個(gè)Map保存著RefreshBean實(shí)例,這里直接就將Map清空了。
思路回到BeanFactory的doGetBean的流程中,從IOC容器中獲取RefreshBean是交給RefreshScope的get方法做的:
public Object get(String name, ObjectFactory<?> objectFactory) { // 由于剛剛清空了緩存Map,這里就會(huì)put一個(gè)新的BeanLifecycleWrapper實(shí)例 BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); this.locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { // 在這里是新的BeanLifecycleWrapper實(shí)例調(diào)用getBean方法 return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } }
public Object getBean() { // 由于是新的BeanLifecycleWrapper實(shí)例,這里一定為null if (this.bean == null) { synchronized (this.name) { if (this.bean == null) { // 調(diào)用IOC容器的createBean,再創(chuàng)建一個(gè)Bean出來(lái) this.bean = this.objectFactory.getObject(); } } } return this.bean; }
可以看到,此時(shí)RefreshBean被IOC容器重新創(chuàng)建一個(gè)出來(lái)了,經(jīng)過(guò)IOC的依賴注入功能,@Value的就是一個(gè)新的配置值了。到這里熱加載功能實(shí)現(xiàn)基本結(jié)束。
根據(jù)以上分析,我們可以看出只要每次我們都從IOC容器中g(shù)etBean,那么拿到的RefreshBean一定是帶有最新配置值的Bean。
@RefreshScope代理對(duì)象
@Scope 的注冊(cè) AnnotatedBeanDefinitionReader#registerBean
public void registerBean(...){ ... ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); abd.setScope(scopeMetadata.getScopeName()); ... definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); }
讀取@Scope元數(shù)據(jù), AnnotationScopeMetadataResolver#resolveScopeMetadata
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor( annDef.getMetadata(), Scope.class); if (attributes != null) { metadata.setScopeName(attributes.getString("value")); ScopedProxyMode proxyMode = attributes.getEnum("proxyMode"); if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) { proxyMode = this.defaultProxyMode; } metadata.setScopedProxyMode(proxyMode); } }
Scope實(shí)例對(duì)象通過(guò)ScopedProxyFactoryBean創(chuàng)建,其中通過(guò)AOP使其實(shí)現(xiàn)ScopedObject接口,這里不再展開(kāi)
每次使用@RefreshScope的bean的get方法時(shí)都會(huì)重新通過(guò)this.beanFactory.getBean(this.targetBeanName);
如果被清空了的話,那么會(huì)重新創(chuàng)建bean會(huì)使用,刷新后的environment的配置注入屬性,實(shí)現(xiàn)動(dòng)態(tài)刷新。
到此這篇關(guān)于SpringBoot @Scope與@RefreshScope注解使用詳解的文章就介紹到這了,更多相關(guān)SpringBoot @Scope與@RefreshScope內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot升級(jí)3.2報(bào)錯(cuò)Invalid value type for
這篇文章給大家介紹了SpringBoot升級(jí)3.2報(bào)錯(cuò)Invalid value type for attribute ‘factoryBeanObjectType‘: java.lang.String的解決方案,文中有詳細(xì)的原因分析,需要的朋友可以參考下2023-12-12Java實(shí)現(xiàn)字符串和輸入流的相互轉(zhuǎn)換
這篇文章主要介紹了Java實(shí)現(xiàn)字符串和輸入流的相互轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08Java用freemarker導(dǎo)出word實(shí)用示例
本篇文章主要介紹了Java用freemarker導(dǎo)出word實(shí)用示例,使用freemarker的模板來(lái)實(shí)現(xiàn)功能,有需要的可以了解一下。2016-11-11詳解SpringBoot之訪問(wèn)靜態(tài)資源(webapp...)
這篇文章主要介紹了詳解SpringBoot之訪問(wèn)靜態(tài)資源(webapp...),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Java并發(fā)工具類CountDownLatch CyclicBarrier使用詳解
這篇文章主要為大家介紹了Java并發(fā)工具類CountDownLatch CyclicBarrier使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06Spring依賴注入的兩種方式(根據(jù)實(shí)例詳解)
這篇文章主要介紹了Spring依賴注入的兩種方式(根據(jù)實(shí)例詳解),非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05Jetty啟動(dòng)項(xiàng)目中引用json-lib相關(guān)類庫(kù)報(bào)錯(cuò)ClassNotFound的解決方案
今天小編就為大家分享一篇關(guān)于Jetty啟動(dòng)項(xiàng)目中引用json-lib相關(guān)類庫(kù)報(bào)錯(cuò)ClassNotFound的解決方案,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12SpringBoot項(xiàng)目部署時(shí)application.yml文件的加載優(yōu)先級(jí)和啟動(dòng)腳本問(wèn)題
Spring Boot在啟動(dòng)時(shí)會(huì)根據(jù)一定的優(yōu)先級(jí)順序加載配置文件,優(yōu)先級(jí)從高到低依次是:命令行參數(shù)、Jar包外部config目錄下的配置文件、Jar包同級(jí)目錄下的配置文件、classpath下的/config目錄、classpath根路徑2024-09-09