为什么 server.ports 属性源会同时出现在 application 和 bootstrap 上下文中

server.port 属性是在 application.yml 文件中配置的,按理说应该只出现在 application context 中,但实际情况是 application 和 bootstrap 上下文中同时存在。

这是为什么呢?

事件发布的传递性

事件发布时,如果存在父上下文,那么会将事件传递给父上下文。

org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)

/**
 * Publish the given event to all listeners.
 * @param event the event to publish (may be an {@link ApplicationEvent}
 * or a payload object to be turned into a {@link PayloadApplicationEvent})
 * @param eventType the resolved event type, if known
 * @since 4.2
 */
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
	Assert.notNull(event, "Event must not be null");

	// Decorate event as an ApplicationEvent if necessary
	ApplicationEvent applicationEvent;
	if (event instanceof ApplicationEvent) {
		applicationEvent = (ApplicationEvent) event;
	}
	else {
		applicationEvent = new PayloadApplicationEvent<>(this, event);
		if (eventType == null) {
			eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
		}
	}

	// Multicast right now if possible - or lazily once the multicaster is initialized
	if (this.earlyApplicationEvents != null) {
		this.earlyApplicationEvents.add(applicationEvent);
	}
	else {
		getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
	}

	// Publish event via parent context as well...
	if (this.parent != null) {
		if (this.parent instanceof AbstractApplicationContext) {
			((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
		}
		else {
			this.parent.publishEvent(event);
		}
	}
}

ServerPortInfoApplicationContextInitializer

这是一个 ApplicationContextInitializer,配置在 spring-boot 包的 META-INF/spring.factories中,因此 application 和 bootstrap 上下文在容器初始化时都会加载。

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer

而ServerPortInfoApplicationContextInitializer的作用便是注册了事件 EmbeddedServletContainerInitializedEvent 的监听。
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer#initialize

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
	applicationContext.addApplicationListener(
			new ApplicationListener<EmbeddedServletContainerInitializedEvent>() {

				@Override
				public void onApplicationEvent(
						EmbeddedServletContainerInitializedEvent event) {
					ServerPortInfoApplicationContextInitializer.this
							.onApplicationEvent(event);
				}

			});
}

EmbeddedServletContainerInitializedEvent 事件触发

在容器初始化完成后,启动Embedded Web Application, Application 启动完毕后触发 EmbeddedServletContainerInitializedEvent 事件。

org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#finishRefresh

@Override
protected void finishRefresh() {
	super.finishRefresh();
	EmbeddedServletContainer localContainer = startEmbeddedServletContainer();
	if (localContainer != null) {
		publishEvent(
				new EmbeddedServletContainerInitializedEvent(this, localContainer));
	}
}

EmbeddedServletContainerInitializedEvent 事件监听

public class ServerPortInfoApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext> {

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		// 省略初始化内容
	}

	protected void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
		String propertyName = getPropertyName(event.getApplicationContext());
		setPortProperty(event.getApplicationContext(), propertyName,
				event.getEmbeddedServletContainer().getPort());
	}

	protected String getPropertyName(EmbeddedWebApplicationContext context) {
		String name = context.getNamespace();
		if (StringUtils.isEmpty(name)) {
			name = "server";
		}
		return "local." + name + ".port";
	}
	
	// 该方法是递归方法,会在所有的层级的 context 上都添加 server.ports 属性源。
	private void setPortProperty(ApplicationContext context, String propertyName,
			int port) {
		if (context instanceof ConfigurableApplicationContext) {
			setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(),
					propertyName, port);
		}
		// 如果容器存在父容器,进行递归调用
		if (context.getParent() != null) {
			setPortProperty(context.getParent(), propertyName, port);
		}
	}

	@SuppressWarnings("unchecked")
	private void setPortProperty(ConfigurableEnvironment environment, String propertyName,
			int port) {
		MutablePropertySources sources = environment.getPropertySources();
		PropertySource<?> source = sources.get("server.ports");
		if (source == null) {
			source = new MapPropertySource("server.ports", new HashMap<String, Object>());
			sources.addFirst(source);
		}
		((Map<String, Object>) source.getSource()).put(propertyName, port);
	}

}

总结

通过对源码的解析不难看出, 即使不考虑 spring 事件的传递性, EmbeddedServletContainerInitializedEvent事件触发时,其监听器也会在 context 的所有层级添加 server.ports 属性源。

如果考虑 spring 事件的传递性, 相当于 事件监听器 被多次执行,不过好在 server.ports 属性源只有在获取不到时才会被创建。:)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值