1. 程式人生 > 其它 >springboot啟動讀取配置檔案過程&自定義配置檔案處理器

springboot啟動讀取配置檔案過程&自定義配置檔案處理器

    最近看到看到spring的配置檔案放在了resources/config/application.yal 檔案內部,第一次見。就想的研究下,springboot啟動讀取配置檔案的過程。

1. 啟動過程

  1. org.springframework.boot.SpringApplication#run(java.lang.Class<?>[], java.lang.String[])
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
    }
  1. org.springframework.boot.SpringApplication#SpringApplication(java.lang.Class<?>...)
    public SpringApplication(Class<?>... primarySources) {
        this((ResourceLoader)null, primarySources);
    }

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = Collections.emptySet();
        this.isCustomEnvironment = false;
        this.lazyInitialization = false;
        this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
        this.applicationStartup = ApplicationStartup.DEFAULT;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

這裡會呼叫到org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories 去讀取相關的自動配置。

    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            Map<String, List<String>> result = new HashMap();

            try {
                Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;

                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }

                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }

這裡也就看到了我們熟悉的掃描classpath下META-INF/spring.factories 檔案資訊,然後快取到map。

  1. 在springboot.xxx.jar META-INF/spring.factories 檔案有下面配置:
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
  1. 注意這裡不是自動配置

自動配置是在一個Spring的後置處理器中處理的。org.springframework.boot.autoconfigure.AutoConfigurationImportSelector。

2. 讀取配置檔案過程

springboot 啟動過程中會呼叫到org.springframework.boot.SpringApplication#run(java.lang.String...)

    public ConfigurableApplicationContext run(String... args) {
        long startTime = System.nanoTime();
        DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
        ConfigurableApplicationContext context = null;
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting(bootstrapContext, this.mainApplicationClass);

        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
            }

            listeners.started(context, timeTakenToStartup);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var12) {
            this.handleRunFailure(context, var12, listeners);
            throw new IllegalStateException(var12);
        }

        try {
            Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
            listeners.ready(context, timeTakenToReady);
            return context;
        } catch (Throwable var11) {
            this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var11);
        }
    }

1. getRunListeners 方法

​ 這個方法原始碼如下:

    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
        return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args), this.applicationStartup);
    }

方法返回一個SpringApplicationRunListeners 物件。 getSpringFactoriesInstances 方法會從springboot 的自動配置讀取SpringApplicationRunListener 實現類。也就是上面的EventPublishingRunListener。

2. prepareEnvironment

開始準備環境

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        this.configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        listeners.environmentPrepared(bootstrapContext, environment);
        DefaultPropertiesPropertySource.moveToEnd(environment);
        Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
        this.bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            EnvironmentConverter environmentConverter = new EnvironmentConverter(this.getClassLoader());
            environment = environmentConverter.convertEnvironmentIfNecessary(environment, this.deduceEnvironmentClass());
        }

        ConfigurationPropertySources.attach(environment);
        return environment;
    }
  1. 建立一個環境物件
  2. 對環境進行配置
  3. listeners.environmentPrepared(bootstrapContext, environment); 準備環境,釋出事件
    void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        this.doWithListeners("spring.boot.application.environment-prepared", (listener) -> {
            listener.environmentPrepared(bootstrapContext, environment);
        });
    }
  1. 繼續呼叫到org.springframework.boot.context.event.EventPublishingRunListener#environmentPrepared
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
    }

這裡就是廣播事件,接下來就是看事件處理器的處理。

  1. 繼續呼叫到org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent)
    public void multicastEvent(ApplicationEvent event) {
        this.multicastEvent(event, this.resolveDefaultEventType(event));
    }

    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
        Executor executor = this.getTaskExecutor();
        Iterator var5 = this.getApplicationListeners(event, type).iterator();

        while(var5.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var5.next();
            if (executor != null) {
                executor.execute(() -> {
                    this.invokeListener(listener, event);
                });
            } else {
                this.invokeListener(listener, event);
            }
        }

    }

這裡獲取到的變數var5包含如下listener:

接著遍歷6個listener,然後invokeListener 內部呼叫doInvokeListener 方法。也就是呼叫到 listener.onApplicationEvent(event); 方法。

4. 6個listener的inApplicationEvent 方法(環境後置處理器用法)

1. org.springframework.boot.env.EnvironmentPostProcessorApplicationListener#onApplicationEvent

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);
        }

        if (event instanceof ApplicationPreparedEvent) {
            this.onApplicationPreparedEvent();
        }

        if (event instanceof ApplicationFailedEvent) {
            this.onApplicationFailedEvent();
        }

    }
    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        SpringApplication application = event.getSpringApplication();
        Iterator var4 = this.getEnvironmentPostProcessors(application.getResourceLoader(), event.getBootstrapContext()).iterator();

        while(var4.hasNext()) {
            EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var4.next();
            postProcessor.postProcessEnvironment(environment, application);
        }

    }
  1. 這裡是獲取到環境的後置處理器,然後進行處理。獲取到的7個後置處理器如下:

2. 核心的後置環境後置處理器是:ConfigDataEnvironmentPostProcessor

