javascript
spring boot和spring cloud的区别_Spring聊聊application和bootstrap
用過Spring 的小伙伴都知道, application.yml或者 application.properties 是Spring 的引導配置文件,但是有了解過其中區(qū)別嗎?本文將從這個問題入手,深入源碼中,研究 application.yml和 bootstrap.yml到底有什么區(qū)別。
配置
首先,我們在程序中,可以通過 spring.profiles.active 來制定生效的配置類,這樣可以來區(qū)分配置。其次,可以通過更改 spring.config.name 來更改引導的配置文件名字,例如可以將 anla.properties 也改為可加載的配置。
Spring 啟動過程
Spring啟動過程中,會首先加載配置,然后才去初始化Ioc容器,以Spring Boot為例,主要邏輯可以如下:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 準備環(huán)境
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 準備上下文
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context); // 回調(diào)監(jiān)聽,通知運行狀態(tài)
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
和配置相關的,主要在以下代碼中:
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
...
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
Spring配置加載
很多資料在說這兩個區(qū)別時,都會先說bootstrap.yml優(yōu)先于application.yml加載,但是都沒有給出代碼上的證據(jù),接下來一起盤他。
BootstrapApplicationListener引導
從 prepareEnvironment 方法進入,里面會有對外發(fā)布一個 ApplicationEnvironmentPreparedEvent 事件:prepareEnvironment 方法:
listeners.environmentPrepared(environment);
SpringApplicationRunListeners.java:
public void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
EventPublishingRunListener.java:
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
經(jīng)過其他監(jiān)聽器后,會進入到 BootstrapApplicationListener 的 onApplicationEvent 方法。BootstrapApplicationListener 定義:
public class BootstrapApplicationListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
}
為什么 BootstrapApplicationListener 會被加載呢?其實當我們引入 spring-cloud-context 依賴時,它下面會有一個spring的spi配置文件 spring.factories,里面定義了一個 ApplicationListener:
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\
org.springframework.cloud.context.restart.RestartListener
BootstrapApplicationListener 有什么用?
BootstrapApplicationListener 的 onApplicationEvent 有以下流程:
冪等性,只會啟動一次(相對于多容器)
如果需要,會啟動一個父容器
將父容器部分信息merge到子容器中
這里,父容器是什么概念?簡單的來說,父容器,就是一個新的Spring 容器,會new出一個新的容器,并執(zhí)行其run方法,即 SpringApplication 的run方法。BootstrapApplicationListener 的 bootstrapServiceContext 方法:
private ConfigurableApplicationContext bootstrapServiceContext(
ConfigurableEnvironment environment, final SpringApplication application,
String configName) {
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
MutablePropertySources bootstrapProperties = bootstrapEnvironment
.getPropertySources();
for (PropertySource> source : bootstrapProperties) {
bootstrapProperties.remove(source.getName());
}
String configLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
Map<String, Object> bootstrapMap = new HashMap<>();
bootstrapMap.put("spring.config.name", configName);
bootstrapMap.put("spring.main.web-application-type", "none");
if (StringUtils.hasText(configLocation)) {
bootstrapMap.put("spring.config.location", configLocation);
}
bootstrapProperties.addFirst(
new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
for (PropertySource> source : environment.getPropertySources()) {
if (source instanceof StubPropertySource) {
continue;
}
bootstrapProperties.addLast(source);
}
SpringApplicationBuilder builder = new SpringApplicationBuilder()
.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
.environment(bootstrapEnvironment)
.registerShutdownHook(false).logStartupInfo(false)
.web(WebApplicationType.NONE);
final SpringApplication builderApplication = builder.application();
if (builderApplication.getMainApplicationClass() == null) {
builder.main(application.getMainApplicationClass());
}
if (environment.getPropertySources().contains("refreshArgs")) {
builderApplication
.setListeners(filterListeners(builderApplication.getListeners()));
}
builder.sources(BootstrapImportSelectorConfiguration.class);
final ConfigurableApplicationContext context = builder.run();
context.setId("bootstrap");
addAncestorInitializer(application, context);
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
由于啟動中時指定的source優(yōu)先,故不會全量刷新所有配置,而只刷新部分配置。但是,如果是代碼里面是通過?spring.factories?來獲取的數(shù)據(jù),則需要在對應的bean 的方法 的冪等性。
傳入的configName將?spring.config.name?重寫了,所以會去加載?bootstrap配置,而當父容器加載完之后,輪到子容器時,仍然會使用?application配置,這就說明了,為什么bootstrap優(yōu)先于application配置
ConfigFileApplicationListener 有什么用
在idea中,點擊 spring.profiles.name 會跳轉到 ConfigFileApplicationListener 中。在 ConfigFileApplicationListener 中,會加載所有的 EnvironmentPostProcessor并執(zhí)行他們的 postProcessorEnvironment方法:
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
正如Spring容器中,有各種處理器 PostProcessor來處理 BeanFactory生命周期事件,或者來處理 Bean生命周期事件,從而可以實現(xiàn)很多特色功能一樣。對于 Evironment來說, EnvironmentPostProcessor就是干這個的。
Spring中定義了一個頂層接口 PropertySource ,作為配置類抽象,利用 EnvironmentPostProcessor 可以對Spring 中配置類進行增刪,例如自定義一個 AnlaEnvironmentPostProcessor 將 anla.properties 讀入:
@Slf4j
public class AnlaEnvironmentPostProcessor implements EnvironmentPostProcessor {
private PropertiesPropertySourceLoader loader = new PropertiesPropertySourceLoader();
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
MutablePropertySources propertySources = environment.getPropertySources();
Resource resource = new ClassPathResource("anla.properties");
try {
PropertySource ps = loader.load("YetAnotherPropertiesFile", resource)
.get(0);
propertySources.addFirst(ps);
} catch (Exception e) {
log.error("Exception!", e);
}
}
}
配置讀取
ConfigFileApplicationListener 本身也是一個 EnvironmentPostProcessor 配置類,看他的 postProcessEnvironment方法:
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
addPropertySources 方法:
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
將?RandomValuePropertySource?加入進去,這樣可以在配置文件中,使用el表達式來增加隨機數(shù)。
加載配置文件,即?spring.config.name?對應配置文件
Loader的 load方法:
public void load() {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (profile != null && !profile.isDefaultProfile()) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
resetEnvironmentProfiles(this.processedProfiles);
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
}
根據(jù)是否有profile,config的名字,去路徑下搜索配置文件:
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));
});
}
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
if (!StringUtils.hasText(name)) {
for (PropertySourceLoader loader : this.propertySourceLoaders) {
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
}
Set<String> processed = new HashSet<>();
for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String fileExtension : loader.getFileExtensions()) {
if (processed.add(fileExtension)) {
loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
consumer);
}
}
}
}
目前Spring支持 yml 和 properties 兩種格式配置 的 PropertySourceLoader。另外,Spring同樣會去根據(jù) spring.config.location 和 spring.config.additional-location 去尋找配置,另外默認加載配置的地方為:classpath:/,classpath:/config/,file:./,file:./config/。
另外,讀完 bootstrap配置,會去讀 application配置嗎?留個疑惑,下篇文章解決。
還有一個問題,微服務配置中心是如何刷新配置的呢?一起下篇解決!
關注博主公眾號: 六點A君。哈哈哈,一起研究Spring:
《新程序員》:云原生和全面數(shù)字化實踐50位技術專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的spring boot和spring cloud的区别_Spring聊聊application和bootstrap的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 将mysql的变量置为0_MySQL 8
- 下一篇: mysql 定时同步数据_如何定时备份M