一文详解SpringBoot如何同时监听多个端口
目录
- 前言
- 场景示例
- 技术实现方案
- 方案一:多 Tomcat Connector 配置
- 方案二:基于路径前缀的更优雅方案
- 高级特性实现
- 1. 端口感知的拦截器
- 2. 端口特定的异常处理
- 3. 动态端口配置
- 监控和日志
- 1. 分端口日志记录
- 2. 端口特定的健康检查
- 安全考虑
- 总结
前言
在日常开发中,我们通常构建的 Spring Boot 应用都是"单面"的——一个端口,一套服务逻辑。但在某些实际场景中,我们可能需要一个应用能够"一心二用":同时提供两套完全不同的服务,分别在不同的端口上运行。
比如:
- 一个端口面向外部用户,提供 API 服务
- 另一个端口面向内部管理,提供监控和运维功能
- 或者在一个应用中同时集成管理后台和用户前台
场景示例
假设我们要开发一个电商平台,需要同时满足:
用户端服务(端口8082)
- 商品浏览
- 购物车管理
- 订单处理
管理端服务(端口8083)
- 商品管理
- 订单管理
- 数据统计
这两套服务功能完全不同,但需要部署在同一个应用中。
技术实现方案
方案一:多 Tomcat Connector 配置
最直接的方式是配置多个 Tomcathttp://www.devze.com Connector。
1. 创建基础项目结构
// 主应用类 @SpringBootApplication public class DualPortApplication { public static void main(String[] args) { SpringApplication.run(DualPortApplication.class, args); } }
2. 配置php双端口
@Configuration public class DualPortConfiguration { @Bean public ServletWebServerFactory servletContainer() { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); // 添加第一个连接器(用户端) factory.addAdditionalTomcatConnectors(createUserPortConnector()); // 添加第二个连接器(管理端) factory.addAdditionalTomcatConnectors(createAdminPortConnector()); return factory; } private Connector createUserPortConnector() { Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL); connector.setPort(8080); connector.setProperty("connectionTimeout", "20000"); return connector; } private Connector createAdminPortConnector() { Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL); connector.setPort(8081); connector.setProperty("connectionTimeout", "20000"); return connector; } }
3. 路由分离策略
现在我们需要为不同端口提供不同的路由处理:
@Component public class PortBasedFilter implements Filter { private static final String USER_PORT_HEADER = "X-User-Port"; private static final String ADMIN_PORT_HEADER = "X-Admin-Port"; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; python int port = httpRequest.getLocalPort(); if (port == 8082) { // 用户端请求 httpRequest.setAttribute("serviceType", "USER"); } else if (port == 8083) { // 管理端请求 httpRequest.setAttribute("serviceType", "ADMIN"); } chain.doFilter(request, response); } }
4. 创建分离的 Controller
// 用户端 Controller @RestController @RequestMapping("/api/user") public class UserController { @GetMapping("/products") public String getProducts() { return "User Products API"; } @PostMapping("/cart") public String addToCart() { return "Add to cart"; } } // 管理端 Controller @RestController @RequestMapping("/api/admin") public class AdminController { @GetMapping("/products") public String manageProducts() { return "Admin Products Management"; } @GetMapping("/statistics") public String getStatistics() { return "Admin Statistics"; } }
方案二:基于路径前缀的更优雅方案
上述方案虽然可行,但在实际使用中可能会有一些问题。让我们采用更优雅的方案。
1. 自定义 Web MVC 配置
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { // 为用户端配置前缀 configurer.addPathPrefix("/user", cls -> cls.isAnnotationPresent(UserApi.class)); // 为管理端配置前缀 configurer.addPathPrefix("/admin", cls -> cls.isAnnotationPresent(AdminApi.class)); } } // 定义注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface UserApi {} @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface AdminApi {}
2. 使用注解标记 Controller
@RestController @RequestMapping("/products") @UserApi public class UserProductController { @GetMapping public String getProducts() { return "用户端商品列表"; } @GetMapping("/{id}") public String getProduct(@PathVariable String id) { return "商品详情: " + id; } } @RestController @RequestMapping("/products") @AdminApi public class AdminProductController { @GetMapping public String getAllProducts() { return "管理端商品管理列表"; } @PostMapping public String createProduct() { return "创建商品"; } @PutMapping("/{id}") public String updateProduct(@PathVariable String id) { return "更新商品: " + id; } }
高级特性实现
1. 端口感知的拦截器
@Component public class PortAwareInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { int port = request.getLocalPort(); if (port == 8082) { // 用户端逻辑 validateUserRequest(request); } else if (port == 8083) { // 管理端逻辑 validateAdminRequest(request); } return true; } private void validateUserRequest(HttpServletRequest request) { // 用户端请求验证逻辑 String userAgent = request.getHeader("User-Agent"); if (userAgent == null) { throw new SecurityException("Invalid user request"); } } private void validateAdminRequest(HttpServletRequest request) { // 管理端请求验证逻辑 String authHeader = request.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { throw new SecurityException("Admin authentication required"); } } }
2. 端口特定的异常处理
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleException( Exception e, HttpServletRequest request) { int port = request.getLocalPort(); ErrorResponse error = new ErrorResponse(); if (port == 8082) { error.setCode("USER_ERROR_" + e.hashCode()); error.setMessage("用户服务异常: " + e.getMessage()); } else if (port == 8083) { error.setCode("ADMIN_ERROR_" + e.hashCode()); error.setMessage("管理服务异常: " + e.getMessage()); } return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(error); } }
3. 动态端口配置
@Configuration @ConfigurationProperties(prefix = "dual.port") @Data public class DualPortProperties { private int userPort = 8082; private int adminPort = 8083; @Bean public ServletWebServerFactory servletContainer(DualPortProperties properties) { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.addAdditionalTomcatConnectors( createConnector("user", properties.getUserPort())); factory.addAdditionalTomcatConnectors( createConnector("admin", properties.getAdminPort())); return factory; } private Connector createConnector(String name, int port) { Connector connector = new Connectojsr(TomcatServletWebServerFactory.DEFAULT_PROTOCOL); connector.setPort(port); connector.setName(name + "-connector"); return connector; } }
监控和日志
1. 分端口日志记录
@Configuration public class LoggingConfigujavascriptration { @Bean public Logger userLogger() { return LoggerFactory.getLogger("USER-PORT"); } @Bean public Logger adminLogger() { return LoggerFactory.getLogger("ADMIN-PORT"); } } @Component public class PortAwareLogger { private final Logger userLogger; private final Logger adminLogger; public PortAwareLogger(Logger userLogger, Logger adminLogger) { this.userLogger = userLogger; this.adminLogger = adminLogger; } public void logRequest(HttpServletRequest request) { int port = request.getLocalPort(); String uri = request.getRequestURI(); String method = request.getMethod(); if (port == 8082) { userLogger.info("用户端请求: {} {}", method, uri); } else if (port == 8083) { adminLogger.info("管理端请求: {} {}", method, uri); } } }
2. 端口特定的健康检查
@Component public class DualPortHealthIndicator implements HealthIndicator { @Override public Health health() { return Health.up() .withDetail("user-port", 8082) .withDetail("admin-port", 8083) .withDetail("status", "Both ports are active") .build(); } } @RestController @RequestMapping("/health") public class HealthController { @GetMapping("/user") public Map<String, Object> userHealth() { Map<String, Object> health = new HashMap<>(); health.put("port", 8082); health.put("status", "UP"); health.put("service", "user-api"); return health; } @GetMapping("/admin") public Map<String, Object> adminHealth() { Map<String, Object> health = new HashMap<>(); health.put("port", 8083); health.put("status", "UP"); health.put("service", "admin-api"); return health; } }
安全考虑
端口访问控制
@Configuration @EnableWebSecurity public class SecurityConfiguration { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz -> authz .requestMatchers(req -> req.getLocalPort() == 8082) .permitAll() .requestMatchers(req -> req.getLocalPort() == 8083) .hasRole("ADMIN") .anyRequest().denyAll() ) .formLogin(form -> form .loginPage("/admin/login") .permitAll() ); return http.build(); } }
总结
构建"双面" Spring Boot 应用是一个有趣且实用的技术挑战。通过本文介绍的多种实现方案,我们可以根据实际需求选择最适合的方式:
多 Connector 方案:适合简单场景,实现直接
路径前缀方案:适合需要清晰 API 结构的场景
在某些特定场景下确实能够简化系统架构,降低运维成本。但同时也要注意避免过度复杂化,确保系统的可维护性和可扩展性。
到此这篇关于一文详解SpringBoot如何同时监听多个端口的文章就介绍到这了,更多相关SpringBoot监听多个端口内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论