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è)專門管理的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)文章
Java中Object.equals和String.equals的區(qū)別詳解
這篇文章主要給大家介紹了Java中Object.equals和String.equals的區(qū)別,文中通過(guò)一個(gè)小示例讓大家輕松的明白這兩者的區(qū)別,對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-04-04
java定長(zhǎng)隊(duì)列的實(shí)現(xiàn)示例
定長(zhǎng)隊(duì)列是一種有限容量的隊(duì)列,對(duì)于某些應(yīng)用場(chǎng)景非常有用,本文主要介紹了java定長(zhǎng)隊(duì)列的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02
Spring?Security使用數(shù)據(jù)庫(kù)登錄認(rèn)證授權(quán)
本文主要介紹了Spring?Security使用數(shù)據(jù)庫(kù)登錄認(rèn)證授權(quán),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
IDEA報(bào)錯(cuò):無(wú)效的源發(fā)行版解決方案
很多小伙伴在刷新maven的時(shí)候總會(huì)報(bào) Error:java:無(wú)效的源發(fā)行版,下面這篇文章主要給大家介紹了關(guān)于IDEA報(bào)錯(cuò):無(wú)效的源發(fā)行版的解決方案,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
Java實(shí)現(xiàn)簡(jiǎn)單的萬(wàn)年歷
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)單的萬(wàn)年歷,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-04
Springboot如何使用OSHI獲取和操作系統(tǒng)和硬件信息
這篇文章主要介紹了Springboot如何使用OSHI獲取和操作系統(tǒng)和硬件信息問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
mybatis實(shí)現(xiàn)對(duì)數(shù)據(jù)的增刪查改實(shí)例詳解
這篇文章主要介紹了mybatis實(shí)現(xiàn)對(duì)數(shù)據(jù)的增刪查改實(shí)例詳解的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07

