SpringCloud | container SpringCloud depth analysis of father and son

Outline

Spring container will create multiple projects in the introduction of SpringCloud herein, this source from the perspective of in-depth analysis of specific points which will create the Spring container, as well as the differences and relations between these containers.

This article describes the Spring container is based on Finchley.RELEASE related projects.


Here Insert Picture Description

Container roughly divided into three layers:

  • BootStrap Spring container: SpringCloud created by the listener, to initialize the context SpringCloud
  • SpringBoot Spring container: Created by SpringBoot, the project is also commonly used in the Spring container.
  • Spring micro-services associated container: Ribbon context Feign and configuration corresponding to a class, NamedContextFactory abstract factory created by the configuration of the container, the container for isolation.

Taken separately.


BootStrap Spring容器

First look at BootStrap Spring container when it is created.

In the previous blog " SpringBoot2 | Process source code analysis (a) SpringBoot start " in reference to the SpringBoot at startup, it will trigger a series of related listeners, listeners carry out their duties, to do some initialization preprocessing operation. SpringCloud realized his listeners: BootstrapApplicationListenerto initialize SpringCloud context.

Look after the listener is triggered processing logic:

@Override
	public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
		ConfigurableEnvironment environment = event.getEnvironment();
		//如果未开启SpringCloud,直接返回
		if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
				true)) {
			return;
		}
		// don't listen to events in a bootstrap context
		//判断该监听器是否已经执行过,如果执行过,直接返回
		if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
			return;
		}
		//这里返回了一个 Spring 容器
		ConfigurableApplicationContext context = bootstrapServiceContext(environment,
				event.getSpringApplication());
		apply(context, event.getSpringApplication(), environment);
	}

bootstrapServiceContextMethod creates a Spring container: ConfigurableApplicationContext, take a look:

private ConfigurableApplicationContext bootstrapServiceContext(
			ConfigurableEnvironment environment, final SpringApplication application) {
		StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
		MutablePropertySources bootstrapProperties = bootstrapEnvironment
				.getPropertySources();
		for (PropertySource<?> source : bootstrapProperties) {
			bootstrapProperties.remove(source.getName());
		}
		//设置读取 bootstrap 文件
		String configName = environment
				.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
		//设置 bootstrap 文件路径
		String configLocation = environment
				.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
		Map<String, Object> bootstrapMap = new HashMap<>();
		bootstrapMap.put("spring.config.name", configName);
		if (StringUtils.hasText(configLocation)) {
			bootstrapMap.put("spring.config.location", configLocation);
		}
		//设置是否已经初始化BootStrap环境
		bootstrapProperties.addFirst(
				new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
		for (PropertySource<?> source : environment.getPropertySources()) {
			bootstrapProperties.addLast(source);
		}
			//......
			//加载BootstrapConfiguration 配置类
			List<String> names = SpringFactoriesLoader
				.loadFactoryNames(BootstrapConfiguration.class, classLoader);
			for (String name : StringUtils.commaDelimitedListToStringArray(
					environment.getProperty("spring.cloud.bootstrap.sources", ""))) {
				names.add(name);
			}
		//创建 Spring 容器
		SpringApplicationBuilder builder = new SpringApplicationBuilder()
				.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
				.environment(bootstrapEnvironment)
				.properties("spring.application.name:" + configName)
				.registerShutdownHook(false)
				.logStartupInfo(false)
				.web(false);
		List<Class<?>> sources = new ArrayList<>();
	builder.sources(sources.toArray(new Class[sources.size()]));
	AnnotationAwareOrderComparator.sort(sources);
	final ConfigurableApplicationContext context = builder.run();
	//创建祖先容器
	addAncestorInitializer(application, context);
	bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
	mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
	return context;
}

First, the SpringBootproject is by SpringApplicationBuilderstarting in the above logic, he also built an SpringApplicationBuilderobject execution method run again, it will start the process to perform twice, different profiles and configuration class just read. Before I was asked, SpringCloud project ApplicationContextInitializerimplementation class in logic to perform twice, the reason is that the startup process is executed twice.

Similarly, when the second create SpringApplicationBuildertime and starts, the listener will not start again, and then create SpringApplicationBuilderit?
Certainly not. Otherwise, it is the cycle of death. As already mentioned, SpringCloud by identifier BOOTSTRAP_PROPERTY_SOURCE_NAMEjudged. After the implementation of the listener, it will set the variable corresponding to the value before the next start if there is value, indicating been performed.

There are top line of the key code: addAncestorInitializer(application, context);
ancestorancestors mean, look:

private void addAncestorInitializer(SpringApplication application,
			ConfigurableApplicationContext context) {
		boolean installed = false;
		//遍历所有的initializer,判断是否已经存在 祖先initializer
		for (ApplicationContextInitializer<?> initializer : application
				.getInitializers()) {
			if (initializer instanceof AncestorInitializer) {
				installed = true;
				// 如果存在,则设置 bootStrapApplication
				((AncestorInitializer) initializer).setParent(context);
			}
		}
		//如果不存在,则创建。
		if (!installed) {
			application.addInitializers(new AncestorInitializer(context));
		}
	}

Essentially creating AncestorInitializer object, and pass BootStrap Application SpringCloud created. That is the ancestor of the container.
When SpringCloud BootStrap environment initialization is completed, to return to SpringBoot initialization process, SpringBoot will trigger all initializers, when executed ancestors initializer: AncestorInitializer, it will BootStrapApplication container to the parent container:

private static class AncestorInitializer implements
			ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
	private ConfigurableApplicationContext parent;

	public AncestorInitializer(ConfigurableApplicationContext parent) {
		this.parent = parent;
	}
	@Override
	public void initialize(ConfigurableApplicationContext context) {
		//如果已经存在父容器,则直接取出
		while (context.getParent() != null &amp;&amp; context.getParent() != context) {
			context = (ConfigurableApplicationContext) context.getParent();
		}
		reorderSources(context.getEnvironment());
		//设置父容器
		new ParentContextApplicationContextInitializer(this.parent)
				.initialize(context);
	}

}

