[dieke springboot Chapter 3] prepareEnvironment

[dieke - Chapter 3] prepareEnvironment

Reading guide

  • The last article tells you how to get the running listener and what to do by calling the starting method.
  • This article focuses on the process of preparing the environment in the process of springApplication.run

1. Detailed source code

/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * Run a spring program, create and refresh a new ApplicationContext
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
    // Create a timer
		StopWatch stopWatch = new StopWatch();
    // Start timing
		stopWatch.start();
    // Create a configurable ApplicationContext variable that can be configured
		ConfigurableApplicationContext context = null;
    // Create an exception report collection
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // Configure the Headless attribute (this will not be described in detail. It roughly means to turn on a Headless mode, which will deal with programs without input and output devices such as monitors)
		configureHeadlessProperty();
    // Get running listener
		SpringApplicationRunListeners listeners = getRunListeners(args);
    // Call the listener's start method, and the listener's starting method will be called internally in batches to send events to indirectly call ApplicationListener
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      // 1.1 preparation environment
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}
  • Enter the topic, prepare environment (listeners, application arguments);
// 1.2 preparation environment
// Parameter 1: listeners: there is a previously obtained eventPublishRunListener inside
// Parameter 2: applicationArguments starts the passed in parameters
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  • Enter the prepareEnvironment
	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Get or create an environment (SERVLET, REACTIVE, NONE according to the type of application)
		ConfigurableEnvironment environment = getOrCreateEnvironment();
    // Configuration environment
		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;
	}
  • Go inside getOrCreateEnvironment()
	private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
    // Create different environments according to the program type
		switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
		}
	}
  • Enter new StandardServletEnvironment()
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
	
  @Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
	  // SERVLET_CONFIG_PROPERTY_SOURCE_NAME servletConfigInitParams
    // SERVLET_CONTEXT_PROPERTY_SOURCE_NAME servletContextInitParams
    // Add two propertySources to the StandardServletEnvironment object, which is currently empty
    // Stub stub, station, almost means that it is currently empty and occupies a position
    propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
			propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
		}
		super.customizePropertySources(propertySources);
	}
  
  // This is to initialize the servletProperties method. This method will be called in the refresh container method of starting the process, and the values of the above two propertysources will be populated
	@Override
	public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
		WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
	}

}
  • Jump to the parent class of StandardServletEnvironment, inside StandardEnvironment
public class StandardEnvironment extends AbstractEnvironment {

	/** System environment property source name: {@value}. */
	public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

	/** JVM system properties property source name: {@value}. */
	public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
    // Add two propertySources to propertySources. One is the system property and the other is the system environment, which is directly valuable
    // getSystemProperties() will get the property value of the system
    // getSystemEnvironment() will get the environment value of the system
		propertySources.addLast(
				new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		propertySources.addLast(
				new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}
}
  • OK, so far, the new ConfigurableEnvironment object is created, and then other parameters in the configuration
  • Jump to the inside of configureEnvironment
	protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
		if (this.addConversionService) {
      // Obtain a ConversionService object in singleton mode
      // ConversionService is used for type conversion. I saw that there are 136 converters after loading, such as String - Number
      // Before that, the starting() event sent in eventpublishingrunnlistener triggered a BackgroundPreinitializer 
      // This is pre initialized
			ConversionService conversionService = ApplicationConversionService.getSharedInstance();
      // Set the type conversion object to the environment
			environment.setConversionService((ConfigurableConversionService) conversionService);
		}
    // Configure attribute resources
		configurePropertySources(environment, args);
    // Configure which profile takes effect
		configureProfiles(environment, args);
	}
  • Jump to configurePropertySources
	protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    // Get the PropertySources in the environment object. After reading this, I won't forget. There should be four, right
    // System properties system environment servletConfigInitParams servletContextInitParams. The first two are available and the second is empty
		MutablePropertySources sources = environment.getPropertySources();
    // Check whether the default properties are set in the form of key value pairs when starting spring applicaiton, and if so, add them
		if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
			sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
		}
    // addCommandLineProperties defaults to true
    // See if there are any parameters passed in during startup
		if (this.addCommandLineProperties && args.length > 0) {
      // COMMAND_LINE_PROPERTY_SOURCE_NAME commandLineArgs
			String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
      // See if the resource contains commandLineArgs
			if (sources.contains(name)) {
        // Get commandLineArgs from resource
				PropertySource<?> source = sources.get(name);
        // Create a CompositePropertySource object with commandLineArgs as the key
				CompositePropertySource composite = new CompositePropertySource(name);
        // Add a PropertySource with springApplicationCommandLineArgs as the key and the passed in parameter as the value to the composite
				composite.addPropertySource(
						new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
				// And add source to the composite
        composite.addPropertySource(source);
        // Replace the commandLineArgs in sources with one containing commandLineArgs and springApplicationCommandLineArgs
				// CompositePropertySource object
				sources.replace(name, composite);
        // Summary: simply put, put the command line parameters in the attribute and the spring startup parameters into the composite, and then replace the original parameters in the attribute with the current ones
        // composite
			}
			else {
        // If only spring starts the parameters passed in, add the commandLineArgs as the key and the parameter as the value to the front of the most sources
				sources.addFirst(new SimpleCommandLinePropertySource(args));
			}
		}
	}
  • Just now, this process is so complicated. In fact, if you don't pass parameters, the above code will not be executed, so don't worry. It's mainly to add startup parameters to attribute resources
  • So far, if there are no parameters in the property resource, there are still four sources in the propertySource, and the two still have no values
  • Jump into configureProfiles()
	protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
		Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
    // Traverse the PropertySources from to get the property with the property spring.profiles.active
		profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
    // Set the effective profiles in the environment, and multiple profiles can be effective at the same time
		environment.setActiveProfiles(StringUtils.toStringArray(profiles));
	}
  • Here, configure environment (environment, applicationarguments. Getsourceargs()); It has been parsed

  • Then go back to prepareEnvironment() and continue with the next process

