preface

In this section, learn about Spring Profiles.

Profiles are used a lot in our daily work. Generally we use it to distinguish between development, test, and online environments.

So how does Spring Boot load it?

Profiles

The default

The one we use most often is application.yml, which we know Spring Boot will load automatically.

In addition to application. Yml, Spring Boot automatically loads application-default.yml.

Yml and Properties formats are supported by Spring Boot. But in the same case, properties takes precedence over YML.

In addition, we typically deactivate a profile using the active attribute, in which case application-default is invalidated.

The loading process

Let’s focus on the prepareEnvironment method called in the SpringApplication#run() method.

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

After we debug, listeners. EnvironmentPrepared (environment); Before loading these attribute sources:

Attribute source after sending event:

Case solved case solved, our configuration file is loaded into memory in the event listening mechanism.

Or after debug, we found the listener ConfigFileApplicationListener. Let’s focus on how the listener loads the configuration file.

ConfigFileApplicationListener

// ConfigFileApplicationListener # 172
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()); }}Copy the code

Here is mainly cycle call postProcessor. PostProcessEnvironment () method:

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        addPropertySources(environment, application.getResourceLoader());
    }
Copy the code

Continue calling the addPropertySources() method:

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

Excited, we see the keyword Loader, we continue into the load() method:

public void load(a) {
            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();
        }
Copy the code

Something. I guess this is the main logic for loading configuration files.

The first few lines of logic are all initializations, starting with the initializeProfiles() method:

        private void initializeProfiles(a) {
            // The default profile for these purposes is represented as null. We add it
            // first so that it is processed first and has lowest priority.
      // Add a null to echo this.profiles. Size () == 1.
            this.profiles.add(null);
      // Get active configuration properties
            Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
            this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
            // Any pre-existing active profiles set via property sources (e.g.
            // System properties) take precedence over those added in config files.
            addActiveProfiles(activatedViaProperty);
            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); }}}Copy the code

One thing to note is that when we get to this point in the program, our configuration file has not been loaded yet:

So here, we must go to the logic if (this.profiles.size() == 1), and here, we load the default Property. That’s why Spring Boot automatically loads application-default.yml.

Then, the program continues:

while (!this.profiles.isEmpty()) {
         / / out of the stack
                Profile profile = this.profiles.poll();
         // null && default, profile.get(0) == null will not enter here
                if(profile ! =null && !profile.isDefaultProfile()) {
                    addProfileToEnvironment(profile.getName());
                }
         / / here is the main logic, need attention here addToLoaded (MutablePropertySources: : addLast, false) this is Consumer function parameters.
                load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
                this.processedProfiles.add(profile);
            }
Copy the code

Let’s go to 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));
            });
        }
Copy the code

GetSearchLocaltion () is the spot where the loading configuration files, the default has four: the classpath: /, the classpath: / / config file:. /, file:. / config /; The spring.config.location and spring.config.additional-location values are also loaded.

GetSearchNames (), which is the prefix to load the configuration file, uses the default Application. It also loads the value of the spring.config.name property, overriding the default application.

Names.foreach ((name) -> Load (location, name, profile, filterFactory, consumer)):

        private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {... 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); }}}}Copy the code

Enclosing propertySourceLoaders interesting:

This is the loader for the properties and YML files.

I then do a little path concatenation and go to the loadForFileExtension() method. We locate the logic to enter the method body with prefix=classpath:/application, fileExtension=.yml:

private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension, Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {... .// Also try the profile-specific section (if any) of the normal file
      // We will enter this method
            load(loader, prefix + fileExtension, profile, profileFilter, consumer);
        }
Copy the code
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter, DocumentConsumer consumer) {
            try {
        // Load file, our configuration file is loaded into memory
                Resource resource = this.resourceLoader.getResource(location); . .// Wrap the configuration file (application.yml) as a Document
                String name = "applicationConfig: [" + location + "]"; List<Document> documents = loadDocuments(loader, name, resource); . List<Document> loaded =new ArrayList<>();
                for (Document document : documents) {
                    if (filter.match(document)) {
             // Load the active and include configured in the profile and add them to profiles
                        addActiveProfiles(document.getActiveProfiles());
                        addIncludedProfiles(document.getIncludeProfiles());
                        loaded.add(document);
                    }
                }
                Collections.reverse(loaded);
                if(! loaded.isEmpty()) {// Finally go to this methodloaded.forEach((document) -> consumer.accept(profile, document)); . }}... }Copy the code

ForEach ((document) -> consumer. Accept (profile, document))

private DocumentConsumer addToLoaded(BiConsumer<MutablePropertySources, PropertySource<? >> addMethod,boolean checkForExisting) {
            return (profile, document) -> {
                ......
                // Add to loaded (Map
      
       ) property source
      ,>
                MutablePropertySources merged = this.loaded.computeIfAbsent(profile,
                        (k) -> new MutablePropertySources());
                // Add the parsed property to the end of MutablePropertySources
                addMethod.accept(merged, document.getPropertySource());
            };
        }
Copy the code

The above isprofiles.get(0)=nullLoad the specified profile at the specified location, then read its properties including active and include, then add the values of active and include to profiles, and continue traversing.

Once all the traversal is complete, it’s time to execute the addLoadedPropertySources() method:

 private void addLoadedPropertySources(a) {
    MutablePropertySources destination = this.environment.getPropertySources();
    List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
    Collections.reverse(loaded);
    String lastAdded = null;
    Set<String> added = new HashSet<>();
    for (MutablePropertySources sources : loaded) {
        for(PropertySource<? > source : sources) {if(added.add(source.getName())) { addLoadedPropertySource(destination, lastAdded, source); lastAdded = source.getName(); }}}}Copy the code

Add all the property sources from the loaded configuration file to the set of property sources:

In this way, the configuration we configured to activate the configuration file in an environment takes effect.

conclusion

In this section, we learned how profiles are loaded in our daily development.

Profiles are used in almost every project, but never explored. It was a little bit of an answer.