当前位置: 首页 > news >正文

SpringBoot开启多端口探究(1)

文章目录

  • 前情提要
  • 发散探索
  • 从management.port开始
    • 确定否需要开启额外端口
    • 额外端口是如何开启的
    • ManagementContextFactory的故事
      • 从哪儿来
      • 创建过程
    • management 相关API如何被注册
  • 小结

前情提要

最近遇到一个需求,在单个服务进程上开启多网络端口,将API的访问通过端口强行隔离开。

发散探索

  1. SpringBoot自带的多端口配置,server.port和management.port,将常规的API与管理相关API通过端口拆分开;
  2. 1中的端口是基于HTTP/1.1通信的,如果需要基于HTTP/2.0(如gRPC)又会有些差别;
  3. 一般一个进程内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会在配置过程中被处理,处理概要过程如下:

  1. 基于当前的Environment决定实际的ManagementPortType;
  2. 获得当前注解中value对应的设定ManagementPortType;
  3. 基于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 ApplicationListener<WebServerInitializedEvent> {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;List<BeanFactoryPostProcessor> 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());}}}

从中我们可以发现几个点:

  1. ManagementContext是在主WebApplicationContext发布事件WebServerInitializedEvent后开始初始化;
  2. ManagementContext全新的ConfigurableWebServerApplicationContext,这意味着最终启动后,内存中存在至少两个ApplicationContext;如果说开启两个SpringApplication对象是表象的话,那么创建两个WebApplicationContext应该说就是底层的本质了;
  3. 整个处理过程就是创建ConfigurableWebServerApplicationContext,后续做必要的配置,最后refresh。如果你分析过SpringApplication.run(),那想必你看到了熟悉的味道。

ManagementContextFactory的故事

从哪儿来

对应的子类有两个,ReactiveManagementContextFactory和ServletManagementContextFactory,具体初始化则由xxxManagementContextFactoryAutoConfiguration影响,更进一步的细节藏在
@ConditionalOnWebApplication(type=xxx)中。

创建过程

以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.imports

ManagementContextConfiguration.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开放独立端口的探究,从中有几个点值得借鉴:

  1. 独立的ApplicationContext可以作为资源隔离的一种方式,同时又不完全失去与parent的联系;
  2. 条件初始化在SpringBoot自动配置中的广泛应用;
  3. WebApplicationContext与ApplicationContext的区别在于WebServerFactory是否存在,以及RequestHandlerMapping+必要的注解识别,有了这点发现,做API隔离可用的方案也会更加丰富;
    后续会对另外2种实现方式做探讨,感谢你的阅读。

相关文章:

SpringBoot开启多端口探究(1)

文章目录 前情提要发散探索从management.port开始确定否需要开启额外端口额外端口是如何开启的ManagementContextFactory的故事从哪儿来创建过程 management 相关API如何被注册 小结 前情提要 最近遇到一个需求&#xff0c;在单个服务进程上开启多网络端口&#xff0c;将API的…...

优化算法:2.粒子群算法(PSO)及Python实现

一、定义 粒子群算法&#xff08;Particle Swarm Optimization&#xff0c;PSO&#xff09;是一种模拟鸟群觅食行为的优化算法。想象一群鸟在寻找食物&#xff0c;每只鸟都在尝试找到食物最多的位置。它们通过互相交流信息&#xff0c;逐渐向食物最多的地方聚集。PSO就是基于这…...

ThreadLocal面试三道题

针对ThreadLocal的面试题&#xff0c;我将按照由简单到困难的顺序给出三道题目&#xff0c;并附上参考答案的概要。 1. 简单题&#xff1a;请简述ThreadLocal是什么&#xff0c;以及它的主要作用。 参考答案&#xff1a; ThreadLocal是Java中的一个类&#xff0c;用于提供线…...

Git操作指令(已完结)

Git操作指令 一、安装git 1、设置配置信息&#xff1a; # global全局配置 git config --global user.name "Your username" git config --global user.email "Your email"# 显示颜色 git config --global color.ui true# 配置别名&#xff0c;各种指令都…...

大数据采集工具——Flume简介安装配置使用教程

Flume简介&安装配置&使用教程 1、Flume简介 一&#xff1a;概要 Flume 是一个可配置、可靠、高可用的大数据采集工具&#xff0c;主要用于将大量的数据从各种数据源&#xff08;如日志文件、数据库、本地磁盘等&#xff09;采集到数据存储系统&#xff08;主要为Had…...

C语言 #具有展开功能的排雷游戏

文章目录 前言 一、整个排雷游戏的思维梳理 二、整体代码分布布局 三、游戏主体逻辑实现--test.c 四、整个游戏头文件的引用以及函数的声明-- game.h 五、游戏功能的具体实现 -- game.c 六、老六版本 总结 前言 路漫漫其修远兮&#xff0c;吾将上下而求索。 一、整个排…...

npm publish出错,‘proxy‘ config is set properly. See: ‘npm help config‘