其邏輯如下:

    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        this.postProcessEnvironment(environment, application.getResourceLoader(), application.getAdditionalProfiles());
    }

    void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) {
        try {
            this.logger.trace("Post-processing environment to add config data");
            ResourceLoader resourceLoader = resourceLoader != null ? resourceLoader : new DefaultResourceLoader();
            this.getConfigDataEnvironment(environment, (ResourceLoader)resourceLoader, additionalProfiles).processAndApply();
        } catch (UseLegacyConfigProcessingException var5) {
            this.logger.debug(LogMessage.format("Switching to legacy config file processing [%s]", var5.getConfigurationProperty()));
            this.configureAdditionalProfiles(environment, additionalProfiles);
            this.postProcessUsingLegacyApplicationListener(environment, resourceLoader);
        }

    }

    ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) {
        return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader, additionalProfiles, this.environmentUpdateListener);
    }

1.org.springframework.boot.context.config.ConfigDataEnvironment#ConfigDataEnvironment 建立物件和初始化

    ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles, ConfigDataEnvironmentUpdateListener environmentUpdateListener) {
        Binder binder = Binder.get(environment);
        UseLegacyConfigProcessingException.throwIfRequested(binder);
        this.logFactory = logFactory;
        this.logger = logFactory.getLog(this.getClass());
        this.notFoundAction = (ConfigDataNotFoundAction)binder.bind("spring.config.on-not-found", ConfigDataNotFoundAction.class).orElse(ConfigDataNotFoundAction.FAIL);
        this.bootstrapContext = bootstrapContext;
        this.environment = environment;
        this.resolvers = this.createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
        this.additionalProfiles = additionalProfiles;
        this.environmentUpdateListener = environmentUpdateListener != null ? environmentUpdateListener : ConfigDataEnvironmentUpdateListener.NONE;
        this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, resourceLoader.getClassLoader());
        this.contributors = this.createContributors(binder);
    }

(1). this.createContributors(binder) 是建立屬性描述者:

    private ConfigDataEnvironmentContributors createContributors(Binder binder) {
        this.logger.trace("Building config data environment contributors");
        MutablePropertySources propertySources = this.environment.getPropertySources();
        List<ConfigDataEnvironmentContributor> contributors = new ArrayList(propertySources.size() + 10);
        PropertySource<?> defaultPropertySource = null;
        Iterator var5 = propertySources.iterator();

        while(var5.hasNext()) {
            PropertySource<?> propertySource = (PropertySource)var5.next();
            if (DefaultPropertiesPropertySource.hasMatchingName(propertySource)) {
                defaultPropertySource = propertySource;
            } else {
                this.logger.trace(LogMessage.format("Creating wrapped config data contributor for '%s'", propertySource.getName()));
                contributors.add(ConfigDataEnvironmentContributor.ofExisting(propertySource));
            }
        }

        contributors.addAll(this.getInitialImportContributors(binder));
        if (defaultPropertySource != null) {
            this.logger.trace("Creating wrapped config data contributor for default property source");
            contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
        }

        return this.createContributors((List)contributors);
    }

(2). 繼續呼叫到:

    private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
        List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList();
        this.addInitialImportContributors(initialContributors, this.bindLocations(binder, "spring.config.import", EMPTY_LOCATIONS));
        this.addInitialImportContributors(initialContributors, this.bindLocations(binder, "spring.config.additional-location", EMPTY_LOCATIONS));
        this.addInitialImportContributors(initialContributors, this.bindLocations(binder, "spring.config.location", DEFAULT_SEARCH_LOCATIONS));
        return initialContributors;
    }

從靜態程式碼塊可以看到預設的配置檔案路徑如下:

    static {
        List<ConfigDataLocation> locations = new ArrayList();
        locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
        locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
        DEFAULT_SEARCH_LOCATIONS = (ConfigDataLocation[])locations.toArray(new ConfigDataLocation[0]);
        EMPTY_LOCATIONS = new ConfigDataLocation[0];
        CONFIG_DATA_LOCATION_ARRAY = Bindable.of(ConfigDataLocation[].class);
        STRING_LIST = Bindable.listOf(String.class);
        ALLOW_INACTIVE_BINDING = new ConfigDataEnvironmentContributors.BinderOption[0];
        DENY_INACTIVE_BINDING = new ConfigDataEnvironmentContributors.BinderOption[]{BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE};
    }

(3).....繼續後面的初始化

  1. org.springframework.boot.context.config.ConfigDataEnvironment#processAndApply 處理
    void processAndApply() {
        ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers, this.loaders);
        this.registerBootstrapBinder(this.contributors, (ConfigDataActivationContext)null, DENY_INACTIVE_BINDING);
        ConfigDataEnvironmentContributors contributors = this.processInitial(this.contributors, importer);
        ConfigDataActivationContext activationContext = this.createActivationContext(contributors.getBinder((ConfigDataActivationContext)null, new ConfigDataEnvironmentContributors.BinderOption[]{BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE}));
        contributors = this.processWithoutProfiles(contributors, importer, activationContext);
        activationContext = this.withProfiles(contributors, activationContext);
        contributors = this.processWithProfiles(contributors, importer, activationContext);
        this.applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(), importer.getOptionalLocations());
    }

