@RereshScope刷新的原理詳解
在配合配置中心修改配置讓應(yīng)用自動刷新配置時(shí),我們要在需要感知配置變化的bean上面加上@RereshScope。如果我們不加上這注解,那么有可能無法完成配置自動刷新。
一、入口
可以看到@RereshScope是@Scope("refresh")(bean的作用域)的派生注解并指定了作用域?yàn)?code>refresh并在默認(rèn)情況下proxyMode= ScopedProxyMode.TARGET_CLASS即使用CGLIB生成代理對象。
@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動態(tài)代理TARGET_CLASS:使用CGLIB動態(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í)會執(zhí)行BeanFacotryPostProcessor對beanDefinition修改和增加,其中配置類解析、類掃描的工作就是在其中執(zhí)行,而對于一個(gè)ScopedProxyMode.NO它會解析成一個(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-生成代理對象
FactoryBean在注入時(shí)返回的是getObject()所返回的對象,在這里就是返回的就是proxy。ScopedProxyFactoryBean實(shí)現(xiàn)了BeanFactoryAware那么在這個(gè)bean初始化中會調(diào)用setBeanFactory()方法,而在這個(gè)方法中,為它創(chuàng)建一個(gè)CGLIB代理對象作為getObject()的返回值,并使用ScopedObject來代替被代理對象。而在ScopedObject默認(rèn)實(shí)現(xiàn)中每次都是從BeanFactory中獲取(重點(diǎn))。
@Override
public Object getObject() {
if (this.proxy == null) {
throw new FactoryBeanNotInitializedException();
}
//返回代理對象
return this.proxy;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
//...省略其他代碼
// Add an introduction that implements only the methods on ScopedObject.
//增加一個(gè)攔截使用ScopedObject來被代理對象調(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è)代理對象
pf.addInterface(AopInfrastructureBean.class);
//創(chuàng)建代理對象
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}ScopedObject-從容器中獲取代理目標(biāo)
作用域?qū)ο蟮?code>AOP引入的接口??梢詫?code>ScopedProxyFactoryBean創(chuàng)建的對象強(qiáng)制轉(zhuǎn)換到此接口,從而可以控制訪問原始目標(biāo)對象并通過編程刪除目標(biāo)對象。在默認(rèn)實(shí)現(xiàn)中是每次方法攔截都從容器中獲取被代理的目標(biāo)對象。
public interface ScopedObject extends RawTargetAccess {
//返回當(dāng)前代理對象后面的目標(biāo)對象
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**將交給對應(yīng)的Socpe去bean,而創(chuàng)建bean方式和單例bean是一樣的。其他作用域像request、session等等都是屬于這一塊的擴(kuò)展:SPI+策略模式
//AbstractBeanFactory doGetBean()代碼片段
String scopeName = mbd.getScope();
//獲取對應(yīng)的scope
final Scope scope = this.scopes.get(scopeName);
//參數(shù)檢查省略。。。
try {
//使用的對應(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被銷毀了則用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對象
private Object bean;
//銷毀回調(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;
}
//銷毀
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)配置中心刷新配置之后,有兩種方式可以動態(tài)刷新Bean的配置變量值,(SpringCloud-Bus還是Nacos差不多都是這么實(shí)現(xiàn)的):
- 向上下文發(fā)布一個(gè)
RefreshEvent事件 Http訪問/refresh這個(gè)EndPoint
不管是什么方式,最終都會調(diào)用ContextRefresher這個(gè)類的refresh方法
public synchronized Set<String> refresh() {
//刷新環(huán)境
Set<String> keys = this.refreshEnvironment();
//刷新bean 其實(shí)就是銷毀refreshScope中緩存的bean
this.scope.refreshAll();
return keys;
}
//RefreshScope刷新
public void refreshAll() {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}五、總結(jié)
@RereshScope會為這個(gè)標(biāo)記的bean生成ScopedProxyFactoryBean,ScopedProxyFactoryBean生成一個(gè)代理對象使每次方法調(diào)用的目標(biāo)對象都從容器中獲取,而獲取refresh作用域的Bean是RefreshScope緩存中獲取。當(dāng)配置中心在觸發(fā)刷新時(shí)RefreshScope會刪除Socpe緩存的Bean,然后下次獲取時(shí)就會用新的Environment創(chuàng)建配置修改后的Bean,這樣就達(dá)到了配置的自動更新。
六、問題
為什么需要生成代理對象?
因?yàn)锽ean裝配是一次性的,假設(shè)沒有代理的情況下,在另一個(gè)bean注入這個(gè)refreshBean之后就無法改變了,就算refreshBean銷毀(在緩存中置為null)并后面重新生成了,但是之前引用還是老的bean,這也是為什么沒有加@RefreshScope注解而導(dǎo)致配置自動刷新失效了。
到此這篇關(guān)于@RereshScope刷新的原理詳解的文章就介紹到這了,更多相關(guān)@RereshScope刷新內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)快速排序算法(Quicktsort)
這篇文章主要介紹了Java實(shí)現(xiàn)快速排序算法(Quicktsort),有需要的朋友可以參考一下2013-12-12
關(guān)于SpringCloud整合RabbitMQ的實(shí)例
這篇文章主要介紹了關(guān)于SpringCloud整合RabbitMQ的實(shí)例,消息隊(duì)列是指利用高效可靠的消息傳遞機(jī)制進(jìn)行與平臺無關(guān)的數(shù)據(jù)交流,并基于數(shù)據(jù)通信來進(jìn)行分布式系統(tǒng)的集成,是在消息的傳輸過程中保存消息的容器,需要的朋友可以參考下2023-07-07
Java中java.sql.SQLException異常的正確解決方法(親測有效!)
SQLException是在Java中處理數(shù)據(jù)庫操作過程中可能發(fā)生的異常,通常是由于底層數(shù)據(jù)庫操作錯(cuò)誤或違反了數(shù)據(jù)庫規(guī)則而引起的,下面這篇文章主要給大家介紹了關(guān)于Java中java.sql.SQLException異常的正確解決方法,需要的朋友可以參考下2024-01-01
Spring?Native打包本地鏡像的操作方法(無需通過Graal的maven插件buildtools)
這篇文章主要介紹了Spring?Native打包本地鏡像,無需通過Graal的maven插件buildtools,本文探索一下,如果不通過這個(gè)插件來生成鏡像,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02
詳解Spring Cloud Gateway基于服務(wù)發(fā)現(xiàn)的默認(rèn)路由規(guī)則
這篇文章主要介紹了詳解Spring Cloud Gateway基于服務(wù)發(fā)現(xiàn)的默認(rèn)路由規(guī)則,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-05-05

