From 3c8fa3bbd090ed1709ef5fc5f565fba6a7a16541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ver=C3=B3nica=20V=C3=A1squez?= Date: Thu, 19 Sep 2019 19:36:32 -0500 Subject: [PATCH 1/2] Add @LocalRSocketServerPort support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an appication context initializer to detect and store the active RSocket port in the Environment under `local.rsocket.server.port`. Additionally add a `@LocalServerPort` that provides a convenient alternative to `@Value`. See gh-18287 Co-authored-by: Eddú Meléndez --- .../RSocketServerAutoConfigurationTests.java | 13 +++ ...PortInfoApplicationContextInitializer.java | 85 +++++++++++++++++++ .../web/server/LocalRSocketServerPort.java | 42 +++++++++ .../main/resources/META-INF/spring.factories | 3 +- .../SpringApplicationBuilderTests.java | 6 +- .../server/LocalRSocketServerPortTests.java | 55 ++++++++++++ 6 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/context/RSocketPortInfoApplicationContextInitializer.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/LocalRSocketServerPort.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/LocalRSocketServerPortTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java index 6f46a4c8bbc..ac70ddf9357 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java @@ -24,6 +24,7 @@ import org.springframework.boot.rsocket.server.RSocketServerFactory; import org.springframework.boot.rsocket.server.ServerRSocketFactoryCustomizer; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.boot.web.context.RSocketPortInfoApplicationContextInitializer; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -39,6 +40,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link RSocketServerAutoConfiguration}. * * @author Brian Clozel + * @author Verónica Vásquez */ class RSocketServerAutoConfigurationTests { @@ -80,6 +82,17 @@ class RSocketServerAutoConfigurationTests { .hasSingleBean(ServerRSocketFactoryCustomizer.class)); } + @Test + void shouldSetLocalServerPortWhenRSocketServerPortIsSet() { + reactiveWebContextRunner().withPropertyValues("spring.rsocket.server.port=0") + .withInitializer(new RSocketPortInfoApplicationContextInitializer()).run((context) -> { + assertThat(context).hasSingleBean(RSocketServerFactory.class) + .hasSingleBean(RSocketServerBootstrap.class) + .hasSingleBean(ServerRSocketFactoryCustomizer.class); + assertThat(context.getEnvironment().getProperty("local.rsocket.server.port")).isNotNull(); + }); + } + @Test void shouldUseCustomServerBootstrap() { contextRunner().withUserConfiguration(CustomServerBootstrapConfig.class).run((context) -> assertThat(context) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/context/RSocketPortInfoApplicationContextInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/context/RSocketPortInfoApplicationContextInitializer.java new file mode 100644 index 00000000000..f68271df051 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/context/RSocketPortInfoApplicationContextInitializer.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.context; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.rsocket.context.RSocketServerInitializedEvent; +import org.springframework.boot.rsocket.server.RSocketServer; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; + +/** + * {@link ApplicationContextInitializer} that sets {@link Environment} properties for the + * ports that {@link RSocketServer} servers are actually listening on. The property + * {@literal "local.rsocket.server.port"} can be injected directly into tests using + * {@link Value @Value} or obtained via the {@link Environment}. + *

+ * Properties are automatically propagated up to any parent context. + * + * @author Verónica Vásquez + * @author Eddú Meléndez + * @since 2.2.0 + */ +public class RSocketPortInfoApplicationContextInitializer + implements ApplicationContextInitializer, + ApplicationListener { + + private ConfigurableApplicationContext applicationContext; + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + applicationContext.addApplicationListener(this); + this.applicationContext = applicationContext; + } + + @Override + public void onApplicationEvent(RSocketServerInitializedEvent event) { + String propertyName = "local.rsocket.server.port"; + setPortProperty(this.applicationContext, propertyName, event.getrSocketServer().address().getPort()); + } + + private void setPortProperty(ApplicationContext context, String propertyName, int port) { + if (context instanceof ConfigurableApplicationContext) { + setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(), propertyName, port); + } + if (context.getParent() != null) { + setPortProperty(context.getParent(), propertyName, port); + } + } + + @SuppressWarnings("unchecked") + private void setPortProperty(ConfigurableEnvironment environment, String propertyName, int port) { + MutablePropertySources sources = environment.getPropertySources(); + PropertySource source = sources.get("server.ports"); + if (source == null) { + source = new MapPropertySource("server.ports", new HashMap<>()); + sources.addFirst(source); + } + ((Map) source.getSource()).put(propertyName, port); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/LocalRSocketServerPort.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/LocalRSocketServerPort.java new file mode 100644 index 00000000000..a44ce5f06c2 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/LocalRSocketServerPort.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.server; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.beans.factory.annotation.Value; + +/** + * Annotation at the field or method/constructor parameter level that injects the RSocket + * port that got allocated at runtime. Provides a convenient alternative for + * @Value("${local.rsocket.server.port}"). + * + * @author Verónica Vásquez + * @author Eddú Meléndez + * @since 2.2.0 + */ +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Value("${local.rsocket.server.port}") +public @interface LocalRSocketServerPort { + +} diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories index cbaf16b4ab1..2641b68becb 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories @@ -16,7 +16,8 @@ org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\ org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ -org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer +org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer,\ +org.springframework.boot.web.context.RSocketPortInfoApplicationContextInitializer # Application Listeners org.springframework.context.ApplicationListener=\ diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java index 87f8f2bc8b9..8f374ad1531 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java @@ -240,7 +240,7 @@ class SpringApplicationBuilderTests { SpringApplicationBuilder application = new SpringApplicationBuilder(ExampleConfig.class) .web(WebApplicationType.NONE); this.context = application.run(); - assertThat(application.application().getInitializers()).hasSize(4); + assertThat(application.application().getInitializers()).hasSize(5); } @Test @@ -248,7 +248,7 @@ class SpringApplicationBuilderTests { SpringApplicationBuilder application = new SpringApplicationBuilder(ExampleConfig.class) .child(ChildConfig.class).web(WebApplicationType.NONE); this.context = application.run(); - assertThat(application.application().getInitializers()).hasSize(5); + assertThat(application.application().getInitializers()).hasSize(6); } @Test @@ -257,7 +257,7 @@ class SpringApplicationBuilderTests { .web(WebApplicationType.NONE).initializers((ConfigurableApplicationContext applicationContext) -> { }); this.context = application.run(); - assertThat(application.application().getInitializers()).hasSize(5); + assertThat(application.application().getInitializers()).hasSize(6); } @Test diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/LocalRSocketServerPortTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/LocalRSocketServerPortTests.java new file mode 100644 index 00000000000..bde44bd33cf --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/LocalRSocketServerPortTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.server; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LocalRSocketServerPort @LocalRSocketServerPort}. + * + * @author Verónica Vásquez + * @author Eddú Meléndez + */ +@ExtendWith(SpringExtension.class) +@TestPropertySource(properties = "local.rsocket.server.port=8181") +class LocalRSocketServerPortTests { + + @Value("${local.rsocket.server.port}") + private String fromValue; + + @LocalRSocketServerPort + private String fromAnnotation; + + @Test + void testLocalRSocketServerPortAnnotation() { + assertThat(this.fromAnnotation).isNotNull().isEqualTo(this.fromValue); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + } + +} From 4b0fb8ff242f928cceac25d5cbcee80380bad68b Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 27 Sep 2019 21:02:57 -0700 Subject: [PATCH 2/2] Polish 'Add @LocalRSocketServerPort support' See gh-18287 --- .../RSocketServerAutoConfigurationTests.java | 2 +- .../context}/LocalRSocketServerPort.java | 2 +- ...PortInfoApplicationContextInitializer.java | 68 +++++++++++-------- .../main/resources/META-INF/spring.factories | 4 +- .../context}/LocalRSocketServerPortTests.java | 2 +- 5 files changed, 44 insertions(+), 34 deletions(-) rename spring-boot-project/spring-boot/src/main/java/org/springframework/boot/{web/server => rsocket/context}/LocalRSocketServerPort.java (96%) rename spring-boot-project/spring-boot/src/main/java/org/springframework/boot/{web => rsocket}/context/RSocketPortInfoApplicationContextInitializer.java (57%) rename spring-boot-project/spring-boot/src/test/java/org/springframework/boot/{web/server => rsocket/context}/LocalRSocketServerPortTests.java (97%) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java index ac70ddf9357..cf4fedaeea8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java @@ -19,12 +19,12 @@ package org.springframework.boot.autoconfigure.rsocket; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer; import org.springframework.boot.rsocket.server.RSocketServerBootstrap; import org.springframework.boot.rsocket.server.RSocketServerFactory; import org.springframework.boot.rsocket.server.ServerRSocketFactoryCustomizer; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; -import org.springframework.boot.web.context.RSocketPortInfoApplicationContextInitializer; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/LocalRSocketServerPort.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/context/LocalRSocketServerPort.java similarity index 96% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/LocalRSocketServerPort.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/context/LocalRSocketServerPort.java index a44ce5f06c2..7da1f6a32c9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/LocalRSocketServerPort.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/context/LocalRSocketServerPort.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.web.server; +package org.springframework.boot.rsocket.context; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/context/RSocketPortInfoApplicationContextInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/context/RSocketPortInfoApplicationContextInitializer.java similarity index 57% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/context/RSocketPortInfoApplicationContextInitializer.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/context/RSocketPortInfoApplicationContextInitializer.java index f68271df051..09fb5d05f5a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/context/RSocketPortInfoApplicationContextInitializer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/context/RSocketPortInfoApplicationContextInitializer.java @@ -14,13 +14,12 @@ * limitations under the License. */ -package org.springframework.boot.web.context; +package org.springframework.boot.rsocket.context; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.rsocket.context.RSocketServerInitializedEvent; import org.springframework.boot.rsocket.server.RSocketServer; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; @@ -45,41 +44,52 @@ import org.springframework.core.env.PropertySource; * @since 2.2.0 */ public class RSocketPortInfoApplicationContextInitializer - implements ApplicationContextInitializer, - ApplicationListener { - - private ConfigurableApplicationContext applicationContext; + implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { - applicationContext.addApplicationListener(this); - this.applicationContext = applicationContext; + applicationContext.addApplicationListener(new Listener(applicationContext)); } - @Override - public void onApplicationEvent(RSocketServerInitializedEvent event) { - String propertyName = "local.rsocket.server.port"; - setPortProperty(this.applicationContext, propertyName, event.getrSocketServer().address().getPort()); - } + private static class Listener implements ApplicationListener { - private void setPortProperty(ApplicationContext context, String propertyName, int port) { - if (context instanceof ConfigurableApplicationContext) { - setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(), propertyName, port); - } - if (context.getParent() != null) { - setPortProperty(context.getParent(), propertyName, port); - } - } + private static final String PROPERTY_NAME = "local.rsocket.server.port"; - @SuppressWarnings("unchecked") - private void setPortProperty(ConfigurableEnvironment environment, String propertyName, int port) { - MutablePropertySources sources = environment.getPropertySources(); - PropertySource source = sources.get("server.ports"); - if (source == null) { - source = new MapPropertySource("server.ports", new HashMap<>()); - sources.addFirst(source); + private ConfigurableApplicationContext applicationContext; + + Listener(ConfigurableApplicationContext applicationContext) { + this.applicationContext = applicationContext; } - ((Map) source.getSource()).put(propertyName, port); + + @Override + public void onApplicationEvent(RSocketServerInitializedEvent event) { + setPortProperty(this.applicationContext, event.getrSocketServer().address().getPort()); + } + + private void setPortProperty(ApplicationContext context, int port) { + if (context instanceof ConfigurableApplicationContext) { + setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(), port); + } + if (context.getParent() != null) { + setPortProperty(context.getParent(), port); + } + } + + private void setPortProperty(ConfigurableEnvironment environment, int port) { + MutablePropertySources sources = environment.getPropertySources(); + PropertySource source = sources.get("server.ports"); + if (source == null) { + source = new MapPropertySource("server.ports", new HashMap<>()); + sources.addFirst(source); + } + setPortProperty(port, source); + } + + @SuppressWarnings("unchecked") + private void setPortProperty(int port, PropertySource source) { + ((Map) source.getSource()).put(PROPERTY_NAME, port); + } + } } diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories index 2641b68becb..137c661cf9b 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories @@ -16,8 +16,8 @@ org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\ org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ -org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer,\ -org.springframework.boot.web.context.RSocketPortInfoApplicationContextInitializer +org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\ +org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer # Application Listeners org.springframework.context.ApplicationListener=\ diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/LocalRSocketServerPortTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/rsocket/context/LocalRSocketServerPortTests.java similarity index 97% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/LocalRSocketServerPortTests.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/rsocket/context/LocalRSocketServerPortTests.java index bde44bd33cf..452a7d8e1d3 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/LocalRSocketServerPortTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/rsocket/context/LocalRSocketServerPortTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.web.server; +package org.springframework.boot.rsocket.context; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith;