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?

Wielkie żarcie
źródło
Związane z: stackoverflow.com/a/24643484/1686330
Dirk Lachowski
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;
LaughingLemon
źródło
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.

hennr
źródło
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();
      }
    }
Wielkie żarcie
źródło
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.

Jacomoman
źródł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;
bsyk
źródło
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;

  // ...
}
jabal
źródło
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).

mre
źródło
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.

sam
źródło
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();
}
    
mafei
źródło
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"));
    }
Ahmed
źródło
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;
    }
}
Janning
źródło