1. 程式人生 > 其它 >springboot原始碼(七)

springboot原始碼(七)

springboot原始碼之屬性檔案載入原理剖析

還是先看run方法

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);
//此處就是配置檔案application.yml 就在這個配置環境資訊的事件中完成的 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; }

 

 進入preparEnvironment()方法

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
//釋出ApplicationEnvironmentPreparedEvent事件 listeners.environmentPrepared(environment); bindToSpringApplication(environment);
if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }

 

進入到SpringPublishingRunListener中的environmentPrepared()中

@Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
//釋出ApplicationEnvironmentPreparedEvent事件
this.initialMulticaster .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment)); }

 

進入multicasEvent()--》invokeListener()--》doInvokeListener()---》ConfigFileApplicationListener.onApplicationEvent()

@Override
    public void onApplicationEvent(ApplicationEvent event) {
//跟釋出的事件型別吻合,會處理相應的邏輯
if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } }

 

進入onApplicationEnvironmentPreparedEvent()中

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
//會將spring.factories中的EnvironmentPostProcessor型別載入進來(但這並不是關鍵,關鍵是下一句) List
<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
//將ConfigFileApplicationListener加到postProcessor中 postProcessors.add(
this); AnnotationAwareOrderComparator.sort(postProcessors); for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } }

 

進入postProcessorEnvironment()方法中  ---》主要看ConfigFileApplicationListener.postProcessorEnvironment()

@Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        addPropertySources(environment, application.getResourceLoader());
    }
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
        RandomValuePropertySource.addToEnvironment(environment);
//建立Loader物件,並且完成屬性載入器的載入,並呼叫load()方法
new Loader(environment, resourceLoader).load(); }

 

先看Loader的構造方法:

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
            this.environment = environment;
            this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
            this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(null);
//將spring.factories中的key為PropertySourceLoader的載入進來 (PropertiesPropertiesSourceLoader,YamlPropertiesSourceLoader)
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); }

 

接著看load()方法

void load() {
            FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
                    (defaultProperties) -> {
//建立預設的profile連結串列
this.profiles = new LinkedList<>();
//建立已經處理過的profile類別
this.processedProfiles = new LinkedList<>();
//預設設定為未啟用
this.activatedProfiles = false;
//建立loaded物件
this.loaded = new LinkedHashMap<>();
//載入配置profile的資訊,預設為default initializeProfiles();
//遍歷profiles,並載入解析
while (!this.profiles.isEmpty()) {
//從雙向連結串列中獲取一個profile物件 Profile profile
= this.profiles.poll();
//非預設的就加入,進去看原始碼即可清楚
if (isDefaultProfile(profile)) { addProfileToEnvironment(profile.getName()); } load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); }
//解析profile load(
null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
//載入預設的屬性檔案 application.yml addLoadedPropertySources(); applyActiveProfiles(defaultProperties); }); }

 

然後進入具體的apply()方法中檢視

static void apply(ConfigurableEnvironment environment, String propertySourceName, Set<String> filteredProperties,
            Consumer<PropertySource<?>> operation) {
        MutablePropertySources propertySources = environment.getPropertySources();
//根據propertySourceName從眾多的載入器中獲取對應的載入器 預設的沒有 PropertySource
<?> original = propertySources.get(propertySourceName); if (original == null) {
//此處會回撥前面在apply方法中定義的lambda表示式 operation.accept(
null); return; } propertySources.replace(propertySourceName, new FilteredPropertySource(original, filteredProperties)); try { operation.accept(original); } finally { propertySources.replace(propertySourceName, original); } }

 

接下來看load()方法

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
//getSearchLocations()是個關鍵,獲取配置檔案的位置(如果沒有自定義的話) getSearchLocations().forEach((location)
-> { boolean isDirectory = location.endsWith("/"); Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES; names.forEach((name) -> load(location, name, profile, filterFactory, consumer)); }); }

 

private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";

