SpringBoot @Scope與@RefreshScope注解使用詳解
前言
在SpringIOC中,我們熟知的BeanScope有單例(singleton)、原型(prototype), Bean的Scope影響了Bean的管理方式,例如創(chuàng)建Scope=singleton的Bean時,IOC會保存實(shí)例在一個Map中,保證這個Bean在一個IOC上下文有且僅有一個實(shí)例。SpringCloud新增了一個refresh范圍的scope,同樣用了一種獨(dú)特的方式改變了Bean的管理方式,使得其可以通過外部化配置(.properties)的刷新,在應(yīng)用不需要重啟的情況下熱加載新的外部化配置的值。
那么這個scope是如何做到熱加載的呢?RefreshScope主要做了以下動作:
單獨(dú)管理Bean生命周期創(chuàng)建Bean的時候如果是RefreshScope就緩存在一個專門管理的ScopeMap中,這樣就可以管理Scope是Refresh的Bean的生命周期了重新創(chuàng)建Bean外部化配置刷新之后,會觸發(fā)一個動作,這個動作將上面的ScopeMap中的Bean清空,這樣,這些Bean就會重新被IOC容器創(chuàng)建一次,使用最新的外部化配置的值注入類中,達(dá)到熱加載新值的效果下面我們深入源碼,來驗(yàn)證我們上述的講法。
@Scope注解
Spring管理的Bean默認(rèn)是單例的
@Scope (“prototype”) 通過注解可以實(shí)現(xiàn)多個實(shí)例的解決
Spring定義了多種作用域,可以基于這些作用域創(chuàng)建bean,包括:
單例( singleton):在整個應(yīng)用中,只創(chuàng)建bean的一個實(shí)例。也就是單例
原型(prototype):每次注入或者通過Spring應(yīng)用上下文獲取的時候:getBean,都會創(chuàng)建一個新的bean實(shí)例。多例,每次getBean的時候都會創(chuàng)建新的對象
request表示請求,即在一次http請求中,被注解的Bean都是同一個Bean,不同的請求是不同的Bean;
session表示會話,即在同一個會話中,被注解的Bean都是使用的同一個Bean,不同的會話使用不同的Bean。
創(chuàng)建一個Bean的時候,會去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對象
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
// 讓Scope對象去管理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對象處理的
具體創(chuàng)建Bean的過程都是由IOC做的,只不過Bean的獲取是通過Scope對象
通過scopeName獲取對應(yīng)的scope實(shí)例
@RefreshScope刷新bean
這里scope.get獲取的Scope對象為RefreshScope,可以看到,創(chuàng)建Bean還是由IOC來做(createBean方法),但是獲取Bean,都由RefreshScope對象的get方法去獲取,其get方法在父類GenericScope中實(shí)現(xiàn):
public Object get(String name, ObjectFactory<?> objectFactory) {
// 將Bean緩存下來
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
// 創(chuàng)建Bean,只會創(chuàng)建一次,后面直接返回創(chuàng)建好的Bean
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}首先這里將Bean包裝起來緩存下來
這里scope.get獲取的Scope對象為RefreshScope,可以看到,創(chuàng)建Bean還是由IOC來做(createBean方法),但是獲取Bean,都由RefreshScope對象的get方法去獲取,其get方法在父類GenericScope中實(shí)現(xiàn)。bean的生命周期也由GenericScope控制
public Object get(String name, ObjectFactory<?> objectFactory) {
// 將Bean緩存下來
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
// 創(chuàng)建Bean,只會創(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對象其實(shí)就是一個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);
}
// 如果不存在,才會put進(jìn)去
public Object put(String name, Object value) {
// result若不等于null,表示緩存存在了,不會進(jìn)行put操作
Object result = this.cache.putIfAbsent(name, value);
if (result != null) {
// 直接返回舊對象
return result;
}
// put成功,返回新對象
return value;
}
}這里就是將Bean包裝成一個對象,緩存在一個Map中,下次如果再GetBean,還是那個舊的BeanWrapper?;氐絊cope的get方法,接下來就是調(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;
}可以看出來,BeanWrapper中的bean變量即為實(shí)際Bean,如果第一次get肯定為空,就會調(diào)用BeanFactory的createBean方法創(chuàng)建Bean,創(chuàng)建出來之后就會一直保存下來。
由此可見,RefreshScope管理了Scope=Refresh的Bean的生命周期。
重新創(chuàng)建RefreshBean
當(dāng)配置中心刷新配置之后,有兩種方式可以動態(tài)刷新Bean的配置變量值,(SpringCloud-Bus還是Nacos差不多都是這么實(shí)現(xiàn)的):
向上下文發(fā)布一個RefreshEvent事件
Http訪問/refresh這個EndPoint
不管是什么方式,最終都會調(diào)用ContextRefresher這個類的refresh方法,那么我們由此為入口來分析一下,熱加載配置的原理:
// 這就是我們上面一直分析的Scope對象(實(shí)際上可以看作一個保存refreshBean的Map)
private RefreshScope scope;
public synchronized Set<String> refresh() {
// 更新上下文中Environment外部化配置值
Set<String> keys = refreshEnvironment();
// 調(diào)用scope對象的refreshAll方法
this.scope.refreshAll();
return keys;
}我們一般是使用@Value、@ConfigurationProperties去獲取配置變量值,其底層在IOC中則是通過上下文的Environment對象去獲取property值,然后依賴注入利用反射Set到Bean對象中去的。
那么如果我們更新Environment里的Property值,然后重新創(chuàng)建一次RefreshBean,再進(jìn)行一次上述的依賴注入,是不是就能完成配置熱加載了呢?@Value的變量值就可以加載為最新的了。
這里說的刷新Environment對象并重新依賴注入則為上述兩個方法做的事情:
Set keys = refreshEnvironment();
this.scope.refreshAll();
刷新Environment對象
刷新環(huán)境遍歷指的的是將配置替換到當(dāng)前的Environment,后面如果再根據(jù)配置創(chuàng)建對象就會使用新的配置設(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啟動上下文那種方法,新做了一個Spring上下文,因?yàn)镾pring啟動后會對上下文中的Environment進(jìn)行初始化,獲取最新配置,所以這里利用Spring的啟動,達(dá)到了獲取最新的Environment對象的目的。然后去替換舊的上下文中的Environment對象中的配置值即可。
重新創(chuàng)建RefreshBean
經(jīng)過上述刷新Environment對象的動作,此時上下文中的配置值已經(jīng)是最新的了。思路回到ContextRefresher的refresh方法,接下來會調(diào)用Scope對象的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變量是一個Map保存著RefreshBean實(shí)例,這里直接就將Map清空了。
思路回到BeanFactory的doGetBean的流程中,從IOC容器中獲取RefreshBean是交給RefreshScope的get方法做的:
public Object get(String name, ObjectFactory<?> objectFactory) {
// 由于剛剛清空了緩存Map,這里就會put一個新的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)建一個Bean出來
this.bean = this.objectFactory.getObject();
}
}
}
return this.bean;
}可以看到,此時RefreshBean被IOC容器重新創(chuàng)建一個出來了,經(jīng)過IOC的依賴注入功能,@Value的就是一個新的配置值了。到這里熱加載功能實(shí)現(xiàn)基本結(jié)束。
根據(jù)以上分析,我們可以看出只要每次我們都從IOC容器中g(shù)etBean,那么拿到的RefreshBean一定是帶有最新配置值的Bean。
@RefreshScope代理對象
@Scope 的注冊 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í)例對象通過ScopedProxyFactoryBean創(chuàng)建,其中通過AOP使其實(shí)現(xiàn)ScopedObject接口,這里不再展開

