From 6403bd96fad1e5e86f3815968f7d8c9fe01f2055 Mon Sep 17 00:00:00 2001 From: Alexey Genus Date: Fri, 16 May 2025 10:07:57 +0200 Subject: [PATCH] Implement CoroutineStubFactory Signed-off-by: Alexey Genus --- spring-grpc-core/pom.xml | 5 +++ .../grpc/client/CoroutineStubFactory.java | 45 +++++++++++++++++++ .../grpc/client/GrpcClientFactoryTests.java | 26 ++++++++++- spring-grpc-dependencies/pom.xml | 6 +++ spring-grpc-spring-boot-autoconfigure/pom.xml | 5 +++ .../client/GrpcClientAutoConfiguration.java | 15 +++++++ .../GrpcClientAutoConfigurationTests.java | 9 ++++ 7 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 spring-grpc-core/src/main/java/org/springframework/grpc/client/CoroutineStubFactory.java diff --git a/spring-grpc-core/pom.xml b/spring-grpc-core/pom.xml index 332016d..352192f 100644 --- a/spring-grpc-core/pom.xml +++ b/spring-grpc-core/pom.xml @@ -93,6 +93,11 @@ com.google.api.grpc proto-google-common-protos + + io.grpc + grpc-kotlin-stub + true + com.salesforce.servicelibs reactor-grpc-stub diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/client/CoroutineStubFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/client/CoroutineStubFactory.java new file mode 100644 index 0000000..4d53b6a --- /dev/null +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/client/CoroutineStubFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright 2025 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.function.Supplier; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ManagedChannel; +import io.grpc.kotlin.AbstractCoroutineStub; + +public class CoroutineStubFactory implements StubFactory> { + + @Override + public AbstractCoroutineStub create(Supplier channel, + Class> type) { + try { + return type.getConstructor(Channel.class, CallOptions.class) + .newInstance(channel.get(), CallOptions.DEFAULT); + + } + catch (Exception e) { + throw new IllegalStateException("Failed to create stub", e); + } + } + + protected static boolean supports(Class type) { + return AbstractCoroutineStub.class.isAssignableFrom(type); + } + +} diff --git a/spring-grpc-core/src/test/java/org/springframework/grpc/client/GrpcClientFactoryTests.java b/spring-grpc-core/src/test/java/org/springframework/grpc/client/GrpcClientFactoryTests.java index 64ca83f..4488e4c 100644 --- a/spring-grpc-core/src/test/java/org/springframework/grpc/client/GrpcClientFactoryTests.java +++ b/spring-grpc-core/src/test/java/org/springframework/grpc/client/GrpcClientFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2024-2024 the original author or authors. + * Copyright 2024-2025 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. @@ -33,6 +33,7 @@ import org.springframework.grpc.client.GrpcClientFactoryTests.MyProto.MyStub; import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.ManagedChannel; +import io.grpc.kotlin.AbstractCoroutineStub; import io.grpc.stub.AbstractStub; public class GrpcClientFactoryTests { @@ -88,6 +89,16 @@ public class GrpcClientFactoryTests { assertThat(factory.getClient("local", OtherStub.class, null)).isNotNull(); } + @Test + void testCoroutineStubFactory() { + context.registerBean(CoroutineStubFactory.class, CoroutineStubFactory::new); + GrpcClientFactory.register(context, + GrpcClientRegistrationSpec.of("local") + .factory(CoroutineStubFactory.class) + .types(MyCoroutineStub.class)); + assertThat(factory.getClient("local", MyCoroutineStub.class, null)).isNotNull(); + } + static class OtherStubFactory implements StubFactory { @Override @@ -135,6 +146,19 @@ public class GrpcClientFactoryTests { } + public static class MyCoroutineStub extends AbstractCoroutineStub { + + public MyCoroutineStub(Channel channel, CallOptions callOptions) { + super(channel, callOptions); + } + + @Override + protected MyCoroutineStub build(Channel channel, CallOptions callOptions) { + return new MyCoroutineStub(channel, callOptions); + } + + } + @Configuration(proxyBeanMethods = false) static class MyConfiguration { diff --git a/spring-grpc-dependencies/pom.xml b/spring-grpc-dependencies/pom.xml index ddc4750..f35a32c 100644 --- a/spring-grpc-dependencies/pom.xml +++ b/spring-grpc-dependencies/pom.xml @@ -50,6 +50,7 @@ 1.72.0 + 1.4.3 4.30.2 2.54.1 1.13.6 @@ -101,6 +102,11 @@ pom import + + io.grpc + grpc-kotlin-stub + ${grpc-kotlin.version} + com.google.protobuf protobuf-bom diff --git a/spring-grpc-spring-boot-autoconfigure/pom.xml b/spring-grpc-spring-boot-autoconfigure/pom.xml index e9d1488..1eecb68 100644 --- a/spring-grpc-spring-boot-autoconfigure/pom.xml +++ b/spring-grpc-spring-boot-autoconfigure/pom.xml @@ -141,6 +141,11 @@ spring-security-web true + + io.grpc + grpc-kotlin-stub + true + io.projectreactor reactor-core diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfiguration.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfiguration.java index a4ec08c..dc8595f 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfiguration.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfiguration.java @@ -18,13 +18,16 @@ package org.springframework.grpc.autoconfigure.client; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.ssl.SslBundles; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.grpc.autoconfigure.common.codec.GrpcCodecConfiguration; import org.springframework.grpc.client.ChannelCredentialsProvider; +import org.springframework.grpc.client.CoroutineStubFactory; import org.springframework.grpc.client.GrpcChannelBuilderCustomizer; import io.grpc.CompressorRegistry; @@ -71,4 +74,16 @@ public class GrpcClientAutoConfiguration { return new ChannelBuilderCustomizers(customizers.orderedStream().toList()); } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "io.grpc.kotlin.AbstractCoroutineStub") + static class GrpcClientCoroutineStubConfiguration { + + @Bean + @ConditionalOnMissingBean + CoroutineStubFactory coroutineStubFactory() { + return new CoroutineStubFactory(); + } + + } + } diff --git a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfigurationTests.java b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfigurationTests.java index 7afe8c6..4660022 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfigurationTests.java +++ b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfigurationTests.java @@ -50,6 +50,7 @@ import io.grpc.Codec; import io.grpc.CompressorRegistry; import io.grpc.DecompressorRegistry; import io.grpc.ManagedChannelBuilder; +import io.grpc.kotlin.AbstractCoroutineStub; import io.grpc.netty.NettyChannelBuilder; import io.grpc.stub.AbstractStub; @@ -73,6 +74,14 @@ class GrpcClientAutoConfigurationTests { .run((context) -> assertThat(context).doesNotHaveBean(GrpcClientAutoConfiguration.class)); } + @Test + void whenGrpcKotlinIsNotOnClasspathThenAutoConfigurationIsSkipped() { + this.contextRunner() + .withClassLoader(new FilteredClassLoader(AbstractCoroutineStub.class)) + .run((context) -> assertThat(context) + .doesNotHaveBean(GrpcClientAutoConfiguration.GrpcClientCoroutineStubConfiguration.class)); + } + @Test void whenClientEnabledPropertySetFalseThenAutoConfigurationIsSkipped() { this.contextRunner()