Implement CoroutineStubFactory

Signed-off-by: Alexey Genus <genus.alexey@gmail.com>
This commit is contained in:
Alexey Genus
2025-05-16 10:07:57 +02:00
committed by Dave Syer
parent 7de96df5bd
commit 6403bd96fa
7 changed files with 110 additions and 1 deletions

View File

@@ -93,6 +93,11 @@
<groupId>com.google.api.grpc</groupId>
<artifactId>proto-google-common-protos</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-kotlin-stub</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.salesforce.servicelibs</groupId>
<artifactId>reactor-grpc-stub</artifactId>

View File

@@ -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<AbstractCoroutineStub<?>> {
@Override
public AbstractCoroutineStub<?> create(Supplier<ManagedChannel> channel,
Class<? extends AbstractCoroutineStub<?>> 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);
}
}

View File

@@ -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<OtherStub> {
@Override
@@ -135,6 +146,19 @@ public class GrpcClientFactoryTests {
}
public static class MyCoroutineStub extends AbstractCoroutineStub<MyCoroutineStub> {
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 {

View File

@@ -50,6 +50,7 @@
<properties>
<grpc.version>1.72.0</grpc.version>
<grpc-kotlin.version>1.4.3</grpc-kotlin.version>
<protobuf-java.version>4.30.2</protobuf-java.version>
<google-common-protos.version>2.54.1</google-common-protos.version>
<micrometer.version>1.13.6</micrometer.version>
@@ -101,6 +102,11 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-kotlin-stub</artifactId>
<version>${grpc-kotlin.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-bom</artifactId>

View File

@@ -141,6 +141,11 @@
<artifactId>spring-security-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-kotlin-stub</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>

View File

@@ -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();
}
}
}

View File

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