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