java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot监听多个端口

一文详解SpringBoot如何同时监听多个端口

作者:风象南

这篇文章主要为大家详细介绍了SpringBoot如何同时监听多个端口,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以了解下

前言

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

比如:

场景示例

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

用户端服务(端口8082)

管理端服务(端口8083)

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

技术实现方案

方案一:多 Tomcat Connector 配置

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

1. 创建基础项目结构

// 主应用类
@SpringBootApplication
public class DualPortApplication {
    public static void main(String[] args) {
        SpringApplication.run(DualPortApplication.class, args);
    }
}

2. 配置双端口

@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;
        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 Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
        connector.setPort(port);
        connector.setName(name + "-connector");
        return connector;
    }
}

监控和日志

1. 分端口日志记录

@Configuration
public class LoggingConfiguration {

    @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监听多个端口内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文