前言
現(xiàn)在幾乎所有的java開發(fā)都會用到springboot哲身,除了很老很老的項目做修,應該不會再有人直接用jsp脚猾,servlet等寫web項目了吧葱峡,直接用spring的都很少見了。
今天發(fā)生的這個問題就得從springboot說起龙助。我們都知道springboot遵循約定大于配置的規(guī)則砰奕,盡量將spring中的配置減少,幾行代碼就可以跑一個web項目,但是默認的東西越多军援,其實隱藏的東西也就越多常空,一旦碰到什么問題,如果沒點準備盖溺,真的是會手忙腳亂的漓糙。
問題描述
說起來也不復雜,新拆分了一個項目烘嘱,公司用的apollo作為配置中心昆禽,所以要做一些基本配置,讓新項目從apollo拉取配置蝇庭。本以為簡單的Ctrl+C醉鳖,Ctrl+V搞定,結果啟動的時候報dubbo配置找不到哮内,反復查看apollo盗棵,一個字母一個字母的比對過去,確定配置沒有配錯的北发,但是怎么就找不到呢纹因?
源碼分析
一般這種問題想從網(wǎng)上找到答案的可能性微乎其微,只好自己動手琳拨,深入源碼瞭恰,看看到底是哪個妖怪在作祟。
SpringApplicationRunListener和ApplicationContextInitializer
Dubbo配置的加載是架構組jar包提供的狱庇,定義了一個SpringApplicationRunListener的實現(xiàn)類惊畏,而apollo的配置加載是在ApplicationContextInitializer的一個實現(xiàn)類ApolloApplicationContextInitializer中處理的。所以一開始的懷疑Listener和Initializer在應用啟動的時候的順序問題密任。
因為DubboListener里面加載dubbo的配置的代碼在SpringApplicationRunListener的contextLoaded方法中颜启,多以可以從下面SpringApplication的源碼看到,Initializer的initialize方法是在Listener的contextLoaded方法之前執(zhí)行的
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
// Initializer的initialize方法在這里執(zhí)行
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
// Listener的contextLoaded方法在這里執(zhí)行
listeners.contextLoaded(context);
}
debug源碼
猜測不對浪讳,只好跟著啟動的源碼debug觀察了缰盏。這里省略一開始的猜測和嘗試,直接進入重點:調試的時候有個發(fā)現(xiàn)驻债,apollo的是否啟用的配置值居然是false爷光!可以從代碼看到譬涡,方法直接返回了,并沒有去加載apollo中的配置值漩勤,所以并不是順序有問題笙以,而是一開始是否啟用的配置值有問題淌实。
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
initializeSystemProperty(environment);
// 如果這里配置是false,那方法直接return了
String enabled = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "false");
if (!Boolean.valueOf(enabled)) {
logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${{}}", context, PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
return;
}
// 省略
查看項目中的application.properties文件,沒啥問題啊(本來就是其他項目拷過來的拆祈,能有啥問題)恨闪,那怎么就讀成false了呢?
apollo.bootstrap.enabled=true
apollo.bootstrap.namespaces=application,dev.common
只好繼續(xù)debug讓人頭大的源碼放坏,還是略過那些不重要的代碼咙咽,直接來看重點和結論:
-
第一次進入SimpleApplicationEventMulticaster的這個方法,這個時候其實是應用啟動事件淤年,發(fā)現(xiàn)有4個監(jiān)聽器钧敞,但看上去沒一個像跟配置有關的
-
第二次進這個方法,這個時候事件類型是環(huán)境準備事件麸粮,其中有個監(jiān)聽器是ConfigFileApplicationListener
-
進入ConfigFileApplicationListener的事件處理方法
- 通過ConfigFileApplicationListener的方法調用鏈onApplicationEnvironmentPreparedEvent --> postProcessEnvironment --> addPropertySources直接進入到內(nèi)部類Loader的下面這個方法溉苛,其中常量NO_SEARCH_NAMES是包含單個null元素的集合,這里獲取到的names就是配置文件的名稱弄诲。
private void load(Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
Set<String> names = (isFolder ? getSearchNames() : NO_SEARCH_NAMES);
names.forEach(
(name) -> load(location, name, profile, filterFactory, consumer));
});
}
-
小心翼翼的一步一步往下走愚战,本以為不會走進if條件,這樣讀到的就是application配置文件了齐遵,結果居然走了進去寂玲,而且"spring.config.name"對應的value是boostrap,而項目里根本沒有配置boostrap.yaml文件梗摇,那這個值是哪來的呢敢茁?
- 找了個能正常啟動的項目,同樣的地方打上了斷點留美,發(fā)現(xiàn)沒進if條件彰檬,"spring.config.name"這個配置到底什么時候設置進去的,反復debug了好幾遍谎砾,發(fā)現(xiàn)出錯的應用多了一個監(jiān)聽器BootstrapApplicationListener逢倍,而這個監(jiān)聽器的優(yōu)先級如下,比ConfigFileApplicationListener小多了(越小優(yōu)先級越高)
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;
- 現(xiàn)在就剩一個問題了景图,為啥會多這么一個監(jiān)聽器呢较雕?這個類所在的jar包是spring-cloud-context,分析了依賴發(fā)現(xiàn)正常啟動的應用果然是沒有這個依賴的挚币。
結論
有時候默認的約定會給開發(fā)人員帶來一定的困擾亮蒋,就像springcloud默認會去讀bootstrap的配置一樣,而差別僅僅是依賴中是否含有對應的jar包妆毕。本篇的問題其實還有一種解決方案慎玖,就是直接將application.properties里的配置移動到bootstrap.properties里,這樣apollo啟用的配置就能從bootstrap.properties里讀到了笛粘。