中间件原理我爱编程

springboot加载配置文件

2018-08-04  本文已影响16人  fighting_coder

springboot启动时候是通过ConfigFileApplicationListener类加载配置文件,这个类继承了EnvironmentPostProcesser和SmartApplicationListener,在springboot启动的时候会调用这个监听器。

public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent(
                    (ApplicationEnvironmentPreparedEvent) event);
        }
        if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent(event);
        }
    }


如果事件是加载配置文件ApplicationEnvironmentPreparedEvent类型,会调用

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());
        }
    }

ConfigFileApplicationListener也继承了EnvironmentPostProcesser,所以会在postProcessors数组中加入,循环所有的EnvironmentPostProcessor类型调用postProcessorEnvironmet方法加载配置文件。

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

在addPropertySources方法中调用Loader类的load方法将配置文件加载到environment中,这个类是ConfigFileApplicationListener中的私有类。

    protected void addPropertySources(ConfigurableEnvironment environment,
            ResourceLoader resourceLoader) {
        RandomValuePropertySource.addToEnvironment(environment);
        new Loader(environment, resourceLoader).load();
    }

私有的Loader类有如下字段

    private class Loader {

        private final Log logger = ConfigFileApplicationListener.this.logger;

        private final ConfigurableEnvironment environment;

        private final ResourceLoader resourceLoader;
       //有两种分别是PropertiesPropertySourceLoader和 
         YmalPropertySourceLoader
        private final List<PropertySourceLoader> propertySourceLoaders;
       //加载配置文件profile
        private Deque<Profile> profiles;
       //已经处理过的profile
        private List<Profile> processedProfiles;
               
        private boolean activatedProfiles;
       //不同profile的PropertySource
        private Map<Profile, MutablePropertySources> loaded;
       //缓存已经加载的文件
        private Map<DocumentsCacheKey, List<Document>> loadDocumentsCache = new HashMap<>();

load方法如下

        public void load() {
            this.profiles = new LinkedList<>();
            this.processedProfiles = new LinkedList<>();
            this.activatedProfiles = false;
            this.loaded = new LinkedHashMap<>();
         //初始化需要加载的profile
            initializeProfiles();
         //循环处理profiles队列加载配置文件
            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);
            }
            load(null, this::getNegativeProfileFilter,
                    addToLoaded(MutablePropertySources::addFirst, true));
            addLoadedPropertySources();
        }

initializeProfiles()方法如下

        private void initializeProfiles() {
            //先加入null的profile
            this.profiles.add(null);
            Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
            this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
               //加入要激活的profile
                      addActiveProfiles(activatedViaProperty);
                     //加入default的profile
            if (this.profiles.size() == 1) { // only has null profile
                for (String defaultProfileName : this.environment.getDefaultProfiles()) {
                    Profile defaultProfile = new Profile(defaultProfileName, true);
                    this.profiles.add(defaultProfile);
                }
            }
        }

然后继续调用load方法,load方法中有两个参数,this::getPositiveProfileFilter和addToLoaded(MutablePropertySources::addLast, false)

    private DocumentFilter getPositiveProfileFilter(Profile profile) {
            return (Document document) -> {
                if (profile == null) {
                    return ObjectUtils.isEmpty(document.getProfiles());
                }
                return ObjectUtils.containsElement(document.getProfiles(),
                        profile.getName())
                        && this.environment.acceptsProfiles(document.getProfiles());
            };
        }

这个方法根据profile过滤不同的document配置文件,返回是一个DocumentFilter的函数是接口,这个接口也是定义在这个类中

    @FunctionalInterface
    private interface DocumentFilter {

        boolean match(Document document);

    }

如果在上面getPositiveProfileFilter的profile参数,document包含这个profile会返回true然后加载这个配置文件。addToLoaded方法是将加载的配置文件中PropertySource加入到MutablePropertySources中。

        private DocumentConsumer addToLoaded(
                BiConsumer<MutablePropertySources, PropertySource<?>> addMethod,
                boolean checkForExisting) {
            return (profile, document) -> {
                if (checkForExisting) {
                    for (MutablePropertySources merged : this.loaded.values()) {
                        if (merged.contains(document.getPropertySource().getName())) {
                            return;
                        }
                    }
                }
                MutablePropertySources merged = this.loaded.computeIfAbsent(profile,
                        (k) -> new MutablePropertySources());
                addMethod.accept(merged, document.getPropertySource());
            };
        }

其返回类型也是一个函数式接口DocumentConsumer,也定义在这个类下

    @FunctionalInterface
    private interface DocumentConsumer {

        void accept(Profile profile, Document document);

    }

