Exploration of SpringBoot Read Configuration File Source

Keywords: Java Spring SpringBoot Lambda Attribute

1. SpringBook Read Configuration File Source Exploration

1.1. Overview

  • Springboot's source code is wrapped in the original Spring source code. Looking at the Spring source code, we all know that when we enter from debug, the original Spring source code is concentrated in refreshContext method. The main running steps of SpringBoot are basically included in this method, and this method is our transportation. The main function SpringApplication.run(Application.class, args) of the line Springboot; arrived after several steps
    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            analyzers = new FailureAnalyzers(context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            listeners.finished(context, null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        }
        catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
    }

1.2. Configure the read steps

1.2.1. prepareEnvironment

  • Configurable Environment Environment = prepareEnvironment (listeners, application Arguments) is the main step in configuring reads; we'll go further in this step.
    private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        //This is the main step.
        listeners.environmentPrepared(environment);
        if (!this.webEnvironment) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        }
        return environment;
    }
  • After creating the basic environment container, enter listeners. environment Prepared (environment); initialize environment variables through listeners, while reading configuration is part of the work.

1.2.2. environmentPrepared

  • Next, you see that the listener is looped, where by default there is only one Event Publish RunListener in the listeners
    public void environmentPrepared(ConfigurableEnvironment environment) {
        for (SpringApplicationRunListener listener : this.listeners) {
            listener.environmentPrepared(environment);
        }
    }
  • Continue as follows. This step is for broadcasting events.
    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
                this.application, this.args, environment));
    }

1.2.3. multicastEvent

    @Override
    public void multicastEvent(ApplicationEvent event) {
        multicastEvent(event, resolveDefaultEventType(event));
    }

Continuing, the focus is on the invokeListener method to call the listener event, which, as you can imagine for the configuration file, is to read the configuration event. At the same time, there are many listeners. The listener that reads the configuration file is ConfigFile Aplication Listener. Look at the name, it's quite obvious.

    @Override
    public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        invokeListener(listener, event);
                    }
                });
            }
            else {
                //A key
                invokeListener(listener, event);
            }
        }
    }

Continue. Similar to the previous step, do is a real event.

    protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        ErrorHandler errorHandler = getErrorHandler();
        if (errorHandler != null) {
            try {
                doInvokeListener(listener, event);
            }
            catch (Throwable err) {
                errorHandler.handleError(err);
            }
        }
        else {
            //A key
            doInvokeListener(listener, event);
        }
    }
    private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            // Entrance
            listener.onApplicationEvent(event);
        }
        catch (ClassCastException ex) {
            String msg = ex.getMessage();
            if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
                // Possibly a lambda-defined listener which we could not resolve the generic event type for
                // -> let's suppress the exception and just log a debug message.
                Log logger = LogFactory.getLog(getClass());
                if (logger.isDebugEnabled()) {
                    logger.debug("Non-matching event type for listener: " + listener, ex);
                }
            }
            else {
                throw ex;
            }
        }
    }

1.2.4. onApplicationEvent

  • Continue in the ConfigFileApplicationListener class
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            //Configuration Entry
            onApplicationEnvironmentPreparedEvent(
                    (ApplicationEnvironmentPreparedEvent) event);
        }
        if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent(event);
        }
    }

Continue, you can see that the processor has this, and we're looking at ConfigFile Application Listener

    private void onApplicationEnvironmentPreparedEvent(
            ApplicationEnvironmentPreparedEvent event) {
        List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
        postProcessors.add(this);
        AnnotationAwareOrderComparator.sort(postProcessors);
        for (EnvironmentPostProcessor postProcessor : postProcessors) {
            //Entrance
            postProcessor.postProcessEnvironment(event.getEnvironment(),
                    event.getSpringApplication());
        }
    }

1.2.5. postProcessEnvironment

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
            SpringApplication application) {
        //Key entrance
        addPropertySources(environment, application.getResourceLoader());
        configureIgnoreBeanInfo(environment);
        bindToSpringApplication(environment, application);
    }

