一文帶你理解@RefreshScope注解實現(xiàn)動態(tài)刷新原理
注解的作用
@RefreshScope
注解是Spring Cloud中的一個注解,用來實現(xiàn)Bean中屬性的動態(tài)刷新。
/** * Convenience annotation to put a <code>@Bean</code> definition in * {@link org.springframework.cloud.context.scope.refresh.RefreshScope refresh scope}. * Beans annotated this way can be refreshed at runtime and any components that are using * them will get a new instance on the next method call, fully initialized and injected * with all dependencies. * * @author Dave Syer * */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Scope("refresh") @Documented public @interface RefreshScope { /** * @see Scope#proxyMode() * @return proxy mode */ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; }
- 上面是RefreshScope的源碼,該注解被@Scope注解使用,@Scope用來比較Spring Bean的作用域,具體使用參考相關文章。
- 注解的屬性proxyMode默認使用TARGET_CLASS作為代理。
實例
- controller中添加
@RefreshScope
- nacos配置中心中配置
- 驗證, 修改配置中心后,可以不重啟動,刷新配置
- 去掉
@RefreshScope
就不會自動刷新。
原理解析
為了實現(xiàn)動態(tài)刷新配置,主要就是想辦法達成以下兩個核心目標:
- 讓Spring容器重新加載Environment環(huán)境配置變量
- Spring Bean重新創(chuàng)建生成
@RefreshScope
主要就是基于@Scope
注解的作用域代理的基礎上進行擴展實現(xiàn)的,加了@RefreshScope
注解的類,在被Bean工廠創(chuàng)建后會加入自己的refresh scope 這個Bean緩存中,后續(xù)會優(yōu)先從Bean緩存中獲取,當配置中心發(fā)生了變更,會把變更的配置更新到spring容器的Environment中,并且同事bean緩存就會被清空,從而就會從bean工廠中創(chuàng)建bean實例了,而這次創(chuàng)建bean實例的時候就會繼續(xù)經(jīng)歷這個bean的生命周期,使得@Value屬性值能夠從Environment中獲取到最新的屬性值,這樣整個過程就達到了動態(tài)刷新配置的效果。
獲取RefreshScope注解的Bean
通過打上斷點查看堆??芍?/p>
- 因為Class被加上了
@RefreshScope
注解,那么這個BeanDefinition信息中的scope為refresh,在getBean的的時候會單獨處理邏輯。
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { // 如果scope是單例的情況, 這里不進行分析 if (mbd.isSingleton()) { ..... } // 如果scope是prototype的情況, 這里不進行分析 else if (mbd.isPrototype()) { ...... } // 如果scope是其他的情況,本例中是reresh else { String scopeName = mbd.getScope(); if (!StringUtils.hasLength(scopeName)) { throw new IllegalStateException("No scope name defined for bean '" + beanName + "'"); } // 獲取refresh scope的實現(xiàn)類RefreshScope,這個類在哪里注入,我們后面講 Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { // 這邊是獲取bean,調(diào)用的是RefreshScope中的的方法 Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new ScopeNotActiveException(beanName, scopeName, ex); } } } catch (BeansException ex) { beanCreation.tag("exception", ex.getClass().toString()); beanCreation.tag("message", String.valueOf(ex.getMessage())); cleanupAfterBeanCreationFailure(beanName); throw ex; } finally { beanCreation.end(); } } return adaptBeanInstance(name, beanInstance, requiredType); } }
- RefreshScope繼承成了GenericScope類,最終調(diào)用的的是GenericScope的get方法
public class GenericScope implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean { @Override public Object get(String name, ObjectFactory<?> objectFactory) { // 將bean添加到緩存cache中 BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); this.locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { // 調(diào)用下面的getBean方法 return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } } private static class BeanLifecycleWrapper { public Object getBean() { // 如果bean為空,則創(chuàng)建bean if (this.bean == null) { synchronized (this.name) { if (this.bean == null) { this.bean = this.objectFactory.getObject(); } } } // 否則返回之前創(chuàng)建好的bean return this.bean; } } }
小結:
從這邊的代碼中可以印證了上面的說法,創(chuàng)建后的Bean會緩存到scope的cache中,優(yōu)先從緩存中獲取,如果緩存中是null, 則重新走一遍create bean的流程。
RefeshScope Bean的創(chuàng)建
上面的在getBean的時候依賴到RefreshScope這個Bean,那么這個Bean是在什么時候加入到Spring Bean中的呢?答案就是RefreshAutoConfiguration
。
配置中心刷新后刷新Bean緩存
- 配置中心發(fā)生變化后,會收到一個
RefreshEvent
事件,RefreshEventListner
監(jiān)聽器會監(jiān)聽到這個事件。
public class RefreshEventListener implements SmartApplicationListener { ........ public void handle(RefreshEvent event) { if (this.ready.get()) { // don't handle events before app is ready log.debug("Event received " + event.getEventDesc()); // 會調(diào)用refresh方法,進行刷新 Set<String> keys = this.refresh.refresh(); log.info("Refresh keys changed: " + keys); } } } // 這個是ContextRefresher類中的刷新方法 public synchronized Set<String> refresh() { // 刷新spring的envirionment 變量配置 Set<String> keys = refreshEnvironment(); // 刷新其他scope this.scope.refreshAll(); return keys; }
- refresh方法最終調(diào)用destroy方法,清空之前緩存的bean
public class RefreshScope extends GenericScope implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered { @ManagedOperation(description = "Dispose of the current instance of all beans " + "in this scope and force a refresh on next method execution.") public void refreshAll() { // 調(diào)用父類的destroy super.destroy(); this.context.publishEvent(new RefreshScopeRefreshedEvent()); } } @Override public void destroy() { List<Throwable> errors = new ArrayList<Throwable>(); Collection<BeanLifecycleWrapper> wrappers = this.cache.clear(); for (BeanLifecycleWrapper wrapper : wrappers) { try { Lock lock = this.locks.get(wrapper.getName()).writeLock(); lock.lock(); try { // 這里主要就是把之前的bean設置為null, 就會重新走createBean的流程了 wrapper.destroy(); } finally { lock.unlock(); } } catch (RuntimeException e) { errors.add(e); } } if (!errors.isEmpty()) { throw wrapIfNecessary(errors.get(0)); } this.errors.clear(); }
總結
上面是這個RefreshScope實現(xiàn)動態(tài)刷新大致的原理,其中里面還有很多細節(jié),可能需要留給大家自己debug去深入理解。
以上就是一文帶你理解@RefreshScope注解實現(xiàn)動態(tài)刷新原理的詳細內(nèi)容,更多關于@RefreshScope注解實現(xiàn)動態(tài)刷新的資料請關注腳本之家其它相關文章!
相關文章
使用Backoff策略提高HttpClient連接管理的效率
這篇文章主要為大家介紹了Backoff策略提高HttpClient連接管理的效率使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10spring中@Transactional?注解失效的原因及解決辦法
面試中經(jīng)常會被問到事務失效的場景有哪些,本文主要介紹了spring中@Transactional?注解失效的原因及解決辦法,具有一定的參考價值,感興趣的可以了解一下2024-06-06完美解決Logback configuration error detected的問題
這篇文章主要介紹了完美解決Logback configuration error detected的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08java selenium 常見web UI 元素操作及API使用
本文主要介紹java selenium 常見web UI 元素操作,這里幫大家整理了相關資料并附示例代碼,有需要的小伙伴可以參考下2016-08-08MyBatis之傳入?yún)?shù)為list、數(shù)組、map的寫法
這篇文章主要介紹了MyBatis之傳入?yún)?shù)為list、數(shù)組、map的寫法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11