ChatGPT解决这个技术问题 Extra ChatGPT

Spring Boot - 如何获取正在运行的端口

我有一个 spring boot 应用程序(使用嵌入式 tomcat 7),我在我的 application.properties 中设置了 server.port = 0,所以我可以有一个随机端口。服务器启动并在端口上运行后,我需要能够获取所选的端口。

我不能使用 @Value("$server.port"),因为它为零。这是一条看似简单的信息,为什么我不能从我的 java 代码中访问它呢?我怎样才能访问它?

另一种可能性可以在文档中找到:docs.spring.io/spring-boot/docs/current/reference/html/…(请参阅 64.5 在运行时发现 HTTP 端口)

L
LaughingLemon

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

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

    @LocalServerPort
    int randomServerPort;

    @LocalManagementPort
    int randomManagementPort;

@LocalServerPort 只是 @Value("${local.server.port}") 的快捷方式。
@deamon 意味着如果你没有在属性中指定 local.server.port - 它不会工作
使用 webEnvironment = WebEnvironment.RANDOM_PORT 解决了该问题。谢谢
M
Matthew Wise

Spring 的 Environment 为您保存这些信息。

@Autowired
Environment environment;

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

从表面上看,这看起来与注入带注释的 @Value("${local.server.port}")(或 @LocalServerPort,相同)的字段相同,因此在启动时会引发自动装配失败,因为在上下文完全初始化之前该值不可用。此处的不同之处在于,此调用是在运行时业务逻辑中隐式进行的,而不是在应用程序启动时调用,因此端口的“延迟获取”可以解决。


由于某种原因,这对我不起作用,environment.getProperty("server.port") 做到了。
在我的情况下,portnull@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) @ActiveProfiles("test") public class SpringBootH2IntegrationTest { @Autowired Environment environment; @Test public void test() { String port = environment.getProperty("local.server.port"); // null here } 添加后:SpringBootTest 旁边的 webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 我看到了端口。
这是因为@SpringBootTest 默认为 MockMVC 环境,而不是启动可以提供端口的应用服务器。
T
Tucker

感谢@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();
      }
    }

AFAIK 如果您要使用服务器端口配置 bean,这将不起作用。直到所有 bean 都已加载且 servlet 已注册后,才会触发此事件。
当时它对我有用,这就是我接受它的原因。不过,我还没有尝试过hennr的答案。
阅读文档后,我想出了与您几乎相同的小类,将其命名为 PortProvider,并提供了一个 getPort() 方法。将我的 PortProvider 自动连接到需要端口的控制器中,当我的业务逻辑调用 portProvider.getPort() 时,运行时端口被返回。
对于尝试使用 Spring Boot 2.0 或更高版本的任何人,API 似乎略有变化。我不再能够订阅 EmbeddedServletContainerInitializedEvent,但有一个名为 ServletWebServerInitializedEvent 的类似类,它有一个 .getWebServer() 方法。这将为您提供至少 Tomcat 正在侦听的端口。
b
bsyk

您可以通过注入 local.server.port 值来获取嵌入式 Tomcat 实例在测试期间使用的端口,如下所示:

// Inject which port we were assigned
@Value("${local.server.port}")
int port;

local.server.port 仅在与 @WebIntegrationTests 一起运行时设置
WebIntegrationTest 已弃用。
J
Jacomoman

就像其他像我一样配置了他们的应用程序的人从我所经历的事情中受益......

以上解决方案都不适合我,因为我的项目库下有一个 ./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 时,将从 ./config 目录读取 *.properties 文件,一切都很好。

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

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

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

然后,直到那时,Spring 才终于开始在一个随机端口上启动 Tomcat。恕我直言,这是一个 Spring 测试框架错误,但我相信他们对此会有自己的看法。

希望这对某人有所帮助。


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

从 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;

  // ...
}

m
mre

这些解决方案都不适合我。在构建 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)。


我想要同样的东西来招摇
s
sam

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

    @Autowired
    private ServletWebServerApplicationContext server;

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

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

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


这是 Spring Boot 2 的正确答案。适用于 @SpringBootTest 和 WebEnvironment.RANDOM_PORT。
M
Mafei

您可以从

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();
}
    

我认为这是个坏主意。可以在发出请求时检索信息。可能有人想在发出第一个请求之前知道启动时的端口。
如果调整不当,另一个可能很严重的问题是 HttpServletRequest 被设置为控制器类的私有成员变量。当在“同一”时间有两个请求时,“请求”的设置将被覆盖,因为该类是一个单例(不是吗? - 让我知道)如果它是线程方式的,那么实现就可以了。 (另见:stackoverflow.com/a/4506382/845117
L
Leon Deng

我在 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);
    }

}

A
Ahmed

请确保您已导入正确的包

import org.springframework.core.env.Environment;

然后使用 Environment 对象

@Autowired
private Environment env;    // Environment Object containts the port number

 @GetMapping("/status")
  public String status()
    {
   return "it is runing on"+(env.getProperty("local.server.port"));
    }

我已经对我的答案进行了更改,您仍然遇到同样的问题吗?
J
Janning

我用一种代理bean解决了它。客户端在需要时被初始化,到那时端口应该可用:

@Component
public class GraphQLClient {

    private ApolloClient apolloClient;
    private final Environment environment;

    public GraphQLClient(Environment environment) {
        this.environment = environment;
    }

    public ApolloClient getApolloClient() {
        if (apolloClient == null) {
            String port = environment.getProperty("local.server.port");
            initApolloClient(port);
        }
        return apolloClient;
    }

    public synchronized void initApolloClient(String port) {
        this.apolloClient = ApolloClient.builder()
                .serverUrl("http://localhost:" + port + "/graphql")
                .build();
    }

    public <D extends Operation.Data, T, V extends Operation.Variables> GraphQLCallback<T> graphql(Operation<D, T, V> operation) {
        GraphQLCallback<T> graphQLCallback = new GraphQLCallback<>();
        if (operation instanceof Query) {
            Query<D, T, V> query = (Query<D, T, V>) operation;
            getApolloClient()
                    .query(query)
                    .enqueue(graphQLCallback);
        } else {
            Mutation<D, T, V> mutation = (Mutation<D, T, V>) operation;
            getApolloClient()
                    .mutate(mutation)
                    .enqueue(graphQLCallback);

        }
        return graphQLCallback;
    }
}