ConfigurationPropertySources.attach(environment);
  • Inside the attach method
	/**
	 * Attach a {@link ConfigurationPropertySource} support to the specified
	 * {@link Environment}. 
	 * 
	 * Add a ConfigurationPropertySource support to the specified environment
	 * 
	 * Adapts each {@link PropertySource} managed by the environment
	 * to a {@link ConfigurationPropertySource} and allows classic
	 * {@link PropertySourcesPropertyResolver} calls to resolve using
	 * {@link ConfigurationPropertyName configuration property names}.
	 * <p>
	 *
	 * Adjust each PropertySource of environment management to ConfigurationPropertySource,
	 * It also allows the classic PropertySourcesPropertyResolver call to solve the use problem
	 * ConfigurationPropertyName Configuration attribute name
	 * 
	 * The attached resolver will dynamically track any additions or removals from the
	 * underlying {@link Environment} property sources.
	 * 
	 * The attached parser will dynamically track any additions or deletions of the underlying environment attribute source.
	 *
	 * @param environment the source environment (must be an instance of
	 * {@link ConfigurableEnvironment})
	 * @see #get(Environment)
	 */
	// Translation:
	// Add support for a ConfigurationPropertySource to the specified environment
	public static void attach(Environment environment) {
		Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
    // Gets the PropertySources in the environment
		MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
		// ATTACHED_PROPERTY_SOURCE_NAME configurationProperties
    // Get configurationProperties in sources
    PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
    // If the sources contain configurationProperties and the source is not up-to-date, remove it
		if (attached != null && attached.getSource() != sources) {
			sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
			attached = null;
		}
		if (attached == null) {
      // Add configurationProperties of ConfigurationPropertySourcesPropertySource type at the beginning of sources,
      // Content is the sources in the environment
			sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
					new SpringConfigurationPropertySources(sources)));
		}
	}
  • At present, according to my understanding, this binding is to change the type of all sources. This type provides some methods to better track, delete, join and other operations
  • Return the prepareEnvironment() process and enter listeners.environmentPrepared(environment); inside
	void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}
  • This method is to use eventpublishingrnlistener to call the environmentPrepared function to send events prepared by the environment, thus triggering the listener to execute the method
  • Let's see which listeners are triggered
// A total of 7 listeners will be triggered
0 = {ConfigFileApplicationListener@2420} 
1 = {AnsiOutputApplicationListener@2421} 
2 = {LoggingApplicationListener@2422} 
3 = {ClasspathLoggingApplicationListener@2423} 
4 = {BackgroundPreinitializer@2424} 
5 = {DelegatingApplicationListener@2425} 
6 = {FileEncodingApplicationListener@2426} 

