SpringBoot “分身术”:同时监听多个端口

作者:风象南日期:2025/10/21

前言

在日常开发中,我们通常构建的 Spring Boot 应用都是"单面"的——一个端口,一套服务逻辑。但在某些实际场景中,我们可能需要一个应用能够"一心二用":同时提供两套完全不同的服务,分别在不同的端口上运行。

比如:

  • 一个端口面向外部用户,提供 API 服务
  • 另一个端口面向内部管理,提供监控和运维功能
  • 或者在一个应用中同时集成管理后台和用户前台

场景示例

假设我们要开发一个电商平台,需要同时满足:

用户端服务(端口8082)

  • 商品浏览
  • 购物车管理
  • 订单处理

管理端服务(端口8083)

  • 商品管理
  • 订单管理
  • 数据统计

这两套服务功能完全不同,但需要部署在同一个应用中。

技术实现方案

方案一:多 Tomcat Connector 配置

最直接的方式是配置多个 Tomcat Connector。

1. 创建基础项目结构
1// 主应用类
2@SpringBootApplication
3public class DualPortApplication {
4    public static void main(String[] args) {
5        SpringApplication.run(DualPortApplication.class, args);
6    }
7}
8
2. 配置双端口
1@Configuration
2public class DualPortConfiguration {
3
4    @Bean
5    public ServletWebServerFactory servletContainer() {
6        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
7
8        // 添加第一个连接器(用户端)
9        factory.addAdditionalTomcatConnectors(createUserPortConnector());
10        // 添加第二个连接器(管理端)
11        factory.addAdditionalTomcatConnectors(createAdminPortConnector());
12
13        return factory;
14    }
15
16    private Connector createUserPortConnector() {
17        Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
18        connector.setPort(8080);
19        connector.setProperty("connectionTimeout", "20000");
20        return connector;
21    }
22
23    private Connector createAdminPortConnector() {
24        Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
25        connector.setPort(8081);
26        connector.setProperty("connectionTimeout", "20000");
27        return connector;
28    }
29}
30
3. 路由分离策略

现在我们需要为不同端口提供不同的路由处理:

1@Component
2public class PortBasedFilter implements Filter {
3
4    private static final String USER_PORT_HEADER = "X-User-Port";
5    private static final String ADMIN_PORT_HEADER = "X-Admin-Port";
6
7    @Override
8    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
9            throws IOException, ServletException {
10
11        HttpServletRequest httpRequest = (HttpServletRequest) request;
12        int port = httpRequest.getLocalPort();
13
14        if (port == 8082) {
15            // 用户端请求
16            httpRequest.setAttribute("serviceType", "USER");
17        } else if (port == 8083) {
18            // 管理端请求
19            httpRequest.setAttribute("serviceType", "ADMIN");
20        }
21
22        chain.doFilter(request, response);
23    }
24}
25
4. 创建分离的 Controller
1// 用户端 Controller
2@RestController
3@RequestMapping("/api/user")
4public class UserController {
5
6    @GetMapping("/products")
7    public String getProducts() {
8        return "User Products API";
9    }
10
11    @PostMapping("/cart")
12    public String addToCart() {
13        return "Add to cart";
14    }
15}
16
17// 管理端 Controller
18@RestController
19@RequestMapping("/api/admin")
20public class AdminController {
21
22    @GetMapping("/products")
23    public String manageProducts() {
24        return "Admin Products Management";
25    }
26
27    @GetMapping("/statistics")
28    public String getStatistics() {
29        return "Admin Statistics";
30    }
31}
32

方案二:基于路径前缀的更优雅方案

上述方案虽然可行,但在实际使用中可能会有一些问题。让我们采用更优雅的方案。

