Microservice architecture * 2.3 Spring Cloud startup and loading configuration file source code analysis (taking Nacos as an example)

preface

reference material:
<Spring Microservices in Action>
Principles and practice of Spring Cloud Alibaba microservice
"Spring cloud framework development tutorial in Silicon Valley of station B" Zhou Yang

To realize unified configuration management, Spring Cloud needs to solve two problems: how to obtain remote server configuration and how to dynamically update configuration; Before that, we need to know when Spring Cloud will load the configuration file for us;

1. When does spring cloud load the configuration file

  • Firstly, Spring abstracts an Environment to represent the Spring application Environment configuration, integrates various external environments, and provides a unified access method getProperty();
  • When the Spring application starts, the configuration is loaded into the Environment. When creating a Bean, you can inject some attribute values into the business code in the form of @ Value from the Environment;
  • Spring Cloud is based on the development of spring, so the configuration file is loaded at startup. The startup program of Spring Cloud is the main program class XxxApplication, and we enter the main program class at the breakpoint;
@EnableDiscoveryClient
@SpringBootApplication
public class ProviderApplication {
	public static void main(String[] args) {
	    //[breakpoint] main startup method
		SpringApplication.run(ProviderApplication.class, args);
	}
}
  • In spring application The Environment environment will be prepared in the run () method;
public ConfigurableApplicationContext run(String... args) {
        //Initialize StopWatch and call the start method to start timing
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        //Set system properties Java awt. Headless, which is true here, means that it runs on the server side, works in the mode without display, mouse and keyboard, and simulates the functions of input and output devices
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        //Spring applicationrunlisteners listener work -- > publish ApplicationStartingEvent event
        listeners.starting();

        Collection exceptionReporters;
    
    try {
        //Holding args parameter
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        
        //[2.] Prepare Environment -- > publish ApplicationEnvironmentPreparedEvent event
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        //Print banner
        Banner printedBanner = this.printBanner(environment);
        //Create SpringBoot context
        context = this.createApplicationContext();
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
        
        //[3.] Prepare application context -- > publish ApplicationEnvironmentPreparedEvent event
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        //Refresh context -- > publish ContextRefreshedEvent event
        this.refreshContext(context);
        //After the container finishes refreshing, call the registered Runners in turn
        this.afterRefresh(context, applicationArguments);
        //Stop timing
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }
        //Spring applicationrunlisteners listener work -- > publish ApplicationStartedEvent event
        listeners.started(context);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, exceptionReporters, listeners);
        throw new IllegalStateException(var10);
    }
    
    try {
        //[4.] SpringApplicationRunListeners listener run event -- > publish ApplicationReadyEvent event
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}
  • Note that some students may not be able to debug the try statement here. It may be because the following dependency is introduced and can be removed;
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>

2. Prepare Environment configuration Environment

2.1 configure Environment springapplication prepareEnvironment()

  • Let's go to spring application In the prepareEnvironment () method, the method mainly configuring the Environment environment according to some information, and then invokes the SpringApplicationRunListeners (Spring application running listener) listener.
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
    //Get configurable environment
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    //Configure the Environment environment according to the configurable Environment
    this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
    //[breakpoint entry] tell the listener that the Environment is ready
    listeners.environmentPrepared((ConfigurableEnvironment)environment);
    this.bindToSpringApplication((ConfigurableEnvironment)environment);
    if (!this.isCustomEnvironment) {
        environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
    }
  • Go to springapplicationrunlisteners Environmentprepared () method, which is used to traverse each listener and operate on these listeners;
public void environmentPrepared(ConfigurableEnvironment environment) {
    //Traversing listeners using iterators
    Iterator var2 = this.listeners.iterator();
    while(var2.hasNext()) {
        SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
        //[breakpoint entry] operate on each listener
        listener.environmentPrepared(environment);
    }
}

2.2 use the event master to create and publish the event simpleapplicationeventmulticast multicastEvent()

  • Enter eventpublishing runlistener Environmentprepared() method. It is found that the operations implemented for each listener are: publishing an ApplicationEnvironmentPreparedEvent (application environment preparation completion event) using simpleapplicationeventmulticast (event master);
