Spring Cloud-8-BootstrapApplicationListener有条件创建多个application context

在SpringCloud应用启动时,使用的还是Spring Boot,下面这个run()方法,createBootstrapContext()这个是Spring Boot的引导上下文,每个Spring Boot应用都会有,而在prepareEnvironment()方法中,会创建一个另外一个cloud 的上下文。

	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			prepareContext(bootstrapContext, 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, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

在org.springframework.context.ApplicationListener众多实现者有一个非常重要的实现就是
org.springframework.cloud.bootstrap.BootstrapApplicationListener
可以看到,ApplicationListener是定义在spring-context包中,属于经典上下文范畴,BootstrapApplicationListener是在spring-cloud-context中,属于cloud上下文范畴。

在classpath中出现spring-cloud-context.jar包时,它在spring.factories文件里写了BootstrapApplicationListener,所以在构建SpringApplication对象时就扫描出这个监听器了。然后在run()方法中发送事件给监听器,监听器执行回调。具体流程是:
org.springframework.boot.SpringApplication#run(java.lang.String...)
org.springframework.boot.SpringApplication#prepareEnvironment()
prepareEnvironment中listeners.environmentPrepared(bootstrapContext, environment);
org.springframework.cloud.bootstrap.BootstrapApplicationListener#onApplicationEvent()
org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext()
bootstrapServiceContext中final ConfigurableApplicationContext context = builder.run();
org.springframework.boot.builder.SpringApplicationBuilder#run()
这里又到了org.springframework.boot.SpringApplication#run(java.lang.String...)
这就是很多人在调试Spring cloud源码时,发现创建多个application context的原因。
 

官方解释:
这个listener会prepare SpringApplication(例如填充他的Environment等),通过代理给ApplicationContextInitializer beans,这些beans在独立的bootstrap context里。bootstrap context 是SpringApplication从spring.factories中BootstrapConfiguration中创建的,从bootstrap.properties(or yml)而不是普通的application.properties(or yml)初始化的。

这里有几个判断条件,只有在开启了bootstrap的情况下才会创建。

BootstrapApplicationListener执行后会创建一个id=bootstrap的上下文,这个上下文不是一个独立的父上下文,不是装载应用bean的上下文。

public class BootstrapApplicationListener
		implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {



	@Override
	public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
		ConfigurableEnvironment environment = event.getEnvironment();
        // 没有启用bootstrap且没有使用老配置方式,老配置方式需要创建bootstrap context
		if (!bootstrapEnabled(environment) && !useLegacyProcessing(environment)) {
			return;
		}
		// don't listen to events in a bootstrap context
		if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
			return;
		}
		ConfigurableApplicationContext context = null;
		String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
		for (ApplicationContextInitializer<?> initializer : event.getSpringApplication().getInitializers()) {
			if (initializer instanceof ParentContextApplicationContextInitializer) {
				context = findBootstrapContext((ParentContextApplicationContextInitializer) initializer, configName);
			}
		}
		if (context == null) {
			context = bootstrapServiceContext(environment, event.getSpringApplication(), configName);
			event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
		}

		apply(context, event.getSpringApplication(), environment);
	}

}

	public static final String BOOTSTRAP_ENABLED_PROPERTY = "spring.cloud.bootstrap.enabled";

	public static final boolean MARKER_CLASS_EXISTS = ClassUtils.isPresent(MARKER_CLASS, null);

	public static final String MARKER_CLASS = "org.springframework.cloud.bootstrap.marker.Marker";


public static boolean bootstrapEnabled(Environment environment) {
		return environment.getProperty(BOOTSTRAP_ENABLED_PROPERTY, Boolean.class, false) || MARKER_CLASS_EXISTS;
	}

整个回调方法通过开关控制

spring.cloud.bootstrap.enabled

org.springframework.cloud.bootstrap.BootstrapApplicationListener#onApplicationEvent()方法做的事情是

