做软件公司网站,湖北省建设厅乡镇污水官方网站,新手站长如何购买虚拟主机做网站,企业网站怎么搜索优化文章目录 前情提要发散探索从management.port开始确定否需要开启额外端口额外端口是如何开启的ManagementContextFactory的故事从哪儿来创建过程 management 相关API如何被注册 小结 前情提要
最近遇到一个需求#xff0c;在单个服务进程上开启多网络端口#xff0c;将API的… 文章目录 前情提要发散探索从management.port开始确定否需要开启额外端口额外端口是如何开启的ManagementContextFactory的故事从哪儿来创建过程 management 相关API如何被注册 小结 前情提要
最近遇到一个需求在单个服务进程上开启多网络端口将API的访问通过端口强行隔离开。
发散探索
SpringBoot自带的多端口配置server.port和management.port将常规的API与管理相关API通过端口拆分开1中的端口是基于HTTP/1.1通信的如果需要基于HTTP/2.0如gRPC)又会有些差别一般一个进程内SpringApplication对象只有1个理论上搞多个自然就能开启多个端口略显粗暴 本着探索的态度我会逐个探讨上述3个方向本文先关注在方向1上。如果你有更NB的问题或者建议欢迎评论区留言。
从management.port开始
配置生效依赖SpringBoot的AutoConfiguration机制management相关配置体现在ManagementContextAutoConfiguration中完成ManagementContext的配置其中涉及的问题如下。
确定否需要开启额外端口
ConditionalOnManagementPort决定了是否开启新端口该注解声明如下
Retention(RetentionPolicy.RUNTIME)
Target({ ElementType.TYPE, ElementType.METHOD })
Documented
Conditional(OnManagementPortCondition.class)
public interface ConditionalOnManagementPort {/*** The {link ManagementPortType} to match.* return the port type*/ManagementPortType value();}其依赖的OnManagementPortCondition会在配置过程中被处理处理概要过程如下
基于当前的Environment决定实际的ManagementPortType获得当前注解中value对应的设定ManagementPortType;基于1和2决定被其注解的bean是否初始化ManagementPortType的定义如下
public enum ManagementPortType {/*** The management port has been disabled.*/DISABLED,/*** The management port is the same as the server port.*/SAME,/*** The management port and server port are different.*/DIFFERENT;/*** Look at the given environment to determine if the {link ManagementPortType} is* {link #DISABLED}, {link #SAME} or {link #DIFFERENT}.* param environment the Spring environment* return {link #DISABLED} if {code management.server.port} is set to a negative* value, {link #SAME} if {code management.server.port} is not specified or equal to* {code server.port} and {link #DIFFERENT} otherwise.* since 2.1.4*/public static ManagementPortType get(Environment environment) {Integer managementPort getPortProperty(environment, management.server.);if (managementPort ! null managementPort 0) {return DISABLED;}Integer serverPort getPortProperty(environment, server.);return ((managementPort null || (serverPort null managementPort.equals(8080))|| (managementPort ! 0 managementPort.equals(serverPort))) ? SAME : DIFFERENT);}private static Integer getPortProperty(Environment environment, String prefix) {return environment.getProperty(prefix port, Integer.class);}}额外端口是如何开启的
到这里我们可以回到ManagementContextAutoConfiguration针对性地关注ConditionalOnManagementPort(ManagementPortType.DIFFERENT)相关类, 也就是下面这段 Configuration(proxyBeanMethods false)ConditionalOnManagementPort(ManagementPortType.DIFFERENT)static class DifferentManagementContextConfiguration implements ApplicationListenerWebServerInitializedEvent {private final ApplicationContext applicationContext;private final ManagementContextFactory managementContextFactory;DifferentManagementContextConfiguration(ApplicationContext applicationContext,ManagementContextFactory managementContextFactory) {this.applicationContext applicationContext;this.managementContextFactory managementContextFactory;}Overridepublic void onApplicationEvent(WebServerInitializedEvent event) {if (event.getApplicationContext().equals(this.applicationContext)) {ConfigurableWebServerApplicationContext managementContext this.managementContextFactory.createManagementContext(this.applicationContext,EnableChildManagementContextConfiguration.class,PropertyPlaceholderAutoConfiguration.class);if (isLazyInitialization()) {managementContext.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}managementContext.setServerNamespace(management);managementContext.setId(this.applicationContext.getId() :management);setClassLoaderIfPossible(managementContext);CloseManagementContextListener.addIfPossible(this.applicationContext, managementContext);managementContext.refresh();}}protected boolean isLazyInitialization() {AbstractApplicationContext context (AbstractApplicationContext) this.applicationContext;ListBeanFactoryPostProcessor postProcessors context.getBeanFactoryPostProcessors();return postProcessors.stream().anyMatch(LazyInitializationBeanFactoryPostProcessor.class::isInstance);}private void setClassLoaderIfPossible(ConfigurableApplicationContext child) {if (child instanceof DefaultResourceLoader) {((DefaultResourceLoader) child).setClassLoader(this.applicationContext.getClassLoader());}}}从中我们可以发现几个点
ManagementContext是在主WebApplicationContext发布事件WebServerInitializedEvent后开始初始化;ManagementContext全新的ConfigurableWebServerApplicationContext这意味着最终启动后内存中存在至少两个ApplicationContext如果说开启两个SpringApplication对象是表象的话那么创建两个WebApplicationContext应该说就是底层的本质了整个处理过程就是创建ConfigurableWebServerApplicationContext后续做必要的配置最后refresh。如果你分析过SpringApplication.run()那想必你看到了熟悉的味道。
ManagementContextFactory的故事
从哪儿来
对应的子类有两个ReactiveManagementContextFactory和ServletManagementContextFactory具体初始化则由xxxManagementContextFactoryAutoConfiguration影响更进一步的细节藏在 ConditionalOnWebApplication(typexxx)中。
创建过程
以ReactiveManagementContextFactory为例
Overridepublic ConfigurableWebServerApplicationContext createManagementContext(ApplicationContext parent,Class?... configClasses) {// 复用EnvironmentEnvironment parentEnvironment parent.getEnvironment();ConfigurableEnvironment childEnvironment ApplicationContextFactory.DEFAULT.createEnvironment(WebApplicationType.REACTIVE);if (parentEnvironment instanceof ConfigurableEnvironment) {// 复用ConversionServicechildEnvironment.setConversionService(((ConfigurableEnvironment) parentEnvironment).getConversionService());}AnnotationConfigReactiveWebServerApplicationContext child new AnnotationConfigReactiveWebServerApplicationContext();child.setEnvironment(childEnvironment);// 这里可以看到最终会是parent和child关系child.setParent(parent);/*
这里以编程的方式完成BeanDefinition注册, 这里的configClass {EnableChildManagementContextConfiguration.class,PropertyPlaceholderAutoConfiguration.class} */Class?[] combinedClasses ObjectUtils.addObjectToArray(configClasses,ReactiveWebServerFactoryAutoConfiguration.class);child.register(combinedClasses);// 最后保持parent和child的WebFactory一致, 直接复用parent的BeanDefinitionregisterReactiveWebServerFactory(parent, child);return child;}management 相关API如何被注册
经过前面一波分析我们已经看到了创建ApplicationContext注册必要的Class而后refresh整个容器就要启动但没有看到management相关API其实就藏在EnableChildManagementContextConfiguration.class这里, 从这里出发最终找到配置文件ManagementContextConfiguration.imports链路如下
EnableManagementContext - ManagementContextConfigurationImportSelector - META-INFO/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.importsManagementContextConfiguration.imports的内容如下
org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration
org.springframework.boot.actuate.autoconfigure.endpoint.web.reactive.WebFluxEndpointManagementContextConfiguration
org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration
org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration
org.springframework.boot.actuate.autoconfigure.security.servlet.SecurityRequestMatchersManagementContextConfiguration
org.springframework.boot.actuate.autoconfigure.web.jersey.JerseySameManagementContextConfiguration
org.springframework.boot.actuate.autoconfigure.web.jersey.JerseyChildManagementContextConfiguration
org.springframework.boot.actuate.autoconfigure.web.reactive.ReactiveManagementChildContextConfiguration
org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementChildContextConfiguration
org.springframework.boot.actuate.autoconfigure.web.servlet.WebMvcEndpointChildContextConfiguration这些Configuration最终受如下2个注解的解决决定是否初始化 ManagementContextConfiguration(value ManagementContextType.xxx, proxyBeanMethods false) ConditionalOnWebApplication(type Type.xxx) 最终不同的EndPoint由不同的EndPointDiscover对整个ApplicationContext中的类进行扫描并提取出来组装为EndPointHandlerMapping。
到这里可以解释一个现象就是在manage端口上无法访问到应用端口上的API因为两者各自关联了一个独立的HandlerMapping。虽然通常child context可以访问到parent context中的所有bean实例但是各自可见的API被各自的RequestHandlerMapping限制而隔离。
小结
以上是针对mangement API开放独立端口的探究从中有几个点值得借鉴
独立的ApplicationContext可以作为资源隔离的一种方式同时又不完全失去与parent的联系条件初始化在SpringBoot自动配置中的广泛应用WebApplicationContext与ApplicationContext的区别在于WebServerFactory是否存在以及RequestHandlerMapping必要的注解识别有了这点发现做API隔离可用的方案也会更加丰富 后续会对另外2种实现方式做探讨感谢你的阅读。