Continue

    protected void addPropertySources(ConfigurableEnvironment environment,
            ResourceLoader resourceLoader) {
        RandomValuePropertySource.addToEnvironment(environment);
        //Finally, you see the loading entrance.
        new Loader(environment, resourceLoader).load();
    }

1.2.6. load

public void load() {
            this.propertiesLoader = new PropertySourcesLoader();
            this.activatedProfiles = false;
            this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
            this.processedProfiles = new LinkedList<Profile>();

            // Pre-existing active profiles set via Environment.setActiveProfiles()
            // are additional profiles and config files are allowed to add more if
            // they want to, so don't call addActiveProfiles() here.
            Set<Profile> initialActiveProfiles = initializeActiveProfiles();
            this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
            if (this.profiles.isEmpty()) {
                for (String defaultProfileName : this.environment.getDefaultProfiles()) {
                    Profile defaultProfile = new Profile(defaultProfileName, true);
                    if (!this.profiles.contains(defaultProfile)) {
                        this.profiles.add(defaultProfile);
                    }
                }
            }

            // The default profile for these purposes is represented as null. We add it
            // last so that it is first out of the queue (active profiles will then
            // override any settings in the defaults when the list is reversed later).
            this.profiles.add(null);

            while (!this.profiles.isEmpty()) {
                Profile profile = this.profiles.poll();
                for (String location : getSearchLocations()) {
                    if (!location.endsWith("/")) {
                        // location is a filename already, so don't search for more
                        // filenames
                        load(location, null, profile);
                    }
                    else {
                        for (String name : getSearchNames()) {
                            //Load entry
                            load(location, name, profile);
                        }
                    }
                }
                this.processedProfiles.add(profile);
            }

            addConfigurationProperties(this.propertiesLoader.getPropertySources());
        }
  • You can see that its load name is retrieved from getSearchNames, so take a look at this method
        private Set<String> getSearchNames() {
            if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
                return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
                        null);
            }
            return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
        }
  • CONFIG_NAME_PROPERTY is spring.config.name, DEFAULT_NAMES is application, so you can see that the name of application is the default configuration name, but it can also be modified with spring.config.name attribute.
  • In fact, at this point, there is no difficulty behind it. It can be imagined that the next step should be to stitch up a complete path, find the file to read, or go through the process.

  • Continue

        private void load(String location, String name, Profile profile) {
            String group = "profile=" + ((profile != null) ? profile : "");
            if (!StringUtils.hasText(name)) {
                // Try to load directly from the location
                loadIntoGroup(group, location, profile);
            }
            else {
                // Search for a file with the given name
                for (String ext : this.propertiesLoader.getAllFileExtensions()) {
                    if (profile != null) {
                        // Try the profile-specific file
                        loadIntoGroup(group, location + name + "-" + profile + "." + ext,
                                null);
                        for (Profile processedProfile : this.processedProfiles) {
                            if (processedProfile != null) {
                                loadIntoGroup(group, location + name + "-"
                                        + processedProfile + "." + ext, profile);
                            }
                        }
                        // Sometimes people put "spring.profiles: dev" in
                        // application-dev.yml (gh-340). Arguably we should try and error
                        // out on that, but we can be kind and load it anyway.
                        loadIntoGroup(group, location + name + "-" + profile + "." + ext,
                                profile);
                    }
                    // Also try the profile-specific section (if any) of the normal file
                    //Loading emphasis
                    loadIntoGroup(group, location + name + "." + ext, profile);
                }
            }
        }
  • Continue
        private PropertySource<?> loadIntoGroup(String identifier, String location,
                Profile profile) {
            try {
                //Entrance
                return doLoadIntoGroup(identifier, location, profile);
            }
            catch (Exception ex) {
                throw new IllegalStateException(
                        "Failed to load property source from location '" + location + "'",
                        ex);
            }
        }
  • do begins with a formal top job.
        private PropertySource<?> doLoadIntoGroup(String identifier, String location,
                Profile profile) throws IOException {
            Resource resource = this.resourceLoader.getResource(location);
            PropertySource<?> propertySource = null;
            StringBuilder msg = new StringBuilder();
            if (resource != null && resource.exists()) {
                String name = "applicationConfig: [" + location + "]";
                String group = "applicationConfig: [" + identifier + "]";
                // Load entry
                propertySource = this.propertiesLoader.load(resource, group, name,
                        (profile != null) ? profile.getName() : null);
                if (propertySource != null) {
                    msg.append("Loaded ");
                    handleProfileProperties(propertySource);
                }
                else {
                    msg.append("Skipped (empty) ");
                }
            }
            else {
                msg.append("Skipped ");
            }
            msg.append("config file ");
            msg.append(getResourceDescription(location, resource));
            if (profile != null) {
                msg.append(" for profile ").append(profile);
            }
            if (resource == null || !resource.exists()) {
                msg.append(" resource not found");
                this.logger.trace(msg);
            }
            else {
                this.logger.debug(msg);
            }
            return propertySource;
        }
  • load content, there are two loaders, see the name also know, yml end of the file must be loaded with Yaml Property Source Loader, property end with another
    public PropertySource<?> load(Resource resource, String group, String name,
            String profile) throws IOException {
        if (isFile(resource)) {
            String sourceName = generatePropertySourceName(name, profile);
            for (PropertySourceLoader loader : this.loaders) {
                if (canLoadFileExtension(loader, resource)) {
                    // Entrance for Officers
                    PropertySource<?> specific = loader.load(sourceName, resource,
                            profile);
                    addPropertySource(group, specific);
                    return specific;
                }
            }
        }
        return null;
    }
  • I use yml, so in the YamlPropertySourceLoader class
    @Override
    public PropertySource<?> load(String name, Resource resource, String profile)
            throws IOException {
        if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
            Processor processor = new Processor(resource, profile);
            //Real Processing Classes
            Map<String, Object> source = processor.process();
            if (!source.isEmpty()) {
                return new MapPropertySource(name, source);
            }
        }
        return null;
    }
        public Map<String, Object> process() {
            final Map<String, Object> result = new LinkedHashMap<String, Object>();
            //Close
            process(new MatchCallback() {
                @Override
                public void process(Properties properties, Map<String, Object> map) {
                    result.putAll(getFlattenedMap(map));
                }
            });
            return result;
        }

