@RereshScope刷新的原理詳解
在配合配置中心修改配置讓?xiě)?yīng)用自動(dòng)刷新配置時(shí),我們要在需要感知配置變化的bean上面加上@RereshScope
。如果我們不加上這注解,那么有可能無(wú)法完成配置自動(dòng)刷新。
一、入口
可以看到@RereshScope
是@Scope("refresh")
(bean的作用域)的派生注解并指定了作用域?yàn)?code>refresh并在默認(rèn)情況下proxyMode= ScopedProxyMode.TARGET_CLASS
即使用CGLIB生成代理對(duì)象。
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Scope("refresh") @Documented public @interface RefreshScope { ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; }
ScopedProxyMode
ScopedProxyMode
表示作用域的代理模式,共有以下四個(gè)值:
DEFAULT
:默認(rèn)noNO
:不使用代理INTERFACES
:使用JDK動(dòng)態(tài)代理TARGET_CLASS
:使用CGLIB動(dòng)態(tài)代理
public enum ScopedProxyMode { /** * Default typically equals {@link #NO}, unless a different default * has been configured at the component-scan instruction level. */ DEFAULT, /** * Do not create a scoped proxy. */ NO, /** * Create a JDK dynamic proxy implementing <i>all</i> interfaces exposed by * the class of the target object. */ INTERFACES, /** * Create a class-based proxy (uses CGLIB). */ TARGET_CLASS; }
二、配置類解析
在上文刷新時(shí)會(huì)執(zhí)行BeanFacotryPostProcessor
對(duì)beanDefinition
修改和增加,其中配置類解析、類掃描的工作就是在其中執(zhí)行,而對(duì)于一個(gè)ScopedProxyMode.NO
它會(huì)解析成一個(gè)ScopedProxyFactoryBean
//ConfigurationClassBeanDefinitionReader配置類解析代碼片段 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); //如果需要生產(chǎn)代理則創(chuàng)建一個(gè)ScopedProxy的BeanDefinition static BeanDefinitionHolder applyScopedProxyMode( ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) { ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode(); if (scopedProxyMode.equals(ScopedProxyMode.NO)) { return definition; } boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS); return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass); } //createScopedProxy 代碼片段 可以看到BeanDefinition是ScopedProxyFactoryBean RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
ScopedProxyFactoryBean-生成代理對(duì)象
FactoryBean
在注入時(shí)返回的是getObject()
所返回的對(duì)象,在這里就是返回的就是proxy
。ScopedProxyFactoryBean
實(shí)現(xiàn)了BeanFactoryAware
那么在這個(gè)bean初始化中會(huì)調(diào)用setBeanFactory()
方法,而在這個(gè)方法中,為它創(chuàng)建一個(gè)CGLIB
代理對(duì)象作為getObject()
的返回值,并使用ScopedObject
來(lái)代替被代理對(duì)象。而在ScopedObject
默認(rèn)實(shí)現(xiàn)中每次都是從BeanFactory
中獲取(重點(diǎn))。
@Override public Object getObject() { if (this.proxy == null) { throw new FactoryBeanNotInitializedException(); } //返回代理對(duì)象 return this.proxy; } @Override public void setBeanFactory(BeanFactory beanFactory) { //...省略其他代碼 // Add an introduction that implements only the methods on ScopedObject. //增加一個(gè)攔截使用ScopedObject來(lái)被代理對(duì)象調(diào)用方法 ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName()); //委托ScopedObject去執(zhí)行 pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject)); // Add the AopInfrastructureBean marker to indicate that the scoped proxy // itself is not subject to auto-proxying! Only its target bean is. AOP時(shí)復(fù)用這個(gè)代理對(duì)象 pf.addInterface(AopInfrastructureBean.class); //創(chuàng)建代理對(duì)象 this.proxy = pf.getProxy(cbf.getBeanClassLoader()); }
ScopedObject-從容器中獲取代理目標(biāo)
作用域?qū)ο蟮?code>AOP引入的接口??梢詫?code>ScopedProxyFactoryBean創(chuàng)建的對(duì)象強(qiáng)制轉(zhuǎn)換到此接口,從而可以控制訪問(wèn)原始目標(biāo)對(duì)象并通過(guò)編程刪除目標(biāo)對(duì)象。在默認(rèn)實(shí)現(xiàn)中是每次方法攔截都從容器中獲取被代理的目標(biāo)對(duì)象。
public interface ScopedObject extends RawTargetAccess { //返回當(dāng)前代理對(duì)象后面的目標(biāo)對(duì)象 Object getTargetObject(); void removeFromScope(); } public class DefaultScopedObject implements ScopedObject, Serializable { //...省略字段信息和構(gòu)造器 @Override public Object getTargetObject() { //從容器中獲取 return this.beanFactory.getBean(this.targetBeanName); } @Override public void removeFromScope() { this.beanFactory.destroyScopedBean(this.targetBeanName); } }
三、作用域原理
在BeanFactory
獲取bean時(shí)(doGetBean
),如果**不是單例或者原型bean
**將交給對(duì)應(yīng)的Socpe
去bean
,而創(chuàng)建bean
方式和單例bean是一樣的。其他作用域像request
、session
等等都是屬于這一塊的擴(kuò)展:SPI+策略模式
//AbstractBeanFactory doGetBean()代碼片段 String scopeName = mbd.getScope(); //獲取對(duì)應(yīng)的scope final Scope scope = this.scopes.get(scopeName); //參數(shù)檢查省略。。。 try { //使用的對(duì)應(yīng)的Socpe去獲取bean 獲取不到則使用后面的`ObjectFactory` Object scopedInstance = scope.get(beanName, () -> { //ObjectFactory lambda表達(dá)式 怎么創(chuàng)建bean beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
RefreshScope
繼承GenericScope
每次獲取bean是從自己的緩存(ConcurrentHashMap
)中獲取。 如果緩存中bean被銷(xiāo)毀了則用objectFactory
創(chuàng)建一個(gè)。
//GenericScope 中獲取get實(shí)現(xiàn) public Object get(String name, ObjectFactory<?> objectFactory) { //從緩存中獲取 緩存的實(shí)現(xiàn)就是ConcurrentHashMap BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); this.locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { return value.getBean(); } catch (RuntimeException var5) { this.errors.put(name, var5); throw var5; } }
private static class BeanLifecycleWrapper { //當(dāng)前bean對(duì)象 private Object bean; //銷(xiāo)毀回調(diào) private Runnable callback; //bean名稱 private final String name; //bean工廠 private final ObjectFactory<?> objectFactory; //獲取 public Object getBean() { if (this.bean == null) { synchronized(this.name) { if (this.bean == null) { this.bean = this.objectFactory.getObject(); } } } return this.bean; } //銷(xiāo)毀 public void destroy() { if (this.callback != null) { synchronized(this.name) { Runnable callback = this.callback; if (callback != null) { callback.run(); } this.callback = null; //只為null this.bean = null; } } } }
四、配置刷新
當(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
方法
public synchronized Set<String> refresh() { //刷新環(huán)境 Set<String> keys = this.refreshEnvironment(); //刷新bean 其實(shí)就是銷(xiāo)毀refreshScope中緩存的bean this.scope.refreshAll(); return keys; } //RefreshScope刷新 public void refreshAll() { super.destroy(); this.context.publishEvent(new RefreshScopeRefreshedEvent()); }
五、總結(jié)
@RereshScope
會(huì)為這個(gè)標(biāo)記的bean生成ScopedProxyFactoryBean
,ScopedProxyFactoryBean
生成一個(gè)代理對(duì)象使每次方法調(diào)用的目標(biāo)對(duì)象都從容器中獲取,而獲取refresh
作用域的Bean是RefreshScope
緩存中獲取。當(dāng)配置中心在觸發(fā)刷新時(shí)RefreshScope
會(huì)刪除Socpe
緩存的Bean,然后下次獲取時(shí)就會(huì)用新的Environment
創(chuàng)建配置修改后的Bean,這樣就達(dá)到了配置的自動(dòng)更新。
六、問(wèn)題
為什么需要生成代理對(duì)象?
因?yàn)锽ean裝配是一次性的,假設(shè)沒(méi)有代理的情況下,在另一個(gè)bean注入這個(gè)refreshBean
之后就無(wú)法改變了,就算refreshBean
銷(xiāo)毀(在緩存中置為null)并后面重新生成了,但是之前引用還是老的bean
,這也是為什么沒(méi)有加@RefreshScope
注解而導(dǎo)致配置自動(dòng)刷新失效了。
到此這篇關(guān)于@RereshScope刷新的原理詳解的文章就介紹到這了,更多相關(guān)@RereshScope刷新內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決jasperreport導(dǎo)出的pdf每頁(yè)顯示的記錄太少問(wèn)題
這篇文章主要介紹了解決jasperreport導(dǎo)出的pdf每頁(yè)顯示的記錄太少問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06java如何實(shí)現(xiàn)項(xiàng)目啟動(dòng)時(shí)執(zhí)行指定方法
這篇文章主要為大家詳細(xì)介紹了java項(xiàng)目如何啟動(dòng)時(shí)執(zhí)行指定方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Mybatis配置之typeAlias標(biāo)簽的用法
這篇文章主要介紹了Mybatis配置之typeAlias標(biāo)簽的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07SpringBoot整合ElasticSearch實(shí)踐
本篇文章主要介紹了SpringBoot整合ElasticSearch實(shí)踐,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05以Java代碼為例講解設(shè)計(jì)模式中的簡(jiǎn)單工廠模式
簡(jiǎn)單來(lái)說(shuō),工廠模式就是按照需求來(lái)返回一個(gè)類型的對(duì)象,使用工廠模式的意義就是,如果對(duì)象的實(shí)例化與代碼依賴太大的話,不方便進(jìn)行擴(kuò)展和維護(hù),使用工廠的目的就是使對(duì)象的實(shí)例化與主程序代碼就行解耦.來(lái)具體看一下:2016-05-05Java Swing JFrame窗口的實(shí)現(xiàn)
這篇文章主要介紹了Java Swing JFrame窗口的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12springboot 1.5.2 集成kafka的簡(jiǎn)單例子
本篇文章主要介紹了springboot 1.5.2 集成kafka的簡(jiǎn)單例子 ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11