问题&#xff1a;使用 npm publish发布项目依赖失败&#xff0c;报错 proxy config is set properly. See: npm help config 1、先查找一下自己的代理 npm config get proxy npm config get https-proxy npm config get registry2、然后将代理和缓存置空 方式一&#xff1a; …...

Springboot 多数据源事务

起因 在一个service方法上使用的事务,其中有方法是调用的多数据源orderDB 但是多数据源没有生效,而是使用的primaryDB 原因 spring 事务实现的方式 以 Transactional 注解为例 (也可以看 TransactionTemplate&#xff0c; 这个流程更简单一点)。 入口&#xff1a;ProxyTransa…...

Python每日学习

我是从c转来学习Python的&#xff0c;总感觉和c相比Python的实操简单&#xff0c;但是由于写c的代码多了&#xff0c;感觉Python的语法好奇怪 就比如说c的开头要有库&#xff08;就是类似于#include <bits/stdc.h>&#xff09;而且它每一项的代码结束之后要有一个表示结…...

数据库 执行sql添加删除字段

添加字段&#xff1a; ALTER TABLE 表明 ADD COLUMN 字段名 类型 DEFAULT NULL COMMENT 注释 AFTER 哪个字段后面; 效果&#xff1a; 删除字段&#xff1a; ALTER TABLE 表明 DROP COLUMN 字段;...

前端开发:HTML与CSS

文章目录 前言1.1、CS架构和BS架构1.2、网页构成 HTML1.web开发1.1、最简单的web应用程序1.2、HTTP协议1.2.1 、简介1.2.2、 http协议特性1.3.3、http请求协议与响应协议 2.HTML概述3.HTML标准结构4.标签的语法5.基本标签6.超链接标签6.1、超链接基本使用6.2、锚点 7.img标签8.…...

ctfshow解题方法

171 172 爆库名->爆表名->爆字段名->爆字段值 -1 union select 1,database() ,3 -- //返回数据库名 -1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema库名 -- //获取数据库里的表名 -1 union select 1,group_concat(…...

探索 Blockly:自定义积木实例

3.实例 3.1.基础块 无输入 , 无输出 3.1.1.json var textOneJson {"type": "sql_test_text_one","message0": " one ","colour": 30,"tooltip": 无输入 , 无输出 };javascriptGenerator.forBlock[sql_test_te…...

MongoDB教程(二十三):关于MongoDB自增机制

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、MongoD…...

展馆导览系统架构解析,从需求分析到上线运维

在物质生活日益丰富的当下&#xff0c;人们对精神世界的追求愈发强烈&#xff0c;博物馆、展馆、纪念馆等场所成为人们丰富知识、滋养心灵的热门选择。与此同时&#xff0c;人们对展馆的导航体验也提出了更高要求&#xff0c;展馆导览系统作为一种基于室内外地图相结合的位置引…...

Servlet详解(超详细)

Servlet详解 文章目录 Servlet详解一、基本概念二、Servlet的使用1、创建Servlet类2、配置Servleta. 使用web.xml配置b. 使用注解配置 3、部署Web应用4、处理HTTP请求和生成响应5、处理表单数据HTML表单Servlet 6、管理会话 三、servlet生命周期1、加载和实例化2、初始化3、 请…...

Meta AI引入Imagine Me功能,上传图片输入提示词即可实现个性化照片

AITOP100平台获悉&#xff0c;Meta 公司在 AI 领域再次迈出了重要的步伐&#xff0c;其发布的 Llama 3.1 开源 AI 模型以及对 Meta AI 功能的更新扩充引发了广泛关注。 其中&#xff0c;新引入的“Imagine Me”功能尤为引人注目。在这一功能下&#xff0c;美国地区的用户只需上…...

常用自启设置

一、开机自启动 1、编辑 vi /lib/systemd/system/nginx.service 文件&#xff0c;没有创建一个 touch nginx.service 然后将如下内容根据具体情况进行修改后&#xff0c;添加到nginx.service文件中&#xff1a; [Unit] Descriptionnginx Afternetwork.target remote-fs.targ…...

模块与组件、模块化与组件化的理解

在React或其他现代JavaScript框架中&#xff0c;模块与组件、模块化与组件化是核心概念&#xff0c;它们对于提高代码的可维护性、复用性和开发效率具有重要意义。以下是对这些概念的理解&#xff1a; 模块与组件 模块&#xff08;Module&#xff09; 定义&#xff1a;模块是…...

Rust:cargo的常用命令

1.查看版本 $ cargo --version cargo 1.79.0 (ffa9cf99a 2024-06-03) 2.创建新的项目 $ cargo new hello 创建后的目录结构为 $ tree hello/ hello/ ├── Cargo.toml └── src └── main.rs 3.运行项目 $ cd hello $ cargo run Compiling hello v0.1.0 (/home/c…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

ardupilot 开发环境eclipse 中import 缺少C++

目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用

1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...