(1). this.processInitial 會根據上面生成的屬性描述符去找檔案,經過多層呼叫,呼叫到org.springframework.boot.context.config.StandardConfigDataLocationResolver#resolve(org.springframework.boot.context.config.ConfigDataLocationResolverContext, org.springframework.boot.context.config.ConfigDataLocation)

    public List<StandardConfigDataResource> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location) throws ConfigDataNotFoundException {
        return this.resolve(this.getReferences(context, location.split()));
    }

引數如下:

解析到的結果如下:

class path resource [config/application.properties]

(2). this.createActivationContext(contributors.getBinder 會根據檔案解析配置。找到檔案開始呼叫propertySourceLoaders 解析檔案,比如yaml和properties 對應的解析器分別為:PropertiesPropertySourceLoader、YamlPropertySourceLoader。 真正的解析方法也是在loadProperties 內部。呼叫連如下:

(3). 呼叫org.springframework.boot.context.config.ConfigDataEnvironment#applyToEnvironment 應用到environment 物件

private void applyToEnvironment(ConfigDataEnvironmentContributors contributors, ConfigDataActivationContext activationContext, Set<ConfigDataLocation> loadedLocations, Set<ConfigDataLocation> optionalLocations) {
    this.checkForInvalidProperties(contributors);
    this.checkMandatoryLocations(contributors, activationContext, loadedLocations, optionalLocations);
    MutablePropertySources propertySources = this.environment.getPropertySources();
    this.applyContributor(contributors, activationContext, propertySources);
    DefaultPropertiesPropertySource.moveToEnd(propertySources);
    Profiles profiles = activationContext.getProfiles();
    this.logger.trace(LogMessage.format("Setting default profiles: %s", profiles.getDefault()));
    this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault()));
    this.logger.trace(LogMessage.format("Setting active profiles: %s", profiles.getActive()));
    this.environment.setActiveProfiles(StringUtils.toStringArray(profiles.getActive()));
    this.environmentUpdateListener.onSetProfiles(profiles);
}

1》MutablePropertySources propertySources = this.environment.getPropertySources(); 獲取到propertySources;

繼續呼叫org.springframework.boot.context.config.ConfigDataEnvironment#applyContributor:(應用配置)

    private void applyContributor(ConfigDataEnvironmentContributors contributors, ConfigDataActivationContext activationContext, MutablePropertySources propertySources) {
        this.logger.trace("Applying config data environment contributions");
        Iterator var4 = contributors.iterator();

        while(var4.hasNext()) {
            ConfigDataEnvironmentContributor contributor = (ConfigDataEnvironmentContributor)var4.next();
            PropertySource<?> propertySource = contributor.getPropertySource();
            if (contributor.getKind() == Kind.BOUND_IMPORT && propertySource != null) {
                if (!contributor.isActive(activationContext)) {
                    this.logger.trace(LogMessage.format("Skipping inactive property source '%s'", propertySource.getName()));
                } else {
                    this.logger.trace(LogMessage.format("Adding imported property source '%s'", propertySource.getName()));
                    propertySources.addLast(propertySource);
                    this.environmentUpdateListener.onPropertySourceAdded(propertySource, contributor.getLocation(), contributor.getResource());
                }
            }
        }

    }

最終addLast(propertySource) 會加一個物件:

根據java引用傳遞,實際是向environment.getPropertySources(); 添加了一個propertySource 物件。實際也就是org.springframework.core.env.PropertySource 的實現類。

到這裡大致流程走完。

3. 自定義配置後置處理器以及測試

假設一個場景是從自己定義配置來源。我們增加自定的配置後置處理器然後測試。

1. 建立自己的後置處理器

MyEnvironmentPostProcessor

package com.example.demo.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;

import java.util.HashMap;

public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        environment.getPropertySources().addLast(new MyPropertySource("myPropertySource", new HashMap<>()));
    }
}

MyPropertySource

package com.example.demo.config;

import org.springframework.core.env.PropertySource;

import java.util.HashMap;

public class MyPropertySource extends PropertySource<HashMap<String, Object>> {

    public MyPropertySource(String name, HashMap<String, Object> source) {
        super(name, source);

        // 模擬加幾個配置
        source.put("name", "zs");
    }

    @Override
    public Object getProperty(String name) {
        return getSource().get(name);
    }
}

resource/META-INT/spring.factories 檔案增加如下配置:

org.springframework.boot.env.EnvironmentPostProcessor=\
  com.example.demo.config.MyEnvironmentPostProcessor

2. 測試

測試類

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class TestComponent {

    @Autowired
    private Environment environment;

    @Value("${name}")
    private String name;

    @PostConstruct
    public void init() {
        System.out.println(name);
    }
}
  1. 輸出zs

  2. com.example.demo.config.MyEnvironmentPostProcessor#postProcessEnvironment 呼叫連如下

  3. com.example.demo.config.MyPropertySource#getProperty 注入name屬性呼叫連如下: