From 2249f6e2b36976d16db6cbb3e2d5f9538d5f8904 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Mon, 9 Sep 2024 13:25:45 +0100 Subject: [PATCH] Support for @LocalGrpcPort in @Lazy beans --- .../sample/GrpcServerApplicationTests.java | 9 ++- .../grpc/client/GrpcChannelConfigurer.java | 35 ++++++++++ .../server/lifecycle/GrpcServerLifecycle.java | 4 ++ .../grpc/test/LocalGrpcPort.java | 32 +++++++++ ...PortInfoApplicationContextInitializer.java | 70 +++++++++++++++++++ .../main/resources/META-INF/spring.factories | 3 + 6 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 spring-grpc-core/src/main/java/org/springframework/grpc/client/GrpcChannelConfigurer.java create mode 100644 spring-grpc-test/src/main/java/org/springframework/grpc/test/LocalGrpcPort.java create mode 100644 spring-grpc-test/src/main/java/org/springframework/grpc/test/ServerPortInfoApplicationContextInitializer.java create mode 100644 spring-grpc-test/src/main/resources/META-INF/spring.factories diff --git a/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java b/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java index 88048eb..df43579 100644 --- a/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java +++ b/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java @@ -10,13 +10,15 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Lazy; import org.springframework.grpc.client.GrpcChannelFactory; import org.springframework.grpc.sample.proto.HelloReply; import org.springframework.grpc.sample.proto.HelloRequest; import org.springframework.grpc.sample.proto.SimpleGrpc; +import org.springframework.grpc.test.LocalGrpcPort; import org.springframework.test.annotation.DirtiesContext; -@SpringBootTest +@SpringBootTest(properties = "spring.grpc.server.port=0") public class GrpcServerApplicationTests { private static Log log = LogFactory.getLog(GrpcServerApplicationTests.class); @@ -45,8 +47,9 @@ public class GrpcServerApplicationTests { static class ExtraConfiguration { @Bean - SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channels) { - return SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:9090").build()); + @Lazy + SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channels, @LocalGrpcPort int port) { + return SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:" + port).build()); } } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/client/GrpcChannelConfigurer.java b/spring-grpc-core/src/main/java/org/springframework/grpc/client/GrpcChannelConfigurer.java new file mode 100644 index 0000000..d5d4ab3 --- /dev/null +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/client/GrpcChannelConfigurer.java @@ -0,0 +1,35 @@ +/* + * Copyright 2024-2024 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.grpc.client; + +import java.util.Objects; +import java.util.function.Consumer; + +import io.grpc.ManagedChannelBuilder; + +@FunctionalInterface +public interface GrpcChannelConfigurer extends Consumer> { + + @Override + default GrpcChannelConfigurer andThen(final Consumer> after) { + Objects.requireNonNull(after); + return t -> { + accept(t); + after.accept(t); + }; + } + +} diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/lifecycle/GrpcServerLifecycle.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/lifecycle/GrpcServerLifecycle.java index 7ea0f4a..fc03266 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/lifecycle/GrpcServerLifecycle.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/lifecycle/GrpcServerLifecycle.java @@ -103,6 +103,10 @@ public class GrpcServerLifecycle implements SmartLifecycle { return true; } + public int getPort() { + return this.server == null ? 0 : this.server.getPort(); + } + /** * Creates and starts the grpc server. * @throws IOException If the server is unable to bind the port. diff --git a/spring-grpc-test/src/main/java/org/springframework/grpc/test/LocalGrpcPort.java b/spring-grpc-test/src/main/java/org/springframework/grpc/test/LocalGrpcPort.java new file mode 100644 index 0000000..fe73a03 --- /dev/null +++ b/spring-grpc-test/src/main/java/org/springframework/grpc/test/LocalGrpcPort.java @@ -0,0 +1,32 @@ +/* + * Copyright 2024-2024 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.grpc.test; + +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; + +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Value("${local.grpc.port}") +public @interface LocalGrpcPort { + +} diff --git a/spring-grpc-test/src/main/java/org/springframework/grpc/test/ServerPortInfoApplicationContextInitializer.java b/spring-grpc-test/src/main/java/org/springframework/grpc/test/ServerPortInfoApplicationContextInitializer.java new file mode 100644 index 0000000..3e4b373 --- /dev/null +++ b/spring-grpc-test/src/main/java/org/springframework/grpc/test/ServerPortInfoApplicationContextInitializer.java @@ -0,0 +1,70 @@ +/* + * Copyright 2024-2024 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.grpc.test; + +import java.util.HashMap; +import java.util.Map; + +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.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.grpc.server.lifecycle.GrpcServerStartedEvent; + +public class ServerPortInfoApplicationContextInitializer implements + ApplicationContextInitializer, ApplicationListener { + + private static final String PROPERTY_SOURCE_NAME = "grpc.server.ports"; + + private ConfigurableApplicationContext applicationContext; + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + this.applicationContext = applicationContext; + applicationContext.addApplicationListener(this); + } + + @Override + public void onApplicationEvent(GrpcServerStartedEvent event) { + String propertyName = "local.grpc.port"; + setPortProperty(applicationContext, propertyName, event.getPort()); + } + + private void setPortProperty(ApplicationContext context, String propertyName, int port) { + if (context instanceof ConfigurableApplicationContext configurableContext) { + setPortProperty(configurableContext.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(PROPERTY_SOURCE_NAME); + if (source == null) { + source = new MapPropertySource(PROPERTY_SOURCE_NAME, new HashMap<>()); + sources.addFirst(source); + } + ((Map) source.getSource()).put(propertyName, port); + } + +} diff --git a/spring-grpc-test/src/main/resources/META-INF/spring.factories b/spring-grpc-test/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..93d33e3 --- /dev/null +++ b/spring-grpc-test/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Application Context Initializers +org.springframework.context.ApplicationContextInitializer=\ +org.springframework.grpc.test.ServerPortInfoApplicationContextInitializer