1. 自定义 Web MVC 配置
1@Configuration
2public class WebMvcConfig implements WebMvcConfigurer {
3
4    @Override
5    public void configurePathMatch(PathMatchConfigurer configurer) {
6        // 为用户端配置前缀
7        configurer.addPathPrefix("/user", cls -> cls.isAnnotationPresent(UserApi.class));
8        // 为管理端配置前缀
9        configurer.addPathPrefix("/admin", cls -> cls.isAnnotationPresent(AdminApi.class));
10    }
11}
12
13// 定义注解
14@Target(ElementType.TYPE)
15@Retention(RetentionPolicy.RUNTIME)
16public @interface UserApi {}
17
18@Target(ElementType.TYPE)
19@Retention(RetentionPolicy.RUNTIME)
20public @interface AdminApi {}
21
2. 使用注解标记 Controller
1@RestController
2@RequestMapping("/products")
3@UserApi
4public class UserProductController {
5
6    @GetMapping
7    public String getProducts() {
8        return "用户端商品列表";
9    }
10
11    @GetMapping("/{id}")
12    public String getProduct(@PathVariable String id) {
13        return "商品详情: " + id;
14    }
15}
16
17@RestController
18@RequestMapping("/products")
19@AdminApi
20public class AdminProductController {
21
22    @GetMapping
23    public String getAllProducts() {
24        return "管理端商品管理列表";
25    }
26
27    @PostMapping
28    public String createProduct() {
29        return "创建商品";
30    }
31
32    @PutMapping("/{id}")
33    public String updateProduct(@PathVariable String id) {
34        return "更新商品: " + id;
35    }
36}
37

高级特性实现

1. 端口感知的拦截器

1@Component
2public class PortAwareInterceptor implements HandlerInterceptor {
3
4    @Override
5    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
6                           Object handler) throws Exception {
7        int port = request.getLocalPort();
8
9        if (port == 8082) {
10            // 用户端逻辑
11            validateUserRequest(request);
12        } else if (port == 8083) {
13            // 管理端逻辑
14            validateAdminRequest(request);
15        }
16
17        return true;
18    }
19
20    private void validateUserRequest(HttpServletRequest request) {
21        // 用户端请求验证逻辑
22        String userAgent = request.getHeader("User-Agent");
23        if (userAgent == null) {
24            throw new SecurityException("Invalid user request");
25        }
26    }
27
28    private void validateAdminRequest(HttpServletRequest request) {
29        // 管理端请求验证逻辑
30        String authHeader = request.getHeader("Authorization");
31        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
32            throw new SecurityException("Admin authentication required");
33        }
34    }
35}
36

2. 端口特定的异常处理

1@ControllerAdvice
2public class GlobalExceptionHandler {
3
4    @ExceptionHandler(Exception.class)
5    public ResponseEntity<ErrorResponse> handleException(
6            Exception e, HttpServletRequest request) {
7
8        int port = request.getLocalPort();
9        ErrorResponse error = new ErrorResponse();
10
11        if (port == 8082) {
12            error.setCode("USER_ERROR_" + e.hashCode());
13            error.setMessage("用户服务异常: " + e.getMessage());
14        } else if (port == 8083) {
15            error.setCode("ADMIN_ERROR_" + e.hashCode());
16            error.setMessage("管理服务异常: " + e.getMessage());
17        }
18
19        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
20                .body(error);
21    }
22}
23

3. 动态端口配置

1@Configuration
2@ConfigurationProperties(prefix = "dual.port")
3@Data
4public class DualPortProperties {
5    private int userPort = 8082;
6    private int adminPort = 8083;
7
8    @Bean
9    public ServletWebServerFactory servletContainer(DualPortProperties properties) {
10        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
11
12        factory.addAdditionalTomcatConnectors(
13            createConnector("user", properties.getUserPort()));
14        factory.addAdditionalTomcatConnectors(
15            createConnector("admin", properties.getAdminPort()));
16
17        return factory;
18    }
19
20    private Connector createConnector(String name, int port) {
21        Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
22        connector.setPort(port);
23        connector.setName(name + "-connector");
24        return connector;
25    }
26}
27

监控和日志

1. 分端口日志记录

1@Configuration
2public class LoggingConfiguration {
3
4    @Bean
5    public Logger userLogger() {
6        return LoggerFactory.getLogger("USER-PORT");
7    }
8
9    @Bean
10    public Logger adminLogger() {
11        return LoggerFactory.getLogger("ADMIN-PORT");
12    }
13}
14
15@Component
16public class PortAwareLogger {
17
18    private final Logger userLogger;
19    private final Logger adminLogger;
20
21    public PortAwareLogger(Logger userLogger, Logger adminLogger) {
22        this.userLogger = userLogger;
23        this.adminLogger = adminLogger;
24    }
25
26    public void logRequest(HttpServletRequest request) {
27        int port = request.getLocalPort();
28        String uri = request.getRequestURI();
29        String method = request.getMethod();
30
31        if (port == 8082) {
32            userLogger.info("用户端请求: {} {}", method, uri);
33        } else if (port == 8083) {
34            adminLogger.info("管理端请求: {} {}", method, uri);
35        }
36    }
37}
38