1.1 ConfigFileApplicationListener

	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
         // Load environment post processor
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
         // And add yourself, because ConfigFileApplicationListener itself is also the post processor of the environment
		postProcessors.add(this);
         // sort
		AnnotationAwareOrderComparator.sort(postProcessors);
         // Call their post-processing environment functions respectively
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
		}
	}
  • Enter loadPostProcessors(); inside
	List<EnvironmentPostProcessor> loadPostProcessors() {
         // Load EnvironmentPostProcessor from spring.factories
		return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class.class, getClass().getClassLoader());
	}

// Return a total of 4, plus yourself a total of 5
// 0 = {SystemEnvironmentPropertySourceEnvironmentPostProcessor@2450} 
// 1 = {SpringApplicationJsonEnvironmentPostProcessor@2451} 
// 2 = {CloudFoundryVcapEnvironmentPostProcessor@2452} 
// 3 = {ConfigFileApplicationListener@2286} 
// 4 = {DebugAgentEnvironmentPostProcessor@2453} 
  • All right, one by one
  • Enter the SystemEnvironmentPropertySourceEnvironmentPostProcessor to see what it does
/**
 * An {@link EnvironmentPostProcessor} that replaces the systemEnvironment
 * {@link SystemEnvironmentPropertySource} with an
 * {@link OriginAwareSystemEnvironmentPropertySource} that can track the
 * {@link SystemEnvironmentOrigin} for every system environment property.
 *
 * An environment post processor is used to replace the attribute resources of the system environment so that it can track and delete what is like before
 * ConfigurationPropertySources.attach(environment) Role of
 * Replace the original SystemEnvironmentPropertySource with OriginAwareSystemEnvironmentPropertySource, and the value remains unchanged
 * @author Madhura Bhave
 * @since 2.0.0
 */
public class SystemEnvironmentPropertySourceEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
    
	/**
	 * The default order for the processor.
	 */
	public static final int DEFAULT_ORDER = SpringApplicationJsonEnvironmentPostProcessor.DEFAULT_ORDER - 1;

	private int order = DEFAULT_ORDER;

	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
         // SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
		String sourceName = StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME;
         // Get 'systemEnvironment'
		PropertySource<?> propertySource = environment.getPropertySources().get(sourceName);
		if (propertySource != null) {
             // Encapsulate the value of systemEnvironment with a class
			replacePropertySource(environment, sourceName, propertySource);
		}
	}

	@SuppressWarnings("unchecked")
	private void replacePropertySource(ConfigurableEnvironment environment, String sourceName,
			PropertySource<?> propertySource) {
         // Get value
		Map<String, Object> originalSource = (Map<String, Object>) propertySource.getSource();
         // Repackage values
		SystemEnvironmentPropertySource source = new OriginAwareSystemEnvironmentPropertySource(sourceName,
				originalSource);
         // Replace the original SystemEnvironmentPropertySource in the environment with OriginAwareSystemEnvironmentPropertySource
		environment.getPropertySources().replace(sourceName, source);
	}

    ...
    ...
}
  • Enter the second spring application jsonenvironment postprocessor
/**
 * An {@link EnvironmentPostProcessor} that parses JSON from
 * {@code spring.application.json} or equivalently {@code SPRING_APPLICATION_JSON} and
 * adds it as a map property source to the {@link Environment}. The new properties are
 * added with higher priority than the system properties.
 *
 * Translation:
 * 
 * @author Dave Syer
 * @author Phillip Webb
 * @author Madhura Bhave
 * @author Artsiom Yudovin
 * @since 1.3.0
 */
public class SpringApplicationJsonEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
         // Get all propertysources
		MutablePropertySources propertySources = environment.getPropertySources();
         // Find out whether there is spring.application.json. If so, parse the value and store it in the environment
		propertySources.stream().map(JsonPropertyValue::get).filter(Objects::nonNull).findFirst()
				.ifPresent((v) -> processJson(environment, v));
	}

	private void processJson(ConfigurableEnvironment environment, JsonPropertyValue propertyValue) {
		JsonParser parser = JsonParserFactory.getJsonParser();
         // Get the value of spring.application.json and convert it into map
		Map<String, Object> map = parser.parseMap(propertyValue.getJson());
		if (!map.isEmpty()) {
             // Add to environment
			addJsonPropertySource(environment, new JsonPropertySource(propertyValue, flatten(map)));
		}
	}
    
	...
	...
}
  • The simple way to use it is to add a system property of spring.application.json, and then the value is in the form of JSON. You can add the property to the propertysource
  • Enter the third cloud foundry vcapenvironment postprocessor
I don't know what this is
  • Enter the fourth ConfigFileApplicationListener
// Extract the post processor part of ConfigFileApplicationListener
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
	
    ...
	...
        
	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		addPropertySources(environment, application.getResourceLoader());
	}
    
	protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
        // Add a RandomValuePropertySource to the propertysource
		RandomValuePropertySource.addToEnvironment(environment);
        // Create a Loader and execute the load() method
		new Loader(environment, resourceLoader).load();
	}
    
	...
	...
}
  • Enter new Loader() (Loader is the internal class of ConfigFileApplicationListener)
		Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
			this.environment = environment;
            // Create a placeholder parser with PropertyPlaceholderHelper inside. The default placeholder is ${}
			this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
			this.resourceLoader = (resourceLoader != null) ? resourceLoader
					: new DefaultResourceLoader(getClass().getClassLoader());
            // Load the PropertySourceLoader from spring.factories and load the following two
            // 0 = {PropertiesPropertySourceLoader@2621 }Used to load resources with suffix properties
            // 1 = {YamlPropertySourceLoader@2622 }Used to load resources with the suffix yaml
			this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
					getClass().getClassLoader());
		}
  • Once the loader is created, use it to load resources
  • Enter the inside of load()
		void load() {
            // Filter and load resources according to attributes, etc
			FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
					(defaultProperties) -> {
						this.profiles = new LinkedList<>();
						this.processedProfiles = new LinkedList<>();
						this.activatedProfiles = false;
						this.loaded = new LinkedHashMap<>();
                          // Initialize configuration
						initializeProfiles();
						while (!this.profiles.isEmpty()) {
                               // Take out the first one
							Profile profile = this.profiles.poll();
                               // Determine whether it is the default configuration
							if (isDefaultProfile(profile)) {
                                   // Add configuration to environment
								addProfileToEnvironment(profile.getName());
							}
							load(profile, this::getPositiveProfileFilter,
									addToLoaded(MutablePropertySources::addLast, false));
							this.processedProfiles.add(profile);
						}
						load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
						addLoadedPropertySources();
						applyActiveProfiles(defaultProperties);
					});
		}
  • Enter FilteredPropertySource.apply()
	static void apply(ConfigurableEnvironment environment, String propertySourceName, Set<String> filteredProperties,
			Consumer<PropertySource<?>> operation) {
        // Get all propetiesource s. At present, there should be only 6. RandomValuePropertySource has just been added recently
		MutablePropertySources propertySources = environment.getPropertySources();
        // Gets the resource named defaultProperties in the resource
		PropertySource<?> original = propertySources.get(propertySourceName);
		if (original == null) {
            // If not, call the incoming anonymous method directly
			operation.accept(null);
			return;
		}
        // If so, replace the resource with FilteredPropertySource and add two additional properties, spring.profiles.active and spring.profiles.include, for filtering
		propertySources.replace(propertySourceName, new FilteredPropertySource(original, filteredProperties));
		try {
			operation.accept(original);
		}
		finally {
			propertySources.replace(propertySourceName, original);
		}
	}
  • To sum up, the configuration file is loaded according to the passed in parameters and the effective profile
  • The last one is DebugAgentEnvironmentPostProcessor, which enters the DebugAgentEnvironmentPostProcessor
	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		if (ClassUtils.isPresent(REACTOR_DEBUGAGENT_CLASS, null)) {
			Boolean agentEnabled = environment.getProperty(DEBUGAGENT_ENABLED_CONFIG_KEY, Boolean.class);
			if (agentEnabled != Boolean.FALSE) {
				try {
					Class<?> debugAgent = Class.forName(REACTOR_DEBUGAGENT_CLASS);
					debugAgent.getMethod("init").invoke(null);
				}
				catch (Exception ex) {
					throw new RuntimeException("Failed to init Reactor's debug agent");
				}
			}
		}
	}
  • Used to quickly load this class

1.2 AnsiOutputApplicationListener

  • Used to configure ansi encoding

1.3 LoggingApplicationListener

  • Set the configuration log according to the configured properties

1.4 ClasspathLoggingApplicationListener

  • Print the classpath information according to the output level of the log

1.5 BackgroundPreinitializer

  • No action will be performed in this event

1.6 DelegatingApplicationListener

  • No action will be performed in this event

1.7 FileEncodingApplicationListener

  • Judge whether the file type conforms to the configuration according to the configuration

Tags: Java Spring Spring Boot

Posted on Sun, 10 Oct 2021 07:03:13 -0400 by Dumps