Add @LocalRSocketServerPort support

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 <eddu.melendez@gmail.com>
pull/18413/head
Verónica Vásquez 5 years ago committed by Phillip Webb
parent ec747d6b2e
commit 3c8fa3bbd0

@ -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)

@ -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}.
* <p>
* 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<ConfigurableApplicationContext>,
ApplicationListener<RSocketServerInitializedEvent> {
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<String, Object>) source.getSource()).put(propertyName, port);
}
}

@ -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
* <code>&#064;Value(&quot;${local.rsocket.server.port}&quot;)</code>.
*
* @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 {
}

@ -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=\

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

@ -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 {
}
}
Loading…
Cancel
Save