2. 端口特定的健康检查

1@Component
2public class DualPortHealthIndicator implements HealthIndicator {
3
4    @Override
5    public Health health() {
6        return Health.up()
7                .withDetail("user-port", 8082)
8                .withDetail("admin-port", 8083)
9                .withDetail("status", "Both ports are active")
10                .build();
11    }
12}
13
14@RestController
15@RequestMapping("/health")
16public class HealthController {
17
18    @GetMapping("/user")
19    public Map<String, Object> userHealth() {
20        Map<String, Object> health = new HashMap<>();
21        health.put("port", 8082);
22        health.put("status", "UP");
23        health.put("service", "user-api");
24        return health;
25    }
26
27    @GetMapping("/admin")
28    public Map<String, Object> adminHealth() {
29        Map<String, Object> health = new HashMap<>();
30        health.put("port", 8083);
31        health.put("status", "UP");
32        health.put("service", "admin-api");
33        return health;
34    }
35}
36

安全考虑

1. 端口访问控制

1@Configuration
2@EnableWebSecurity
3public class SecurityConfiguration {
4
5    @Bean
6    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
7        http
8            .authorizeHttpRequests(authz -> authz
9                .requestMatchers(req -> req.getLocalPort() == 8082)
10                    .permitAll()
11                .requestMatchers(req -> req.getLocalPort() == 8083)
12                    .hasRole("ADMIN")
13                .anyRequest().denyAll()
14            )
15            .formLogin(form -> form
16                .loginPage("/admin/login")
17                .permitAll()
18            );
19
20        return http.build();
21    }
22}
23

总结

构建"双面" Spring Boot 应用是一个有趣且实用的技术挑战。通过本文介绍的多种实现方案,我们可以根据实际需求选择最适合的方式:

多 Connector 方案:适合简单场景,实现直接

路径前缀方案:适合需要清晰 API 结构的场景

在某些特定场景下确实能够简化系统架构,降低运维成本。但同时也要注意避免过度复杂化,确保系统的可维护性和可扩展性。

github.com/yuboon/java…


SpringBoot “分身术”:同时监听多个端口》 是转载文章,点击查看原文


相关推荐


LeRobot 机器人学习(Robot Learning)入门教程(一)
具身智能与人形机器人2025/10/19

系列文章目录 目录 系列文章目录 前言 一、前言 二、引言 2.1 LeRobotDataset 2.1.1 数据集类的设计 2.2 代码示例:批处理(流式)数据集 2.2.1 批处理(流式)数据集 2.3 代码示例:收集数据 2.3.1 记录数据集 前言         机器人学习正处于一个转折点,这得益于机器学习的快速进步以及大规模机器人数据日益普及。这种从经典的基于模型的方法向数据驱动、基于学习的范式转变,正在为自主系统释放前所未有的能力。本教程全面梳理现代机


如何检查本地是否存在 Docker 镜像 ?
鸠摩智首席音效师2025/10/18

当我们深入研究 Docker 的功能时,发现 Docker 不仅仅是一项技术,这是一个充满镜像、容器和数据卷的宇宙。今天我们戴上侦探帽,调查一个看似简单却至关重要的案件:识别本地是否存在某个 Docker 镜像。这可能看起来微不足道,但通常是维持高效工作的关键步骤,特别是在复杂的 Docker 环境中。 什么是 Docker 镜像 ? 首先,让我们简单地定义一下什么是 Docker 镜像。在 Docker 生态系统中,镜像是轻量级的、独立的、可执行的包含运行一个软件所需的一切的软件包,包括代


.net8.0_webapi 生成二维码
焚 城2025/10/17

文章目录 一、思路二、实现1、Program.cs2、二维码辅助类QrCodeHelper.cs 三、效果保存后: 一、思路 环境: 1、内容生成二维码图片 2、保存到wwwroot为png格式 3、返回存储路径保存到数据库 4、前端读取显示 二、实现 1、Program.cs //配置静态文件路径 app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(


