Spring Boot - 如何获取运行端口

136

我有一个使用嵌入式Tomcat 7的Spring Boot应用程序,并在我的application.properties中设置了server.port = 0以便获得随机端口。启动服务器并运行在某个端口后,我需要能够获取所选端口。

我无法使用@Value("$server.port")因为它是零。这似乎是一个简单的信息,那么为什么我不能从我的Java代码中访问它呢?我该如何访问它?


相关链接:https://dev59.com/tIHba4cB1Zd3GeqPP1jl#24643484 - Dirk Lachowski
另一种可能性可以在文档中找到:http://docs.spring.io/spring-boot/docs/current/reference/html/howto-embedded-servlet-containers.html(请参见64.5在运行时发现HTTP端口) - Dirk Lachowski
13个回答

128

是否也可以通过类似的方式访问管理端口,例如:

  @SpringBootTest(classes = {Application.class}, webEnvironment = WebEnvironment.RANDOM_PORT)
  public class MyTest {

    @LocalServerPort
    int randomServerPort;

    @LocalManagementPort
    int randomManagementPort;

15
@LocalServerPort只是@Value("${local.server.port}")的一种快捷方式。 - deamon
1
@deamon 意味着如果您在属性中未指定 local.server.port,则它将无法正常工作。 - chill appreciator
2
使用 webEnvironment = WebEnvironment.RANDOM_PORT 解决了问题。谢谢 - Muhammad Muzammil
无法解析占位符'value "${local.server.port}"'中的'local.server.port' - undefined

98

Spring的环境为您保存此信息。

@Autowired
Environment environment;

String port = environment.getProperty("local.server.port");

表面上看,这与注入一个带有@Value("${local.server.port}")@LocalServerPort注释的字段相同(两者完全相同),在启动时会抛出自动装配失败的异常,因为该值在上下文完全初始化之前不可用。 这里的区别是该调用隐式地由运行时业务逻辑进行而不是在应用程序启动时调用,因此端口的'lazy-fetch'解决了该问题。


7
由于某些原因,这对我不起作用,但 environment.getProperty("server.port") 可以。 - Anand Rockzz
在我的情况下,portnull@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") public class SpringBootH2IntegrationTest { @Autowired Environment environment; @Test public void test() { String port = environment.getProperty("local.server.port"); // 现在可以看到端口号了 } - tryingHard
1
这是因为@SpringBootTest默认使用MockMVC环境,而不是启动应用程序服务器来提供端口。 - hennr

28

感谢 @Dirk Lachowski 指导我正确的方向。解决方案并不像我所期望的那么优雅,但我成功了。通过阅读 Spring 文档,我可以监听 EmbeddedServletContainerInitializedEvent 并在服务器启动运行后获取端口。这就是它的样子 -

import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;




    @Component
    public class MyListener implements ApplicationListener<EmbeddedServletContainerInitializedEvent> {

      @Override
      public void onApplicationEvent(final EmbeddedServletContainerInitializedEvent event) {
          int thePort = event.getEmbeddedServletContainer().getPort();
      }
    }

据我所知,如果您想配置一个带有服务器端口的bean,则此方法不起作用。该事件直到所有bean都被加载并且servlet已注册后才会触发。 - mre
它在当时对我起作用了,所以我接受了它。不过我还没有尝试过hennr的答案。 - Tucker
阅读文档后,我创建了一个与您几乎相同的小类,将其命名为“PortProvider”,并提供了一个“getPort()”方法。在需要端口的控制器中自动装配了我的“PortProvider”,当我的业务逻辑调用“portProvider.getPort()”时,运行时端口被返回。 - Matthew Wise
20
对于尝试在Spring Boot 2.0或更高版本中实现此操作的人来说,API似乎略有改变。我无法再订阅EmbeddedServletContainerInitializedEvent,但是有一个类似的类叫做ServletWebServerInitializedEvent,它有一个.getWebServer()方法。这将至少获取Tomcat正在侦听的端口。 - NiteLite

17
在测试期间,您可以注入 local.server.port 值来获取嵌入式Tomcat实例正在使用的端口,如下所示:
// Inject which port we were assigned
@Value("${local.server.port}")
int port;

20
只有在使用@WebIntegrationTests运行时,local.server.port才会被设置。 - ejain
WebIntegrationTest已被弃用。 - Jess Chen

17

为了使配置和我一样的应用程序受益于我经历过的问题...

以上解决方案都对我没有用,因为我在项目基础目录下有一个名为./config的目录,其中包含2个文件:

application.properties
application-dev.properties

application.properties中,我有:

spring.profiles.active = dev  # set my default profile to 'dev'

我在 application-dev.properties 文件中有以下内容:

server_host = localhost
server_port = 8080

当我从CLI运行我的fat jar时,*.properties文件将从./config目录中读取,一切都很好。

然而,事实证明这些属性文件完全覆盖了我在Spock规范中的@SpringBootTest中设置的webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT。无论我尝试什么,即使将webEnvironment设置为RANDOM_PORT,Spring也总是会在端口8080上启动嵌入式Tomcat容器(或者我在./config/*.properties文件中设置的任何值)。

我唯一能够克服这个问题的方法是在我的Spock集成规范中在@SpringBootTest注释中添加一个明确的properties = "server_port=0"

@SpringBootTest (webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "server_port=0")

只有在这种情况下,Spring 才会在一个随机端口上启动 Tomcat。个人认为这是 Spring 测试框架的一个 bug,但我相信他们会对此有自己的看法。

希望对某些人有所帮助。


我有完全相同的设置,也遇到了这个问题。我某种程度上认为这可能是问题所在,但感谢您在此发布解决方案。您知道是否有人将其记录为错误吗? - bvulaj

12

从 Spring Boot 1.4.0 开始,您可以在测试中使用以下内容:

import org.springframework.boot.context.embedded.LocalServerPort;

@SpringBootTest(classes = {Application.class}, webEnvironment = WebEnvironment.RANDOM_PORT)
public class MyTest {

  @LocalServerPort
  int randomPort;

  // ...
}

10

在Spring Boot 2之后,有很多变化。上面给出的答案适用于Spring Boot 2之前的版本。如果您在运行应用程序时使用服务器端口的运行参数,则只会得到application.properties文件中提到的静态值@Value("${server.port}")。现在要获取实际运行服务器的端口,请使用以下方法:

    @Autowired
    private ServletWebServerApplicationContext server;

    @GetMapping("/server-port")
    public String serverPort() {

        return "" + server.getWebServer().getPort();
    }

此外,如果您正在使用应用程序作为带有负载平衡RestTemplateWebClient的Eureka/Discovery客户端,上述方法将返回确切的端口号。


3
这是适用于Spring Boot 2的正确答案。在使用@SpringBootTest和WebEnvironment.RANDOM_PORT时运行良好。 - Ken Pronovici
只是为了补充@Ken Pronovici的评论 - 如果在SpringBootTest注释中未指定webEnvironment参数,则Spring不会创建ServletWebServerApplicationContext并在运行时失败,因为它找不到要注入的候选bean。 - raiks
尝试过了,对我来说总是0。 - Vu Nguyen

7

这些解决方案都没有对我起作用。我需要在构建一个Swagger配置bean时知道服务器端口。使用ServerProperties 对我有用:

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.ws.rs.ApplicationPath;

import io.swagger.jaxrs.config.BeanConfig;
import io.swagger.jaxrs.listing.ApiListingResource;
import io.swagger.jaxrs.listing.SwaggerSerializers;

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

@Component
@ApplicationPath("api")
public class JerseyConfig extends ResourceConfig 
{
    @Inject
    private org.springframework.boot.autoconfigure.web.ServerProperties serverProperties;

    public JerseyConfig() 
    {
        property(org.glassfish.jersey.server.ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
    }

    @PostConstruct
    protected void postConstruct()
    {
        // register application endpoints
        registerAndConfigureSwaggerUi();
    }

    private void registerAndConfigureSwaggerUi()
    {
        register(ApiListingResource.class);
        register(SwaggerSerializers.class);

        final BeanConfig config = new BeanConfig();
        // set other properties
        config.setHost("localhost:" + serverProperties.getPort()); // gets server.port from application.properties file         
    }
}

这个例子使用了Spring Boot的自动配置和JAX-RS(而不是Spring MVC)。


1
我希望Swagger也能实现同样的功能。 - Jeef

1

我使用的是Spring 2.5.5和Junit 4.13.2,这是我的解决方案:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit4.SpringRunner;

// tell Java the environment your testcase running is in Spring, 
// which will enable the auto configuration such as value injection
@RunWith(SpringRunner.class)
@SpringBootTest(
    class = Application.class, 
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SimpleWebTest {

    @LocalServerPort
    private int randomPort;

    @Test
    public void test() {
        // use randomPort ...
        System.out.println(randomPort);
    }

}

自2.7.0版本起已被弃用,将在3.0.0版本中移除。 - Ioannis Barakos

0

你可以从中获取服务器端口

HttpServletRequest

@Autowired
private HttpServletRequest request;

@GetMapping(value = "/port")
public Object getServerPort() {
   System.out.println("I am from " + request.getServerPort());
   return "I am from  " + request.getServerPort();
}
    

2
我认为那是一个不好的想法。当请求被发出时可以检索到信息。可能在第一次请求之前,人们希望知道启动时的端口。 - Dirk Schumacher
另一个可能严重的问题是,如果错误地适应了HttpServletRequest,则它将被设置为控制器类的私有成员变量。当同时有两个请求时,“request”的设置将被覆盖,因为该类是单例(不是吗?-让我知道)。如果按线程方式实现,则实现将是可以的。(也请参见:https://dev59.com/Y2855IYBdhLWcg3wJg1y#4506382) - Dirk Schumacher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接