The above-described method of setting a logical entrusted to the parent container ParentContextApplicationContextInitializerhandling, look at the initializemethod:

public class ParentContextApplicationContextInitializer implements
		ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private int order = Ordered.HIGHEST_PRECEDENCE;

private final ApplicationContext parent;

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
	if (applicationContext != this.parent) {
		//设置父容器
		applicationContext.setParent(this.parent);
		//创建监听器,主要用来发布项目中存在父子容器事件
		applicationContext.addApplicationListener(EventPublisher.INSTANCE);
	}
}

}

Action BootStrap Application container:
early loading SpringCloud related configuration class, such as pre-configured BootStrap Application loaded center configuration class, plus priority reads bootstrapconfiguration files logic.

Load default configuration is as follows:
Here Insert Picture Description


SpringBoot Spring container

SpringBoot create a Spring container is the core container is the most used Spring container.
Objects created there will be three types, Servlet, Reactive, and default.
SpringBoot2.x version determined as follows:

public class SpringApplication {
	//......
	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, "
								+ "please specify an ApplicationContextClass",
						ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}
	//......
}

具体细节不多介绍了,创建流程可参考之前 SpringBoot启动流程源码分析文章。


微服务配置容器

上面 uml 图中提到了一个关键类:NamedContextFactory,从命名可以看出,这是一个工厂类:抽象容器工厂。同 hystrix 线程隔离原理一样,该工厂根据不同的服务名称,创建不同的容器。该容器有2个实现类,FeignContextSpringClientFactory,分别用来加载对应的配置。
Here Insert Picture Description
来看一下相关的核心代码:

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {
//Feign 和Ribbon 配置抽象接口
public interface Specification {
	String getName();
	Class&lt;?&gt;[] getConfiguration();
}

//Application集合
private Map&lt;String, AnnotationConfigApplicationContext&gt; contexts = new ConcurrentHashMap&lt;&gt;();


protected AnnotationConfigApplicationContext getContext(String name) {
		//根据服务名称获取对应配置工厂,如果没有,则创建
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
					//创建并进行缓存
					this.contexts.put(name, createContext(name));
				}
			}
		}
		return this.contexts.get(name);
	}

protected AnnotationConfigApplicationContext createContext(String name) {
	//创建一个 Spring 容器
	AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
	if (this.configurations.containsKey(name)) {
		for (Class&lt;?&gt; configuration : this.configurations.get(name)
				.getConfiguration()) {
			//注入配置类
			context.register(configuration);
		}
	}
	//注入默认的Feign或Ribbon配置类
	for (Map.Entry&lt;String, C&gt; entry : this.configurations.entrySet()) {
		if (entry.getKey().startsWith("default.")) {
			for (Class&lt;?&gt; configuration : entry.getValue().getConfiguration()) {
				context.register(configuration);
			}
		}
	}
	context.register(PropertyPlaceholderAutoConfiguration.class,
			this.defaultConfigType);
	context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
			this.propertySourceName,
			Collections.&lt;String, Object&gt; singletonMap(this.propertyName, name)));
	if (this.parent != null) {
		// Uses Environment from parent as well as beans
		//设置父类为 SpringBoot 创建的Spring 容器
		context.setParent(this.parent);
	}
	//启动容器
	context.refresh();
	return context;
}

}

具体执行细节这里不做展开了,之前的文章《SpringCloud | SpringCloud Feign的前世今生【源码深入分析】》有详细介绍。

所以,具体Feign 和 Ribbon配置类会创建多少实例,和项目本身依赖发服务有关。如果依赖10个服务,那就是20个微服务配置容器+SpringBoot容器+BootStrap容器。哪里看到呢?
如果项目引入了SpringBoot 监控模块Spring Boot Actuator,那在idea中可以看到已经创建的容器:
Here Insert Picture Description

注意:由于Ribbon 默认会采用懒加载,也就是只有第一次请求的时候才会加载。所以idea这里不会显示 Ribbon 相关配置类容器,只显示项目启动流程中创建完成的 Spring 容器。
这也是微服务经常第一次超时的根本原理,创建并启动一个Spring容器需要一定的时间。


总结

本篇主要介绍了 SpringCloud 项目中创建的 Spring 容器:

First SpringBoot project started, trigger listener, if introduced SpringCloud in BootstrapApplicationListener, then start initialization SpringCloud relevant context: Bootstrap ApplicationContext, set it to the ancestors of the container, and then proceed to create its children: SpringBoot Application.

If you introduce FeignClient, it will instantiate a container factory, service name key, Feign Ribbon and container configuration class value, given isolation configuration, the parent container are SpringBoot Application.

Guess you like

Origin blog.csdn.net/u014513171/article/details/93211304