1.2.7. process

    protected void process(MatchCallback callback) {
        Yaml yaml = createYaml();
        for (Resource resource : this.resources) {
            //More recently
            boolean found = process(callback, yaml, resource);
            if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND && found) {
                return;
            }
        }
    }
  • Continue to deepen

  • As you can see, I finally read the contents of the document and put it in callback.
    private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
        int count = 0;
        try {
            if (logger.isDebugEnabled()) {
                logger.debug("Loading from YAML: " + resource);
            }
            //read file
            Reader reader = new UnicodeReader(resource.getInputStream());
            try {
                for (Object object : yaml.loadAll(reader)) {
                    //I finally got it.
                    if (object != null && process(asMap(object), callback)) {
                        count++;
                        if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND) {
                            break;
                        }
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Loaded " + count + " document" + (count > 1 ? "s" : "") +
                            " from YAML resource: " + resource);
                }
            }
            finally {
                reader.close();
            }
        }
        catch (IOException ex) {
            handleProcessError(resource, ex);
        }
        return (count > 0);
    }
  • The result is stored in result.

  • Then go back and look, and you'll see that it's stored in the MapPropertySource property resource, which is then used.

1.3. Summary

  • Through step by step code tracking, I analyzed the whole process of SpringBoot reading application.yml. Although the code is more pasted, it can also let beginners follow this step to understand it completely. The key steps in the code are all marked in Chinese. The other non-marked parts are not the focus of this chapter, I want to study. Self-study of research

Posted by abigail on Fri, 30 Aug 2019 03:41:21 -0700