//獲得預設的掃描路徑,如果沒有特殊指定
//就採用常量DEFAULT_SEARCH_LOCATIONS裡定義的5個路徑(之前是4個,現在變成5個了),載入順序就是上述從前到後的順序
//getSearchNames()方法獲取的是application這個預設的配置檔名(當然如果沒有自定義的話)
//然後逐一遍歷載入目錄機器指定檔名的檔案
private Set<String> getSearchLocations() { Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY); if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { locations.addAll(getSearchLocations(CONFIG_LOCATION_PROPERTY)); } else { locations.addAll( asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS)); } return locations; }

 

繼續回到前面的方法:

names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
進入這個load()方法:
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
                DocumentConsumer consumer) {
            if (!StringUtils.hasText(name)) {
//迴圈遍歷2個載入器PropertiesSourceLoader和YamlPropertiesSourceLoader載入properties、xml檔案,和yml、yaml檔案。
for (PropertySourceLoader loader : this.propertySourceLoaders) { if (canLoadFileExtension(loader, location)) { load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer); return; } } throw new IllegalStateException("File extension of config file location '" + location + "' is not known to any PropertySourceLoader. If the location is meant to reference " + "a directory, it must end in '/'"); } 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); } } } }

 

 

 

 

 

private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
                DocumentConsumer consumer) {
//開始載入預設的application.yml檔案了 Resource[] resources
= getResources(location); for (Resource resource : resources) { try { if (resource == null || !resource.exists()) { if (this.logger.isTraceEnabled()) { StringBuilder description = getDescription("Skipped missing config ", location, resource, profile); this.logger.trace(description); } continue; } if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) { if (this.logger.isTraceEnabled()) { StringBuilder description = getDescription("Skipped empty config extension ", location, resource, profile); this.logger.trace(description); } continue; } String name = "applicationConfig: [" + getLocationName(location, resource) + "]";
//開始載入application.yml List
<Document> documents = loadDocuments(loader, name, resource); if (CollectionUtils.isEmpty(documents)) { if (this.logger.isTraceEnabled()) { StringBuilder description = getDescription("Skipped unloaded config ", location, resource, profile); this.logger.trace(description); } continue; } List<Document> loaded = new ArrayList<>(); for (Document document : documents) { if (filter.match(document)) { addActiveProfiles(document.getActiveProfiles()); addIncludedProfiles(document.getIncludeProfiles()); loaded.add(document); } } Collections.reverse(loaded); if (!loaded.isEmpty()) { loaded.forEach((document) -> consumer.accept(profile, document)); if (this.logger.isDebugEnabled()) { StringBuilder description = getDescription("Loaded config file ", location, resource, profile); this.logger.debug(description); } } } catch (Exception ex) { StringBuilder description = getDescription("Failed to load property source from ", location, resource, profile); throw new IllegalStateException(description.toString(), ex); } } }

 

進入localDocuments()方法:

private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource)
                throws IOException {
            DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
//先從快取中獲取 List
<Document> documents = this.loadDocumentsCache.get(cacheKey); if (documents == null) {
//快取中沒有直接載入 List
<PropertySource<?>> loaded = loader.load(name, resource); documents = asDocuments(loaded); this.loadDocumentsCache.put(cacheKey, documents); } return documents; }

 

進入load()---》YamlPropertiesSourceLoader.load()

@Override
    public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
        if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
            throw new IllegalStateException(
                    "Attempted to load " + name + " but snakeyaml was not found on the classpath");
        }
        List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load();
        if (loaded.isEmpty()) {
            return Collections.emptyList();
        }
        List<PropertySource<?>> propertySources = new ArrayList<>(loaded.size());
        for (int i = 0; i < loaded.size(); i++) {
            String documentNumber = (loaded.size() != 1) ? " (document #" + i + ")" : "";
            propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber,
                    Collections.unmodifiableMap(loaded.get(i)), true));
        }
        return propertySources;
    }

 

得到yml檔案中的資訊。

本文到此結束。下一篇:springboot原始碼之Tomcat容器載入原理。