SpringBoot @Scope與@RefreshScope注解使用詳解
前言
在SpringIOC中,我們熟知的BeanScope有單例(singleton)、原型(prototype), Bean的Scope影響了Bean的管理方式,例如創(chuàng)建Scope=singleton的Bean時,IOC會保存實例在一個Map中,保證這個Bean在一個IOC上下文有且僅有一個實例。SpringCloud新增了一個refresh范圍的scope,同樣用了一種獨特的方式改變了Bean的管理方式,使得其可以通過外部化配置(.properties)的刷新,在應用不需要重啟的情況下熱加載新的外部化配置的值。
那么這個scope是如何做到熱加載的呢?RefreshScope主要做了以下動作:
單獨管理Bean生命周期創(chuàng)建Bean的時候如果是RefreshScope就緩存在一個專門管理的ScopeMap中,這樣就可以管理Scope是Refresh的Bean的生命周期了重新創(chuàng)建Bean外部化配置刷新之后,會觸發(fā)一個動作,這個動作將上面的ScopeMap中的Bean清空,這樣,這些Bean就會重新被IOC容器創(chuàng)建一次,使用最新的外部化配置的值注入類中,達到熱加載新值的效果下面我們深入源碼,來驗證我們上述的講法。
@Scope注解
Spring管理的Bean默認是單例的
@Scope (“prototype”) 通過注解可以實現(xiàn)多個實例的解決
Spring定義了多種作用域,可以基于這些作用域創(chuàng)建bean,包括:
單例( singleton):在整個應用中,只創(chuàng)建bean的一個實例。也就是單例
原型(prototype):每次注入或者通過Spring應用上下文獲取的時候:getBean,都會創(chuàng)建一個新的bean實例。多例,每次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是硬編碼單獨處理的
除了單例和原型Bean,其他Scope是由Scope對象處理的
具體創(chuàng)建Bean的過程都是由IOC做的,只不過Bean的獲取是通過Scope對象
通過scopeName獲取對應的scope實例
@RefreshScope刷新bean
這里scope.get獲取的Scope對象為RefreshScope,可以看到,創(chuàng)建Bean還是由IOC來做(createBean方法),但是獲取Bean,都由RefreshScope對象的get方法去獲取,其get方法在父類GenericScope中實現(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中實現(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; // 這里進入上面的 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對象其實就是一個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進去 public Object put(String name, Object value) { // result若不等于null,表示緩存存在了,不會進行put操作 Object result = this.cache.putIfAbsent(name, value); if (result != null) { // 直接返回舊對象 return result; } // put成功,返回新對象 return value; } }
這里就是將Bean包裝成一個對象,緩存在一個Map中,下次如果再GetBean,還是那個舊的BeanWrapper?;氐絊cope的get方法,接下來就是調用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變量即為實際Bean,如果第一次get肯定為空,就會調用BeanFactory的createBean方法創(chuàng)建Bean,創(chuàng)建出來之后就會一直保存下來。
由此可見,RefreshScope管理了Scope=Refresh的Bean的生命周期。
重新創(chuàng)建RefreshBean
當配置中心刷新配置之后,有兩種方式可以動態(tài)刷新Bean的配置變量值,(SpringCloud-Bus還是Nacos差不多都是這么實現(xiàn)的):
向上下文發(fā)布一個RefreshEvent事件
Http訪問/refresh這個EndPoint
不管是什么方式,最終都會調用ContextRefresher這個類的refresh方法,那么我們由此為入口來分析一下,熱加載配置的原理:
// 這就是我們上面一直分析的Scope對象(實際上可以看作一個保存refreshBean的Map) private RefreshScope scope; public synchronized Set<String> refresh() { // 更新上下文中Environment外部化配置值 Set<String> keys = refreshEnvironment(); // 調用scope對象的refreshAll方法 this.scope.refreshAll(); return keys; }
我們一般是使用@Value、@ConfigurationProperties去獲取配置變量值,其底層在IOC中則是通過上下文的Environment對象去獲取property值,然后依賴注入利用反射Set到Bean對象中去的。
那么如果我們更新Environment里的Property值,然后重新創(chuàng)建一次RefreshBean,再進行一次上述的依賴注入,是不是就能完成配置熱加載了呢?@Value的變量值就可以加載為最新的了。
這里說的刷新Environment對象并重新依賴注入則為上述兩個方法做的事情:
Set keys = refreshEnvironment();
this.scope.refreshAll();
刷新Environment對象
刷新環(huán)境遍歷指的的是將配置替換到當前的Environment,后面如果再根據(jù)配置創(chuàng)建對象就會使用新的配置設置屬性。
例如org.springframework.cloud.endpoint.event.RefreshEventListener進行將配置文件刷新進入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(); }
可以看到,這里歸根結底就是SpringBoot啟動上下文那種方法,新做了一個Spring上下文,因為Spring啟動后會對上下文中的Environment進行初始化,獲取最新配置,所以這里利用Spring的啟動,達到了獲取最新的Environment對象的目的。然后去替換舊的上下文中的Environment對象中的配置值即可。
重新創(chuàng)建RefreshBean
經(jīng)過上述刷新Environment對象的動作,此時上下文中的配置值已經(jīng)是最新的了。思路回到ContextRefresher的refresh方法,接下來會調用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é)關于緩存的討論嗎,cache變量是一個Map保存著RefreshBean實例,這里直接就將Map清空了。
思路回到BeanFactory的doGetBean的流程中,從IOC容器中獲取RefreshBean是交給RefreshScope的get方法做的:
public Object get(String name, ObjectFactory<?> objectFactory) { // 由于剛剛清空了緩存Map,這里就會put一個新的BeanLifecycleWrapper實例 BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); this.locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { // 在這里是新的BeanLifecycleWrapper實例調用getBean方法 return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } }
public Object getBean() { // 由于是新的BeanLifecycleWrapper實例,這里一定為null if (this.bean == null) { synchronized (this.name) { if (this.bean == null) { // 調用IOC容器的createBean,再創(chuàng)建一個Bean出來 this.bean = this.objectFactory.getObject(); } } } return this.bean; }
可以看到,此時RefreshBean被IOC容器重新創(chuàng)建一個出來了,經(jīng)過IOC的依賴注入功能,@Value的就是一個新的配置值了。到這里熱加載功能實現(xiàn)基本結束。
根據(jù)以上分析,我們可以看出只要每次我們都從IOC容器中getBean,那么拿到的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實例對象通過ScopedProxyFactoryBean創(chuàng)建,其中通過AOP使其實現(xiàn)ScopedObject接口,這里不再展開
每次使用@RefreshScope的bean的get方法時都會重新通過this.beanFactory.getBean(this.targetBeanName);
如果被清空了的話,那么會重新創(chuàng)建bean會使用,刷新后的environment的配置注入屬性,實現(xiàn)動態(tài)刷新。
到此這篇關于SpringBoot @Scope與@RefreshScope注解使用詳解的文章就介紹到這了,更多相關SpringBoot @Scope與@RefreshScope內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot升級3.2報錯Invalid value type for
這篇文章給大家介紹了SpringBoot升級3.2報錯Invalid value type for attribute ‘factoryBeanObjectType‘: java.lang.String的解決方案,文中有詳細的原因分析,需要的朋友可以參考下2023-12-12詳解SpringBoot之訪問靜態(tài)資源(webapp...)
這篇文章主要介紹了詳解SpringBoot之訪問靜態(tài)資源(webapp...),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-09-09Java并發(fā)工具類CountDownLatch CyclicBarrier使用詳解
這篇文章主要為大家介紹了Java并發(fā)工具類CountDownLatch CyclicBarrier使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06Jetty啟動項目中引用json-lib相關類庫報錯ClassNotFound的解決方案
今天小編就為大家分享一篇關于Jetty啟動項目中引用json-lib相關類庫報錯ClassNotFound的解決方案,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12SpringBoot項目部署時application.yml文件的加載優(yōu)先級和啟動腳本問題
Spring Boot在啟動時會根據(jù)一定的優(yōu)先級順序加載配置文件,優(yōu)先級從高到低依次是:命令行參數(shù)、Jar包外部config目錄下的配置文件、Jar包同級目錄下的配置文件、classpath下的/config目錄、classpath根路徑2024-09-09