其中核心的方法是:
org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext()
这个方法会创建一个bootstrapEnvironment,一个bootstrapApplicationContext,这个applicationContext可以认为是一个父上下文。

private ConfigurableApplicationContext bootstrapServiceContext(
		ConfigurableEnvironment environment, final SpringApplication application,
		String configName) {
	StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
	MutablePropertySources bootstrapProperties = bootstrapEnvironment
			.getPropertySources();
	for (PropertySource<?> source : bootstrapProperties) {
		bootstrapProperties.remove(source.getName());
	}
	String configLocation = environment
			.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
	String configAdditionalLocation = environment
			.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
	Map<String, Object> bootstrapMap = new HashMap<>();
	bootstrapMap.put("spring.config.name", configName);
	// if an app (or test) uses spring.main.web-application-type=reactive, bootstrap
	// will fail
	// force the environment to use none, because if though it is set below in the
	// builder
	// the environment overrides it
	bootstrapMap.put("spring.main.web-application-type", "none");
	if (StringUtils.hasText(configLocation)) {
		bootstrapMap.put("spring.config.location", configLocation);
	}
	if (StringUtils.hasText(configAdditionalLocation)) {
		bootstrapMap.put("spring.config.additional-location",
				configAdditionalLocation);
	}
	bootstrapProperties.addFirst(
			new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
	for (PropertySource<?> source : environment.getPropertySources()) {
		if (source instanceof StubPropertySource) {
			continue;
		}
		bootstrapProperties.addLast(source);
	}
	// TODO: is it possible or sensible to share a ResourceLoader?
	SpringApplicationBuilder builder = new SpringApplicationBuilder()
			.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
			.environment(bootstrapEnvironment)
			// Don't use the default properties in this builder
			.registerShutdownHook(false).logStartupInfo(false)
			.web(WebApplicationType.NONE);
	final SpringApplication builderApplication = builder.application();
	if (builderApplication.getMainApplicationClass() == null) {
		// gh_425:
		// SpringApplication cannot deduce the MainApplicationClass here
		// if it is booted from SpringBootServletInitializer due to the
		// absense of the "main" method in stackTraces.
		// But luckily this method's second parameter "application" here
		// carries the real MainApplicationClass which has been explicitly
		// set by SpringBootServletInitializer itself already.
		builder.main(application.getMainApplicationClass());
	}
	if (environment.getPropertySources().contains("refreshArgs")) {
		// If we are doing a context refresh, really we only want to refresh the
		// Environment, and there are some toxic listeners (like the
		// LoggingApplicationListener) that affect global static state, so we need a
		// way to switch those off.
		builderApplication
				.setListeners(filterListeners(builderApplication.getListeners()));
	}
	builder.sources(BootstrapImportSelectorConfiguration.class);
	final ConfigurableApplicationContext context = builder.run();
	// gh-214 using spring.application.name=bootstrap to set the context id via
	// `ContextIdApplicationContextInitializer` prevents apps from getting the actual
	// spring.application.name
	// during the bootstrap phase.
	context.setId("bootstrap");
	// Make the bootstrap context a parent of the app context
	addAncestorInitializer(application, context);
	// It only has properties in it now that we don't want in the parent so remove
	// it (and it will be added back later)
	bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
	mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
	return context;
}

比较关键的点是这里手动引入了一个导入配置类:
org.springframework.cloud.bootstrap.BootstrapImportSelectorConfiguration

@Configuration(proxyBeanMethods = false)
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {

}

进而导入:
org.springframework.cloud.bootstrap.BootstrapImportSelector
BootstrapImportSelector的主要作用就是从spring.factories文件中读取:
org.springframework.cloud.bootstrap.BootstrapConfiguration配置的类

public class BootstrapImportSelector implements EnvironmentAware, DeferredImportSelector {

	private Environment environment;

	private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();

	@Override
	public void setEnvironment(Environment environment) {
		this.environment = environment;
	}

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		List<String> names = new ArrayList<>(SpringFactoriesLoader
				.loadFactoryNames(BootstrapConfiguration.class, classLoader));
		names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(
				this.environment.getProperty("spring.cloud.bootstrap.sources", ""))));

		List<OrderedAnnotatedElement> elements = new ArrayList<>();
		for (String name : names) {
			try {
				elements.add(
						new OrderedAnnotatedElement(this.metadataReaderFactory, name));
			}
			catch (IOException e) {
				continue;
			}
		}
		AnnotationAwareOrderComparator.sort(elements);

		String[] classNames = elements.stream().map(e -> e.name).toArray(String[]::new);

		return classNames;
	}

	class OrderedAnnotatedElement implements AnnotatedElement {

		private final String name;

		private Order order = null;

		private Integer value;

		OrderedAnnotatedElement(MetadataReaderFactory metadataReaderFactory, String name)
				throws IOException {
			MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(name);
			AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
			Map<String, Object> attributes = metadata
					.getAnnotationAttributes(Order.class.getName());
			this.name = name;
			if (attributes != null && attributes.containsKey("value")) {
				this.value = (Integer) attributes.get("value");
				this.order = new Order() {
					@Override
					public Class<? extends Annotation> annotationType() {
						return Order.class;
					}

					@Override
					public int value() {
						return OrderedAnnotatedElement.this.value;
					}
				};
			}
		}

		@Override
		@SuppressWarnings("unchecked")
		public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
			if (annotationClass == Order.class) {
				return (T) this.order;
			}
			return null;
		}

		@Override
		public Annotation[] getAnnotations() {
			return this.order == null ? new Annotation[0]
					: new Annotation[] { this.order };
		}

		@Override
		public Annotation[] getDeclaredAnnotations() {
			return getAnnotations();
		}

		@Override
		public String toString() {
			return new ToStringCreator(this).append("name", this.name)
					.append("value", this.value).toString();
		}

	}

}
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.cloud.util.random.CachedRandomPropertySourceAutoConfiguration

