在 Spring Boot 运行时获取监听的端口号

1、概览

Spring Boot 应用嵌入了一个 Web 服务器,有时候我们可能希望在运行时获取 HTTP 端口。

本文将带你了解如何在 Spring Boot 应用中以编程式的方式获取 HTTP 端口。

2、示例

2.1、Spring Boot 应用

创建一个简单的 Spring Boot 应用示例,演示如何在运行时获取 HTTP 端口:

@SpringBootApplication
public class GetServerPortApplication {
    public static void main(String[] args) {
        SpringApplication.run(GetServerPortApplication.class, args);
    }
}

2.2、设置端口号的两种方式

通常,配置 Spring Boot 应用 HTTP 端口的最直接方法是在配置文件 application.propertiesapplication.yml 中定义端口。

例如,在 application.properties 文件中,可以将 7777 设置为应用的运行端口:

server.port=7777

另外,也可以不定义固定端口,而是通过将 server.port 属性值设置为 0,让 Spring Boot 应用在随机端口上运行:

server.port=0

3、运行时获取固定端口

创建一个 properties 文件 application-fixedport.properties,并在其中定义固定端口 7777

server.port=7777

接下来,尝试在单元测试类中获得端口:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = GetServerPortApplication.class,
  webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@ActiveProfiles("fixedport")
public class GetServerFixedPortUnitTest {
    private final static int EXPECTED_PORT = 7777;
    ....
}

在了解测试方法之前,先快速了解一下测试类的注解:

  • @RunWith(SpringRunner.class) - 这把 JUnit 测试与 Spring TestContext 连接起来
  • @SpringBootTest(... SpringBootTest.WebEnvironment.DEFINED_PORT) - 在 @SpringBootTest 注解中,可以使用 DEFINED_PORT 来指定嵌入式 Web 服务器的端口
  • @ActiveProfiles("fixedport") - 通过此注解,启用了 Spring Profile “fixedport”,以便加载 application-fixedport.properties

3.1、使用 @Value("${server.port}") 注解

由于会加载 application-fixedport.properties 文件,因此可以使用 @Value 注解获取 server.port 属性:

@Value("${server.port}")
private int serverPort;

@Test
public void givenFixedPortAsServerPort_whenReadServerPort_thenGetThePort() {
    assertEquals(EXPECTED_PORT, serverPort);
}

3.2、使用 ServerProperties 类

ServerProperties 包含嵌入式 Web 服务器的属性,如端口、地址和 Server Header。

可以注入 ServerProperties 组件并从中获取端口:

@Autowired
private ServerProperties serverProperties;

@Test
public void givenFixedPortAsServerPort_whenReadServerProps_thenGetThePort() {
    int port = serverProperties.getPort();
 
    assertEquals(EXPECTED_PORT, port);
}

4、运行时获取随机端口

创建另一个 properties 文件 application-randomport.properties

server.port=0

如上所示,允许 Spring Boot 在 Web 服务器启动时随机选择一个空闲端口。

同样,创建另一个单元测试类:

....
@ActiveProfiles("randomport")
public class GetServerRandomPortUnitTest {
...
}

如上,激活 “randomport” Spring Profile 来加载相应的 properties 文件。

上节所述的两种方式,都没法在运行时获取到随机端口:

@Value("${server.port}")
private int randomServerPort;

@Test
public void given0AsServerPort_whenReadServerPort_thenGet0() {
    assertEquals(0, randomServerPort);
}

@Autowired
private ServerProperties serverProperties;

@Test
public void given0AsServerPort_whenReadServerProps_thenGet0() {
    int port = serverProperties.getPort();
 
    assertEquals(0, port);
}

正如两个测试方法所示,通过 @Value("${server.port}")serverProperties.getPort() 获取到的端口都是 0。显然,这不是正确的运行时端口。

4.1、使用 ServletWebServerApplicationContext

如果嵌入式 Web 服务器启动,Spring Boot 就会启动 ServletWebServerApplicationContext

因此,可以从 context 对象中获取 WebServer,从而获得服务器信息或对服务器进行操作:

@Autowired
private ServletWebServerApplicationContext webServerAppCtxt;

@Test
public void given0AsServerPort_whenReadWebAppCtxt_thenGetThePort() {
    int port = webServerAppCtxt.getWebServer().getPort();
 
    assertTrue(port > 1023);
}

在上述测试中,检查端口是否大于 1023(0-1023 是系统端口)。

4.2、ServletWebServerInitializedEvent 事件

Spring 应用可以发布各种事件,而 EventListeners 可以处理这些事件。

嵌入式 Web 服务器启动后,将发布 ServletWebServerInitializedEvent 事件。该事件包含有关 Web 服务器的信息。

因此,可以创建一个 EventListener 来从该事件中获取端口:

@Service
public class ServerPortService {
    private int port;

    public int getPort() {
        return port;
    }

    @EventListener
    public void onApplicationEvent(final ServletWebServerInitializedEvent event) {
        port = event.getWebServer().getPort();
    }
}

ServerPortService 注入测试类,以快速获取随机端口:

@Autowired
private ServerPortService serverPortService;

@Test
public void given0AsServerPort_whenReadFromListener_thenGetThePort() {
    int port = serverPortService.getPort();
 
    assertTrue(port > 1023);
}

5、总结

本文介绍了如何给 Spring Boot 应用设置固定端口和随机端口,以及如何在运行时获取到监听的端口信息。


Ref:https://www.baeldung.com/spring-boot-running-port