每次使用@RefreshScope的bean的get方法時都會重新通過this.beanFactory.getBean(this.targetBeanName);
如果被清空了的話,那么會重新創(chuàng)建bean會使用,刷新后的environment的配置注入屬性,實(shí)現(xiàn)動態(tài)刷新。
到此這篇關(guān)于SpringBoot @Scope與@RefreshScope注解使用詳解的文章就介紹到這了,更多相關(guān)SpringBoot @Scope與@RefreshScope內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中Object.equals和String.equals的區(qū)別詳解
這篇文章主要給大家介紹了Java中Object.equals和String.equals的區(qū)別,文中通過一個小示例讓大家輕松的明白這兩者的區(qū)別,對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。2017-04-04
Spring?Security使用數(shù)據(jù)庫登錄認(rèn)證授權(quán)
本文主要介紹了Spring?Security使用數(shù)據(jù)庫登錄認(rèn)證授權(quán),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01
Springboot如何使用OSHI獲取和操作系統(tǒng)和硬件信息
這篇文章主要介紹了Springboot如何使用OSHI獲取和操作系統(tǒng)和硬件信息問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10
mybatis實(shí)現(xiàn)對數(shù)據(jù)的增刪查改實(shí)例詳解
這篇文章主要介紹了mybatis實(shí)現(xiàn)對數(shù)據(jù)的增刪查改實(shí)例詳解的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-07-07

