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;
- EventPublishingRunListener.environmentPrepared(): operate each;
- SpringApplicationRunListeners.environmentPrepared(): listen for environment preparation events;
- 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;
- PropertySourceBootstrapConfiguration.initialize(): externalize configuration;
- 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;
- SpringApplication.applyInitializers(): additional operations to initialize the context;
- StopWatch.stop(): stop timing;
- SpringApplicationRunListeners.started(): publish the ApplicationStartedEvent event;
- SpringApplicationRunListeners.running(): listen for program running events;
- EventPublishingRunListener.running(): publish ApplicationReadyEvent events;