继续调用load方法

        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));
            });
        }

springboot项目默认的配置文件的地址路径有四个classpath,classpath:/config, file,file:/config,默认的配置文件名称是application,循环这四个路径下找到配置文件,默认的文件类型propertise,xml,yml,ymal四中类型,对应的加载器PropertiesPropertySourceLoader和 YmalPropertySourceLoader。

                       // 分别用这两中加载器加载
            for (PropertySourceLoader loader : this.propertySourceLoaders) {
                for (String fileExtension : loader.getFileExtensions()) {
                    String prefix = location + name;
                    fileExtension = "." + fileExtension;
                                       // 加载不同后缀
                    loadForFileExtension(loader, prefix, fileExtension, profile,
                            filterFactory, consumer);
                }
            }

继续调用

        private void loadForFileExtension(PropertySourceLoader loader, String prefix,
                String fileExtension, Profile profile,
                DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
            DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
            DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
            if (profile != null) {
                // Try profile-specific file & profile section in profile file (gh-340)
                String profileSpecificFile = prefix + "-" + profile + fileExtension;           
                                // 加载profile为default的load
                load(loader, profileSpecificFile, profile, defaultFilter, consumer);              
                                //加载profile为其他值的load
                load(loader, profileSpecificFile, profile, profileFilter, consumer);
                // Try profile specific sections in files we've already processed
                for (Profile processedProfile : this.processedProfiles) {
                    if (processedProfile != null) {
                        String previouslyLoaded = prefix + "-" + processedProfile
                                + fileExtension;
                        load(loader, previouslyLoaded, profile, profileFilter, consumer);
                    }
                }
            }
            // Also try the profile-specific section (if any) of the normal file
                       // 加载profile为null的load
            load(loader, prefix + fileExtension, profile, profileFilter, consumer);
        }

继续调用

        private void load(PropertySourceLoader loader, String location, Profile profile,
                DocumentFilter filter, DocumentConsumer consumer) {
            try {
                Resource resource = this.resourceLoader.getResource(location);
                String description = getDescription(location, resource);
                if (profile != null) {
                    description = description + " for profile " + profile;
                }
                if (resource == null || !resource.exists()) {
                    this.logger.trace("Skipped missing config " + description);
                    return;
                }
                if (!StringUtils.hasText(
                        StringUtils.getFilenameExtension(resource.getFilename()))) {
                    this.logger.trace("Skipped empty config extension " + description);
                    return;
                }
                String name = "applicationConfig: [" + location + "]";
                        //加载documemt文件
                List<Document> documents = loadDocuments(loader, name, resource);
                if (CollectionUtils.isEmpty(documents)) {
                    this.logger.trace("Skipped unloaded config " + description);
                    return;
                }
                List<Document> loaded = new ArrayList<>();
                for (Document document : documents) {
                                //根据profile的不同,加载不同的documemt
                    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));
                    this.logger.debug("Loaded config file " + description);
                }
            }
            catch (Exception ex) {
                throw new IllegalStateException("Failed to load property "
                        + "source from location '" + location + "'", ex);
            }
        }

配置文件的优先级
springboot中默认文件路径file:,file:/config,classpath:/config,classpath:/,读取文件是按照这个顺序依次读取,然后调用MutablePropertySources的addLast方法。springboot寻找配置文件时从MutablePropertySources中前面开始,所以前面的配置会覆盖后面的配置。


配置文件加载.png

application.yml配置数据如下

config/application.yml
server:
      port: 8080
---
/applicaiton.yml
server:
     port: 9090

加载到MutablePropertySources数据如下:


优先读取.png

在浏览器中可以看到打印出的端口为8080


测试优先读取.png

Profile-多环境配置
应用程序对于不同的运行环境有不同的配置不同,application.xml的配置数据如下

server:
  port: 8080
spring:
  profiles:
    active: dev
---
spring:
  profiles: dev
name: haha
---
spring:
  profiles: test
name: xixi

浏览器打印的输入如下:


多环境配置.png

加载多环境的过程如下
1.首先默认添加null和default的profile到profiles的队列中
2.从队列中取出null的profile,调用load方法,分别从四个默认的路径下寻找application的文件,调用YamlPropertySourceLoad去加载
3.加载3份document文件如下


加载documents.png
4.此时profile为null,会加载document#0
添加document#0.png

5.profiles队列不为空,继续循环添加,此时profile为dev,会加载document#1


加载dev.png
6.profiles队列为空,循环结束。

参考

http://fangjian0423.github.io/2017/06/10/springboot-environment-analysis/

上一篇下一篇

猜你喜欢

热点阅读