public void environmentPrepared(ConfigurableEnvironment environment) {
    //[breakpoint entry] use the event master to publish events
    this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
  • Enter simpleapplicationeventmulticast Multicastevent() method
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
    //Resolve event type
    ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
    //Get event performer
    Executor executor = this.getTaskExecutor();
    //Get listener iterator
    Iterator var5 = this.getApplicationListeners(event, type).iterator();

    while(var5.hasNext()) {
        ApplicationListener<?> listener = (ApplicationListener)var5.next();
        //If there is an executor, the event is published through the executor
        if (executor != null) {
            executor.execute(() -> {
                this.invokeListener(listener, event);
            });
        } else {
        //[1.2] send an event directly without an executor
            this.invokeListener(listener, event);
        }
    }
}
  • To sum up, after configuring the Environment, Spring Cloud will release an applicationenvironment preparedevent (application Environment preparation completion event) to tell all listeners that the Environment has been configured;
  • All listeners interested in the ApplicationEnvironmentPreparedEvent event will listen to this event. Including bootstrap applicationlistener;

  • Note that the ApplicationEnvironmentPreparedEvent event needs to be traversed here, and other events may be in front of it;

2.3 bootstrap applicationlistener handles events and automatically imports some configuration classes

  • After publishing the event, it is listened by Bootstrap applicationlistener (Bootstrap listener), and Bootstrap applicationlistener is called The onapplicationevent() method handles events:
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    //Get the current Environment
    ConfigurableEnvironment environment = event.getEnvironment();
    //If the environment is available
    if ((Boolean)environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) {
        //If the environment source does not contain bootstrap
        if (!environment.getPropertySources().contains("bootstrap")) {
            ConfigurableApplicationContext context = null;
            String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
            Iterator var5 = event.getSpringApplication().getInitializers().iterator();

            while(var5.hasNext()) {
                ApplicationContextInitializer<?> initializer = (ApplicationContextInitializer)var5.next();
                if (initializer instanceof ParentContextApplicationContextInitializer) {
                    context = this.findBootstrapContext((ParentContextApplicationContextInitializer)initializer, configName);
                }
            }

            if (context == null) {
                //[breakpoint] add a configuration that is not a bootstrap source to the bootstrap context
                context = this.bootstrapServiceContext(environment, event.getSpringApplication(), configName);
                event.getSpringApplication().addListeners(new ApplicationListener[]{new BootstrapApplicationListener.CloseContextOnFailureApplicationListener(context)});
            }

            this.apply(context, event.getSpringApplication(), environment);
        }
    }
}
  • Enter bootstrap applicationlistener Bootstrapservicecontext() method. It is found that its main task is to configure the implementation of automatic import;
private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment, final SpringApplication application, String configName) {

    //Omit other codes
    //Implementation of configuration automatic assembly
    builder.sources(new Class[]{BootstrapImportSelectorConfiguration.class});
    return context;
}
  • We searched the Bootstrap importselectorconfiguration (Bootstrap import configuration class) class and found that it uses Bootstrap importselector (Bootstrap import selector) for automatic configuration;
@Configuration
@Import({BootstrapImportSelector.class}) //Use the bootstrap importselector class for automatic configuration
public class BootstrapImportSelectorConfiguration {
    public BootstrapImportSelectorConfiguration() {
    }
}
  • The selectImports() method of the Bootstrap importselector class uses the SPI mechanism in Spring;
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    
    //Omit other codes
    
    List<String> names = new ArrayList(SpringFactoriesLoader.loadFactoryNames(BootstrapConfiguration.class, classLoader));
}
  • Go to the classpath path to find meta-inf / spring Some predefined extension points of factories, where key is bootstrap configuration;
  • It can be seen that some are imported to us, including two configuration classes propertysourcebootstrap configuration and nacosconfigbbootstrap configuration (these two classes will be mentioned in 3.2 and 3.3 of this chapter to load additional configurations);

3. Refresh application context

3.1 refresh context springapplication prepareContext()

  • Let's go back to spring application In the run () method, after preparing the Environment environment, spring application will be called The preparecontext() method refreshes the application context. We enter the method;
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    //Set the Environment environment of the context
    context.setEnvironment(environment);
    this.postProcessApplicationContext(context);
    //[3.2] initialization context
    this.applyInitializers(context);
    //[3.4] publish context initialization completion event ApplicationContextInitializedEvent 
    listeners.contextPrepared(context);
    
    //The following code is omitted

    //[breakpoint 3.5] this is the last statement of the method, which is released 
    listeners.contextLoaded(context);
}

3.2 additional operations for initializing context springapplication applyInitializers()

  • Enter springapplication Applyinitializers () method to do some additional operations during application context initialization;
  • That is, perform additional operations not officially provided by Spring Cloud, which refers to Nacos's own operations;
protected void applyInitializers(ConfigurableApplicationContext context) {
    Iterator var2 = this.getInitializers().iterator();

    while(var2.hasNext()) {
        ApplicationContextInitializer initializer = (ApplicationContextInitializer)var2.next();
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        //[breakpoint entry] traverse the iterator and initialize the context
        initializer.initialize(context);
    }
}
  • there. The initialize () method finally calls propertysourcebootstrap configuration (Bootstrap property source configuration class), which is automatically imported when preparing the Environment environment in 1.2. We enter PropertySourceBootstrapConfiguration initialize() method to draw the final conclusion:
public void initialize(ConfigurableApplicationContext applicationContext) {

    //Omit other codes
 
    Iterator var5 = this.propertySourceLocators.iterator();

    while(var5.hasNext()) {
        //The main function of PropertySourceLocator interface is to realize application externalization configuration and dynamic loading
        PropertySourceLocator locator = (PropertySourceLocator)var5.next();
        PropertySource<?> source = null;
        //[breakpoint] read the configuration in the Nacos server
        source = locator.locate(environment);
    }
}

3.3 read the configuration of nacospropertysourcelocator in the Nacos server locate()

  • The configuration class NacosPropertySourceLocator that we introduced in the 1.2 Environment environment realized the interface, so we finally called NacosPropertySourceLocator.. Locate() method;
  • This method will read the configuration in the Nacos server, save the structure to the instance of PropertySource and return;
  • NacosPropertySourceLocator. The source code of locate() method is as follows:
@Override
public PropertySource<?> locate(Environment env) {
    //Get the configuration server instance, which is a class provided by the Nacos client to access and implement the basic operations of the configuration center
	ConfigService configService = nacosConfigProperties.configServiceInstance();
	if (null == configService) {
		log.warn("no instance of config service found, can't load config from nacos");
		return null;
	}
	long timeout = nacosConfigProperties.getTimeout();
	//Nacos attribute source generator
	nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
	String name = nacosConfigProperties.getName();

    //DataId prefix
	String dataIdPrefix = nacosConfigProperties.getPrefix();
	if (StringUtils.isEmpty(dataIdPrefix)) {
		dataIdPrefix = name;
	}
    //If the DataId prefix is not configured, use spring application. The value of the name attribute
	if (StringUtils.isEmpty(dataIdPrefix)) {
		dataIdPrefix = env.getProperty("spring.application.name");
	}
    //Create composite attribute source
	CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME);

    //Load shared configuration
	loadSharedConfiguration(composite);
	//Load external configuration
	loadExtConfiguration(composite);
	//Load the configuration corresponding to the application name on the Nacos server
	loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);

	return composite;
}

3.4 after initialization, publish the ApplicationContextInitializedEvent event

  • Go to springapplicationrunlisteners Contextprepared() publishes events;
public void contextPrepared(ConfigurableApplicationContext context) {
    //Construct iterator
    Iterator var2 = this.listeners.iterator();
    while(var2.hasNext()) {
        SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
        //[breakpoint] publish event
        listener.contextPrepared(context);
    }
}
  • Enter eventpublishing runlistener Contextprepared(), publish the event through multicast event;
public void contextPrepared(ConfigurableApplicationContext context) {
    //Publish ApplicationContextInitializedEvent event
    this.initialMulticaster.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
}

3.5 after loading the configuration, add the listener to the context

  • Note that the method is different from that in 3.4;
  • Go to springapplicationrunlisteners Contextloaded() configuration loading completed;
public void contextLoaded(ConfigurableApplicationContext context) {
    Iterator var2 = this.listeners.iterator();

    while(var2.hasNext()) {
        SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
        //[breakpoint entry]
        listener.contextLoaded(context);
    }
}
  • Enter eventpublishing runlistener Contextloaded() adds the listener to the context environment;
public void contextLoaded(ConfigurableApplicationContext context) {
    ApplicationListener listener;
    //Traverse each listener (a total of 13, as shown in the figure below), and add listeners other than the last listener to the context
    for(Iterator var2 = this.application.getListeners().iterator(); var2.hasNext(); context.addApplicationListener(listener)) {
        listener = (ApplicationListener)var2.next();
        if (listener instanceof ApplicationContextAware) {
            //The 10th ParentContextCloseApplicationListener will come in
            ((ApplicationContextAware)listener).setApplicationContext(context);
        }
    }
    //Publish ApplicationPreparedEvent events
    this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}


4. Program running events

  • Go to springapplicationrunlisteners The running () method operates on each listener;
public void running(ConfigurableApplicationContext context) {
    Iterator var2 = this.listeners.iterator();
    while(var2.hasNext()) {
        SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
        //[breakpoint entry] operation listener, including eventpublishing runlistener
        listener.running(context);
    }
}
  • Enter eventpublishing runlistener The running () method publishes the ApplicationReadyEvent event;
public void running(ConfigurableApplicationContext context) {
    context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
}

5. Summary of source code structure diagram of spring cloud startup and loading configuration file

  • SpringApplication.run(): load the configuration context;
    • stopWatch.start(): initialize StopWatch and call the start method to start timing;
    • SpringApplicationRunListeners.starting(): publish the ApplicationStartingEvent event;
    • SpringApplication.prepareEnvironment(): prepare the Environment;
      • SpringApplicationRunListeners.environmentPrepared(): listen for environment preparation events;
        • EventPublishingRunListener.environmentPrepared(): operate each;
          • SimpleApplicationEventMulticaster.multicastEvent(): use the event master to publish the ApplicationEnvironmentPreparedEvent event;
          • BootstrapApplicationListener.onApplicationEvent(): bootstrap listener handles events;
            • BootstrapApplicationListener. Bootstrap servicecontext (): automatically import some configuration classes for us;
    • SpringApplication.printBanner(): print Banner;
    • printBanner.createApplicationContext(): creates a Spring Boot context;
    • SpringApplication.prepareContext(): refresh the application context;
      • SpringApplication.applyInitializers(): additional operations to initialize the context;
        • PropertySourceBootstrapConfiguration.initialize(): externalize configuration;
          • NacosPropertySourceLocator.locate(): read the configuration in the Nacos server;
      • SpringApplicationRunListeners.contextPrepared(): configuration initialization is completed;
        • EventPublishingRunListener.contextPrepared(): publishes the ApplicationContextInitializedEvent event;
      • SpringApplicationRunListeners.contextPrepared(): configuration loading completed
        • EventPublishingRunListener.contextLoaded(): add the listener to the context environment;
    • StopWatch.stop(): stop timing;
    • SpringApplicationRunListeners.started(): publish the ApplicationStartedEvent event;
    • SpringApplicationRunListeners.running(): listen for program running events;
      • EventPublishingRunListener.running(): publish ApplicationReadyEvent events;

last

Newcomer production, if there are mistakes, welcome to point out, thank you very much! Welcome to the official account and share some more everyday things. If you need to reprint, please mark the source!

Keywords: Distribution Spring Cloud Microservices config

Added by tstout2 on Sat, 22 Jan 2022 15:45:57 +0200