SpringCloud | 아버지와 아들의 용기 SpringCloud 깊이 분석

개요

스프링 컨테이너는 본원 SpringCloud 도입 특정 스프링 컨테이너를 생성 할 점뿐만 아니라, 이들 용기 간의 차이와의 관계에 대한 심층 분석의 관점에서 본 소스를 다수의 프로젝트를 작성한다.

이 글은 스프링 컨테이너가 Finchley.RELEASE 관련 프로젝트를 기반으로 설명합니다.


여기에 그림 삽입 설명

컨테이너는 대략 세 가지 층으로 나누어 :

  • 부트 스트랩 봄 컨테이너 : SpringCloud 상황 SpringCloud를 초기화 리스너에 의해 생성
  • SpringBoot 스프링 컨테이너 : SpringBoot에 의해 만들어진이 프로젝트는 또한 일반적으로 스프링 컨테이너에 사용됩니다.
  • 클래스, 컨테이너의 구성, 분리를위한 컨테이너에 의해 생성 NamedContextFactory 추상적 인 공장에 해당하는 리본 상황 척하기 및 구성 : 봄 마이크로 서비스는 컨테이너 관련.

별도로 촬영.


부트 스트랩 봄 容器

이 생성 될 부트 스트랩 스프링 컨테이너에서 첫 모습.

이전 블로그에서 " | SpringBoot2 프로세스 소스 코드 분석의 (a)를 시작 SpringBoot 시작시 SpringBoot을 참조"는 관련 청취자의 일련의 트리거, 청취자는 일부 초기화 전처리 작업을 수행하기 위해, 자신의 임무를 수행. : SpringCloud 그의 청취자를 실현 BootstrapApplicationListenerSpringCloud 컨텍스트를 초기화 할 수 있습니다.

리스너가 트리거 된 후 처리 로직을 봐 :

@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);
	}

bootstrapServiceContext방법은 Spring 컨테이너를 만듭니다 ConfigurableApplicationContext가 살펴 :

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;
}

첫째, SpringBoot프로젝트입니다 SpringApplicationBuilder위의 논리에 시작, 그는 또한 내장 SpringApplicationBuilder이 과정을 두 번, 다른 프로필 및 구성 클래스는 방금 읽은 수행하기 시작, 오브젝트의 실행 방법을 다시 실행합니다. 내가 질문을 받았다 전에 SpringCloud 프로젝트 ApplicationContextInitializer를 두 번 수행 할 수있는 논리의 구현 클래스는, 그 이유는 시작 프로세스가 두 번 실행되는 것입니다.

(가) 두 번째 만들 때 마찬가지로, SpringApplicationBuilder시간과 시작, 리스너는 다시 시작되지 않습니다, 다음 생성 SpringApplicationBuilder을?
물론 없습니다. 그렇지 않으면, 그것은 죽음의주기이다. 이미 언급 한 바와 같이, 식별자는 SpringCloud BOOTSTRAP_PROPERTY_SOURCE_NAME판정. 수행 나타내는 값이 있으면, 청취자의 실행 후, 다음 개시전의 값에 대응하는 변수를 설정한다.

키 코드의 맨 윗줄이 있습니다 addAncestorInitializer(application, context);
ancestor조상, 봐 의미 :

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));
		}
	}

기본적으로 AncestorInitializer 객체를 생성하고, 부트 스트랩 응용 프로그램 SpringCloud 만들어 전달합니다. 즉, 컨테이너의 조상이다.
SpringCloud 부트 스트랩 환경 초기화가 완료되면, SpringBoot 모든 초기화를 트리거, SpringBoot 초기화 프로세스로 돌아가려면, 조상의 초기화를 실행되는 경우 AncestorInitializer, 그것은 부모 컨테이너에 컨테이너를 BootStrapApplication합니다 :

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);
	}

}

부모 컨테이너 위탁 논리 설정의 전술 한 방법 ParentContextApplicationContextInitializer처리는보고 initialize방법

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);
	}
}

}

액션 부트 스트랩 응용 프로그램 컨테이너 :
초기 로딩 SpringCloud 관련 구성 클래스는 같은 사전 구성된 부트 스트랩 응용 프로그램로드 센터 구성 클래스로, 플러스 우선 순위는 읽고 bootstrap구성 파일 논리를.

다음로드 기본 구성은 다음과 같습니다
여기에 그림 삽입 설명


SpringBoot 스프링 컨테이너

Spring 컨테이너를 생성 SpringBoot하는 핵심 컨테이너가 가장 많이 사용 스프링 컨테이너입니다.
객체는 세 가지 유형, 서블릿, 반응성 및 기본있을 것 만들었습니다.
다음과 같이 SpringBoot2.x 버전이 결정 :

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,分别用来加载对应的配置。
여기에 그림 삽입 설명
来看一下相关的核心代码:

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中可以看到已经创建的容器:
여기에 그림 삽입 설명

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


总结

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

에 SpringCloud를 도입하는 경우 먼저 SpringBoot 프로젝트는, 트리거 수신기를 시작한 BootstrapApplicationListener아이를 작성하기 위해 진행 한 후, 부트 스트랩의 ApplicationContext 컨테이너의 조상으로 설정하고 : SpringBoot 응용 프로그램을 다음 초기화 SpringCloud 관련 컨텍스트를 시작합니다.

당신이 FeignClient을 소개하면, 컨테이너 공장, 서비스 이름 키 척하기 리본 및 컨테이너 구성 클래스 값, 주어진 분리 구성의 인스턴스를, 부모 컨테이너 SpringBoot 응용 프로그램입니다.

추천

출처blog.csdn.net/u014513171/article/details/93211304