Spring Boot - Jak uzyskać działający port


89

Mam aplikację do rozruchu sprężynowego (przy użyciu osadzonego tomcat 7) i ustawiłem server.port = 0 w moim, application.propertieswięc mogę mieć losowy port. Po uruchomieniu serwera i uruchomieniu go na porcie muszę mieć możliwość uzyskania portu, który został wybrany.

Nie mogę użyć, @Value("$server.port")ponieważ jest zero. To pozornie prosta informacja, więc dlaczego nie mogę uzyskać do niej dostępu z mojego kodu java? Jak mogę uzyskać do niego dostęp?



Inną możliwość można znaleźć w docs: docs.spring.io/spring-boot/docs/current/reference/html/ ... (patrz 64.5 Odkryj port HTTP w czasie wykonywania)
— Dirk Lachowski

Odpowiedzi:


95

Czy można również uzyskać dostęp do portu zarządzania w podobny sposób, np .:

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

    @LocalServerPort
    int randomServerPort;

    @LocalManagementPort
    int randomManagementPort;

7
@LocalServerPortto tylko skrót do @Value("${local.server.port}").
— deamon

@deamon oznacza, że ​​jeśli nie określisz local.server.port we właściwościach - to nie będzie
— działać

79

Spring's Environment przechowuje te informacje dla Ciebie.

@Autowired
Environment environment;

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

Z pozoru wygląda to tak samo, jak wstawianie pola z adnotacjami @Value("${local.server.port}")(lub @LocalServerPort, które jest identyczne), w wyniku czego podczas uruchamiania zgłaszany jest błąd automatycznego okablowania, ponieważ wartość nie jest dostępna do momentu pełnej inicjalizacji kontekstu. Różnica polega na tym, że to wywołanie jest niejawnie wykonywane w logice biznesowej środowiska wykonawczego, a nie jest wywoływane podczas uruchamiania aplikacji, a zatem „leniwe pobieranie” portu rozwiązuje się prawidłowo.


4
z jakiegoś powodu to nie zadziałało dla mnie environment.getProperty("server.port").
— Anand Rockzz

24

Dziękuję @Dirk Lachowski za wskazanie mi właściwego kierunku. Rozwiązanie nie jest tak eleganckie, jak bym sobie życzył, ale udało mi się. Czytając dokumentację Spring, mogę nasłuchiwać w EmbeddedServletContainerInitializedEvent i uzyskać port, gdy serwer jest już uruchomiony. Oto, jak to wygląda -

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 to nie zadziała, jeśli chcesz skonfigurować bean z portem serwera. To zdarzenie jest uruchamiane dopiero po załadowaniu wszystkich komponentów bean i zarejestrowaniu serwletów.
— MRE

to działało dla mnie w tamtym czasie, dlatego to zaakceptowałem. Nie próbowałem jednak odpowiedzi Hennra.
— Tucker,

Po przeczytaniu dokumentów wymyśliłem praktycznie tę samą małą klasę co ty, nadając jej nazwę PortProvideri podając getPort()metodę. Automatycznie połączyłem PortProvidersię z kontrolerem wymagającym portu, a gdy zadzwoniła logika biznesowa portProvider.getPort(), zwrócony został port środowiska wykonawczego.
— Matthew Wise

11
Dla każdego, kto spróbuje tego z Spring Boot 2.0 lub nowszym, wydaje się, że interfejs API nieco się zmienił. Nie mogłem już subskrybować EmbeddedServletContainerInitializedEvent, ale istnieje podobna klasa o nazwie, ServletWebServerInitializedEventktóra ma .getWebServer()metodę. Pozwoli ci to przynajmniej uzyskać port, którego słucha Tomcat.
— NiteLite

17

Tylko po to, aby inni, którzy skonfigurowali swoje aplikacje takie jak moja, skorzystali z tego, przez co przeszedłem ...

Żadne z powyższych rozwiązań nie zadziałało, ponieważ mam ./configkatalog tuż pod bazą projektu z 2 plikami:

application.properties
application-dev.properties

W application.propertiesmam:

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

W application-dev.propertiesmam:

server_host = localhost
server_port = 8080

Dzieje się tak, gdy uruchomię mój fat jar z CLI, *.propertiespliki zostaną odczytane z katalogu ./configi wszystko jest w porządku.

Cóż, okazuje się, że te pliki właściwości całkowicie zastępują webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORTustawienie @SpringBootTestw moich specyfikacjach Spocka. Bez względu na to, co próbowałem, nawet przy webEnvironmentustawieniu na RANDOM_PORTSpring zawsze uruchamiałby osadzony kontener Tomcat na porcie 8080 (lub jakąkolwiek wartość, którą ustawiłem w moich ./config/*.propertiesplikach).

TYLKO sposób udało mi się przezwyciężyć ten był wyraźny dodając properties = "server_port=0"do @SpringBootTestadnotacji w moich specyfikacji integracyjnych Spock:

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

Wtedy i dopiero wtedy Spring wreszcie zaczęła uruchamiać Tomcata na losowym porcie. IMHO to jest błąd w ramach testów wiosennych, ale jestem pewien, że będą mieli na ten temat własne zdanie.

Mam nadzieję, że to komuś pomogło.


Miej dokładnie taką samą konfigurację, a także wpadłem na to. Zakładałem, że to w pewnym sensie problem, ale dziękuję za opublikowanie tutaj rozwiązania. Czy wiesz, czy ktoś już zarejestrował to jako błąd?
— bvulaj

15

Możesz pobrać port używany przez osadzoną instancję Tomcat podczas testów, wstrzykując wartość local.server.port jako taką:

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

17
local.server.portjest ustawiana tylko podczas pracy z@WebIntegrationTests
— ejain


12

Począwszy od Spring Boot 1.4.0, możesz użyć tego w swoim teście:

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

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

  @LocalServerPort
  int randomPort;

  // ...
}

8

Żadne z tych rozwiązań nie działało dla mnie. Musiałem znać port serwera podczas tworzenia komponentu bean konfiguracji Swaggera. Korzystanie z ServerProperties zadziałało:

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         
    }
}

W tym przykładzie zastosowano automatyczną konfigurację Spring Boot i JAX-RS (nie Spring MVC).


1
Chciałem tego samego dla
— dumy

1

Po Spring Boot 2 wiele się zmieniło. Powyższe odpowiedzi działają przed Spring Boot 2. Teraz, jeśli uruchamiasz aplikację z argumentami runtime dla portu serwera, otrzymasz tylko wartość statyczną @Value("${server.port}"), która jest wymieniona w pliku application.properties . Teraz, aby uzyskać rzeczywisty port, na którym działa serwer, użyj następującej metody:

    @Autowired
    private ServletWebServerApplicationContext server;

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

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

Ponadto, jeśli używasz aplikacji jako klientów Eureka / Discovery ze zrównoważonym obciążeniem RestTemplatelub WebClientpowyższa metoda zwróci dokładny numer portu.


1
To jest właściwa odpowiedź na Spring Boot 2. Działa dobrze z @SpringBootTest i WebEnvironment.RANDOM_PORT.
— Ken Pronovici

1

Możesz uzyskać port serwera z

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

0

Upewnij się, że zaimportowałeś właściwy pakiet

import org.springframework.core.env.Environment;

a następnie użyj obiektu 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"));
    }

1
Wprowadziłem zmiany w mojej odpowiedzi. Czy nadal masz ten sam problem?
— Ahmed

0

Rozwiązałem to za pomocą czegoś w rodzaju fasoli zastępczej. Klient jest inicjowany, gdy jest potrzebny, do tego czasu port powinien być dostępny:

@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;
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.