Spring Cloud中,Pox(Proxy Object XML)文件通常用于配置Spring应用中的代理和负载均衡。当你想要将`spring-cloud-starter-loadbalancer`依赖引入到项目中,并在Pox文件中进行配置时,你需要做以下几个步骤: 1. 添加依赖:首先,在你的Maven或Gradle构建文件中添加`spring-cloud-starter-netflix-eureka-client`和`spring-cloud-starter-loadbalancer`这两个依赖,它们包含了负载均衡相关的组件。 Maven示例: ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> ``` Gradle示例: ```groovy implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer' ``` 2. 配置Eureka客户端:如果你的应用需要注册到Eureka服务发现中心,记得配置Eureka客户端的相关属性,如`eureka.client.serviceUrl.defaultZone`等。 3. Pox文件配置:在你的Spring Boot应用的`application-context.xml`或其他Pox文件中,可以配置LoadBalancerClient,比如Ribbon或Hystrix。例如,你可以创建一个`load-balancer-beans.xml`并将其包含进来: ```xml <!-- 引入外部配置文件 --> <import resource="classpath:/load-balancer-beans.xml" /> <!-- 如果你想配置 Ribbon 或 Hystrix 负载均衡 --> <bean id="myServiceLoadBalancer" class="org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerFactoryBean"> <!-- 这里设置具体的实例选择策略、服务器列表等配置 --> </bean> <!-- 或者配置 Hystrix Load Balancer --> <bean id="myServiceLoadBalancer" class="com.netflix.hystrix.HystrixCommandGroupKeyFactory$DefaultHystrixCommandGroupKey"/> ``` 4. 使用`@LoadBalanced`注解:在需要负载均衡的服务类上使用`@LoadBalanced`注解,Spring Cloud会自动处理负载均衡。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值