Spring應(yīng)用中使用acutator/refresh刷新屬性不生效的問(wèn)題分析及解決
問(wèn)題的引入
在Spring應(yīng)用收到/actuator/refresh的POST請(qǐng)求后,標(biāo)注了@RefreshScope以及@ConfiguratioinProperties的bean會(huì)被Spring容器重新加載。這樣,如果配置文件(一般來(lái)自于配置中心,或者k8s的ConfigMap)發(fā)生了變化,那么這些變化就會(huì)因?yàn)锽ean的重新加載而被應(yīng)用感知。
但是,在實(shí)際應(yīng)用中,可能會(huì)發(fā)現(xiàn)有些標(biāo)注了@ConfigurationProperties的bean,并沒(méi)有按照預(yù)期被Spring容器加載。本文將討論導(dǎo)致這種未按預(yù)期刷新的一種原因。
結(jié)論
在進(jìn)行詳細(xì)的討論之前,先把結(jié)論寫(xiě)出來(lái)。如果大家時(shí)間緊張,而且碰巧遇到了這樣的問(wèn)題,可以直接根據(jù)結(jié)論把問(wèn)題解決掉。
確保標(biāo)注了@ConfigurationProperties注解的bean沒(méi)有被任何Advisor依賴
比如:如下的Bean就不會(huì)被Spring容器刷新。
@ConfigurationProperties(prefix="com.dadaer.test")
public class MyProp {
//...
}
// ............
@Component
public class MyAdvisor extends AbstractPointcutAdvisor {
private final MyProp myProp;
public class MyAdvisor(MyProp myProp) {
this.myProp = myProp;
}
//...
}
這里的MyAdvisor依賴了MyProp,所以在收到/actuator/refresh的請(qǐng)求以后,MyProp的bean不會(huì)被重新加載。
分析
應(yīng)用啟動(dòng)階段
在Spring應(yīng)用啟動(dòng)時(shí),在執(zhí)行到AbstractApplicationContext#refresh方法初始化容器的時(shí)候,其中有一個(gè)步驟(12大步中的第5步),Spring會(huì)向容器中注入所有的BeanPostProcessor,代碼如下:
// Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory);
而其中有一個(gè)BeanPostProcessor叫做:ConfigurationPropertiesBeans,是一個(gè)用來(lái)注冊(cè)所有標(biāo)注了@ConfigurationProperties注解的后置處理器。
在初始化BeanPostProcessor的時(shí)候,會(huì)經(jīng)歷到下面的一段代碼:
@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
// TODO: Consider optimization by caching the list of the aspect names
List<Advisor> candidateAdvisors = findCandidateAdvisors();
for (Advisor advisor : candidateAdvisors) {
if (advisor instanceof AspectJPointcutAdvisor pointcutAdvisor &&
pointcutAdvisor.getAspectName().equals(beanName)) {
return true;
}
}
return super.shouldSkip(beanClass, beanName);
}
這里,會(huì)查找所有的Advisor并初始化。這樣,如果某個(gè)Advisor(比如上述MyAdvisor)依賴了一個(gè)@ConfigurationProperties注解的類(lèi)(比如上述MyProp)。那么此時(shí)MyProp就需要在BeanPostProcessor之前初始化完成,即:MyProp先于ConfigurationPropertiesBeans完成初始化。
應(yīng)用運(yùn)行階段
在應(yīng)用運(yùn)行階段,當(dāng)收到/actuator/refresh的POST請(qǐng)求時(shí),會(huì)觸發(fā)RefreshEndpoint:
@Endpoint(id = "refresh")
public class RefreshEndpoint {
//...
@WriteOperation
public Collection<String> refresh() {
Set<String> keys = this.contextRefresher.refresh();
return keys;
}
}
然后調(diào)用ContextRefresher#refresh方法進(jìn)入下面的代碼:
public synchronized Set<String> refreshEnvironment() {
Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
updateEnvironment();
Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
這里,發(fā)送了一個(gè)EnvironmentChangeEvent事件,這個(gè)事件會(huì)被ConfigurationPropertiesRebinder捕獲,如下:
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.applicationContext.equals(event.getSource())
// Backwards compatible
|| event.getKeys().equals(event.getSource())) {
rebind();
}
}
然后rebind方法被調(diào)用:
@ManagedOperation
public void rebind() {
this.errors.clear();
for (String name : this.beans.getBeanNames()) {
rebind(name);
}
}
這里beans變量的類(lèi)型是:ConfigurationPropertiesBeans,它里面保存了所有待刷新的ConfigurationProperties的bean。
結(jié)論
因?yàn)閱?dòng)階段中,MyProp優(yōu)先于ConfigurationPropertiesBeans被加載,導(dǎo)致ConfigurationPropertiesBeans里面不會(huì)包含MyProp這個(gè)bean,從而導(dǎo)致它不會(huì)被刷新。
以上就是Spring應(yīng)用中使用acutator/refresh刷新屬性不生效的問(wèn)題分析及解決的詳細(xì)內(nèi)容,更多關(guān)于Spring使用acutator/refresh不生效的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot如何進(jìn)行業(yè)務(wù)校驗(yàn)實(shí)例詳解
這篇文章主要給大家介紹了關(guān)于SpringBoot如何進(jìn)行業(yè)務(wù)校驗(yàn)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-01-01
java實(shí)用小技巧之判斷l(xiāng)ist是否有重復(fù)項(xiàng)簡(jiǎn)單例子
這篇文章主要給大家介紹了關(guān)于java實(shí)用小技巧之判斷l(xiāng)ist是否有重復(fù)項(xiàng)的相關(guān)資料,在開(kāi)發(fā)工作中我們有時(shí)需要去判斷List集合中是否含有重復(fù)的元素,需要的朋友可以參考下2023-10-10
使用MQ消息隊(duì)列的優(yōu)缺點(diǎn)詳解
這篇文章主要介紹了使用MQ消息隊(duì)列的優(yōu)缺點(diǎn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07
java智能問(wèn)答圖靈機(jī)器人AI接口(聚合數(shù)據(jù))
這篇文章主要介紹了java智能問(wèn)答圖靈機(jī)器人AI接口(聚合數(shù)據(jù)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
關(guān)于Java中@SuppressWarnings的正確使用方法
這篇文章主要介紹了關(guān)于Java中@SuppressWarnings的正確使用方法,@SuppressWarnings注解主要用在取消一些編譯器產(chǎn)生的警告對(duì)代碼左側(cè)行列的遮擋,有時(shí)候這會(huì)擋住我們斷點(diǎn)調(diào)試時(shí)打的斷點(diǎn),需要的朋友可以參考下2023-05-05
使用?Spring?AI?+?Ollama?構(gòu)建生成式?AI?應(yīng)用的方法
通過(guò)集成SpringBoot和Ollama,本文詳細(xì)介紹了如何構(gòu)建生成式AI應(yīng)用,首先,介紹了AI大模型服務(wù)的兩種實(shí)現(xiàn)方式,選擇使用ollama進(jìn)行部署,隨后,通過(guò)SpringBoot+SpringAI來(lái)實(shí)現(xiàn)應(yīng)用構(gòu)建,本文為開(kāi)發(fā)者提供了一個(gè)實(shí)用的指南,幫助他們快速入門(mén)生成式AI應(yīng)用的開(kāi)發(fā)2024-11-11
Spring MVC 中 AJAX請(qǐng)求并返回JSON的示例
本篇文章主要介紹了Spring MVC 中 AJAX請(qǐng)求并返回JSON,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01