axios请求
梦6502025/10/15

安装 Axios 在项目中安装 Axios 依赖,可以通过 npm 或 yarn 进行安装。 npm install axios 或 yarn add axios 引入 Axios 在需要使用 Axios 的文件中引入 Axios。 import axios from 'axios'; 发起 GET 请求 使用 Axios 发起 GET 请求,获取数据。 axios.get('https://api.example.com/data') .then(response


公开一个AI产品的商业逻辑与设计方案——AI带来的涂色卡自由
华洛2025/10/14

我孩子正是玩涂色绘本的年纪,我每年给她买绘本的开销就要几百块钱。 涂色绘本这东西也没啥,就是某个角色的线稿,让儿童发挥想象涂色用。 高级一点的,还会给你一个参照的涂好颜色的形象,就是下面图上这种: 每年花个几百块钱虽然不多,但是也总觉的不值,而且绘本的角色、形象都是固定的,孩子想玩的多一次就要买很多本。 直到前几个月开始,我开始自己用AI做线稿图,打印后带给孩子涂色。我发现了一个事情: 孩子的快乐一点都没少,甚至快乐加倍了! 以前买一本拉布布就涂几张,不感兴趣了,扔一边就去涂公主。 现在完全不


设计模式-迭代器模式
紫菜紫薯紫甘蓝2025/10/13

设计模式-迭代器模式 迭代器模式(Iterator Design Pattern),也叫作游标模式(Cursor Design Pattern),用来给类实例提供一种遍历对象的方式。 案例分析 首先写一个经典的 User 类 @Data @ToString public class User { private String uuid; private String name; private Integer age; } 通常我们遍历一个对象有三种方式 pub


医疗设备控制系统中同步与异步通信的架构设计
oioihoii2025/10/11

在医疗设备控制系统的开发过程中,我们面临一个经典的技术挑战:如何在保持用户界面流畅响应的同时,可靠地处理设备控制的长时间操作。本文将通过一个医疗床控制系统的实际案例,分享我们在同步与异步通信架构设计上的解决方案。 问题场景 我们的医疗床控制系统采用主从架构:Host(主控端)与EPC(设备控制单元)通过双端口通信: Command端口:用于发送控制命令和接收立即响应 Event端口:用于接收异步的执行结果和状态更新 关键需求: 用户点击"移动病床"按钮后,需要等待设备执行完成(可能耗时数十


【SCI一区】【电动车】基于ADMM双层凸优化的燃料电池混合动力汽车研究(Matlab代码实现)
荔枝科研社2025/10/9

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭:行百里者,半于九十。 💥1 概述 基于ADMM双层凸优化的燃料电池混合动力汽车研究 随着车辆互联性的出现,互联汽车 (CVs) 在增强道路安全、改善乘坐舒适性、提高交通效率和提高能源效率方面提供了巨大的潜力。通过从车对车 (V2V) 和车对基础设施 (V2I) 通信中获取交通信息,CV 能够更准确、更广泛地感知


第7章:数据库与持久化存储
芝麻开门-新起点2025/10/8

7.1 为何需要数据库:记忆与状态管理 内容讲解 默认情况下,AI Bot 是**“无状态”的。这意味着除了短暂的当前对话上下文,它不记得任何过去的事情。每次对话都是一次全新的开始。然而,在许多真实场景中,我们需要 Bot 拥有记忆**,能够持久化地存储和检索信息。这就是**数据库(Database)**的作用。 数据库为 Bot 提供了以下关键能力: 长期记忆:记住用户的偏好、历史订单、个人信息等。例如,一个订餐 Bot 应该记住你常去的地址和喜欢的口味。状态跟踪:在复杂的多轮任务中,跟踪当前


Python 的 TCP 编程
hubenchang05152025/10/6

#Python 的 TCP 编程 传输控制协议(Transmission Control Protocol) 是一种 面向连接、可靠传输 的网络通信协议,是现代互联网最核心的协议之一。 #客户端程序 TCP 客户端程序通常只需要连接到服务器然后收发数据即可。下面是一个示例,它向 tcpbin.com 的 4242 端口发送 hello\n,对方会原样返回。 import socket # 创建 TCP socket sock = socket.socket(socket.AF_INET, so

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0