日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

spring boot和spring cloud的区别_Spring聊聊application和bootstrap

發布時間:2025/3/21 javascript 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 spring boot和spring cloud的区别_Spring聊聊application和bootstrap 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

用過Spring 的小伙伴都知道, application.yml或者 application.properties 是Spring 的引導配置文件,但是有了解過其中區別嗎?本文將從這個問題入手,深入源碼中,研究 application.yml和 bootstrap.yml到底有什么區別。

配置

首先,我們在程序中,可以通過 spring.profiles.active 來制定生效的配置類,這樣可以來區分配置。其次,可以通過更改 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); // 準備環境

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); // 回調監聽,通知運行狀態

}

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配置加載

很多資料在說這兩個區別時,都會先說bootstrap.yml優先于application.yml加載,但是都沒有給出代碼上的證據,接下來一起盤他。

BootstrapApplicationListener引導

從 prepareEnvironment 方法進入,里面會有對外發布一個 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));

}

經過其他監聽器后,會進入到 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出一個新的容器,并執行其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優先,故不會全量刷新所有配置,而只刷新部分配置。但是,如果是代碼里面是通過?spring.factories?來獲取的數據,則需要在對應的bean 的方法 的冪等性。

  • 傳入的configName將?spring.config.name?重寫了,所以會去加載?bootstrap配置,而當父容器加載完之后,輪到子容器時,仍然會使用?application配置,這就說明了,為什么bootstrap優先于application配置

  • ConfigFileApplicationListener 有什么用

    在idea中,點擊 spring.profiles.name 會跳轉到 ConfigFileApplicationListener 中。在 ConfigFileApplicationListener 中,會加載所有的 EnvironmentPostProcessor并執行他們的 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生命周期事件,從而可以實現很多特色功能一樣。對于 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表達式來增加隨機數。

  • 加載配置文件,即?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();

    }

    根據是否有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同樣會去根據 spring.config.location 和 spring.config.additional-location 去尋找配置,另外默認加載配置的地方為:classpath:/,classpath:/config/,file:./,file:./config/。

    另外,讀完 bootstrap配置,會去讀 application配置嗎?留個疑惑,下篇文章解決。

    還有一個問題,微服務配置中心是如何刷新配置的呢?一起下篇解決!

    關注博主公眾號: 六點A君。哈哈哈,一起研究Spring:

    《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

    總結

    以上是生活随笔為你收集整理的spring boot和spring cloud的区别_Spring聊聊application和bootstrap的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。