Add support for in-process outside of tests
This commit allows the usage of inprocess server and channel factories outside of tests. Resolves #144 Signed-off-by: Chris Bono <chris.bono@gmail.com>
This commit is contained in:
@@ -29,7 +29,7 @@ import io.grpc.reflection.v1.ServerReflectionRequest;
|
||||
import io.grpc.reflection.v1.ServerReflectionResponse;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
@SpringBootTest(properties = { "debug=true", "spring.grpc.server.port=0",
|
||||
@SpringBootTest(properties = { "spring.grpc.server.port=0",
|
||||
"spring.grpc.client.default-channel.address=static://0.0.0.0:${local.grpc.port}" })
|
||||
@DirtiesContext
|
||||
public class GrpcServerApplicationTests {
|
||||
|
||||
@@ -71,6 +71,11 @@
|
||||
<artifactId>netty-transport-native-epoll</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-inprocess</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-protobuf</artifactId>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2025-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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import io.grpc.ManagedChannel;
|
||||
|
||||
/**
|
||||
* A composite {@link GrpcChannelFactory} that combines a list of channel factories.
|
||||
* <p>
|
||||
* The composite delegates channel creation to the first composed factory that supports
|
||||
* the given target string.
|
||||
*
|
||||
* @author Chris Bono
|
||||
*/
|
||||
public class CompositeGrpcChannelFactory implements GrpcChannelFactory {
|
||||
|
||||
private List<GrpcChannelFactory> channelFactories = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a new CompositeGrpcChannelFactory with the given factories.
|
||||
* @param channelFactories the channel factories
|
||||
*/
|
||||
public CompositeGrpcChannelFactory(List<GrpcChannelFactory> channelFactories) {
|
||||
Assert.notEmpty(channelFactories, "composite channel factory requires at least one channel factory");
|
||||
this.channelFactories.addAll(channelFactories);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(String target) {
|
||||
return this.channelFactories.stream().anyMatch((cf) -> cf.supports(target));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedChannel createChannel(final String target, ChannelBuilderOptions options) {
|
||||
return this.channelFactories.stream()
|
||||
.filter((cf) -> cf.supports(target))
|
||||
.findFirst()
|
||||
.orElseThrow(
|
||||
() -> new IllegalStateException("No grpc channel factory found that supports target : " + target))
|
||||
.createChannel(target, options);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -70,6 +70,18 @@ public class DefaultGrpcChannelFactory<T extends ManagedChannelBuilder<T>>
|
||||
this.interceptorsConfigurer = interceptorsConfigurer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this factory supports the given target string. The target can be either a
|
||||
* valid nameresolver-compliant URI, an authority string as described in
|
||||
* {@link Grpc#newChannelBuilder(String, ChannelCredentials)}.
|
||||
* @param target the target string as described in method javadocs
|
||||
* @return true unless the target begins with 'in-process:'
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(String target) {
|
||||
return !target.startsWith("in-process:");
|
||||
}
|
||||
|
||||
public void setVirtualTargets(VirtualTargets targets) {
|
||||
this.targets = targets;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,15 @@ import io.grpc.ManagedChannel;
|
||||
*/
|
||||
public interface GrpcChannelFactory {
|
||||
|
||||
/**
|
||||
* Whether this factory supports the given target string. The target can be either a
|
||||
* valid nameresolver-compliant URI, an authority string as described in
|
||||
* {@link Grpc#newChannelBuilder(String, ChannelCredentials)}.
|
||||
* @param target the target string as described in method javadocs
|
||||
* @return whether this factory supports the given target string
|
||||
*/
|
||||
boolean supports(String target);
|
||||
|
||||
/**
|
||||
* Creates a {@link ManagedChannel} for the given target string. The target can be
|
||||
* either a valid nameresolver-compliant URI, an authority string as described in
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.List;
|
||||
|
||||
import io.grpc.ChannelCredentials;
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
|
||||
/**
|
||||
* {@link GrpcChannelFactory} that creates in-process gRPC channels.
|
||||
*
|
||||
* @author Chris Bono
|
||||
*/
|
||||
public class InProcessGrpcChannelFactory extends DefaultGrpcChannelFactory<InProcessChannelBuilder> {
|
||||
|
||||
/**
|
||||
* Construct an in-process channel factory instance and sets the
|
||||
* {@link #setVirtualTargets virtualTargets} to the identity function so that the
|
||||
* exact passed in target string is used as the target of the channel factory.
|
||||
* @param globalCustomizers the global customizers to apply to all created channels
|
||||
* @param interceptorsConfigurer configures the client interceptors on the created
|
||||
* channels
|
||||
*/
|
||||
public InProcessGrpcChannelFactory(List<GrpcChannelBuilderCustomizer<InProcessChannelBuilder>> globalCustomizers,
|
||||
ClientInterceptorsConfigurer interceptorsConfigurer) {
|
||||
super(globalCustomizers, interceptorsConfigurer);
|
||||
setVirtualTargets((p) -> p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this factory supports the given target string. The target can be either a
|
||||
* valid nameresolver-compliant URI, an authority string as described in
|
||||
* {@link Grpc#newChannelBuilder(String, ChannelCredentials)}.
|
||||
* @param target the target string as described in method javadocs
|
||||
* @return true if the target begins with 'in-process:'
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(String target) {
|
||||
return target.startsWith("in-process:");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InProcessChannelBuilder newChannelBuilder(String target, ChannelCredentials creds) {
|
||||
return InProcessChannelBuilder.forName(target.substring(11));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -13,15 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.grpc.test;
|
||||
package org.springframework.grpc.server;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.grpc.server.DefaultGrpcServerFactory;
|
||||
import org.springframework.grpc.server.ServerBuilderCustomizer;
|
||||
|
||||
import io.grpc.inprocess.InProcessServerBuilder;
|
||||
|
||||
/**
|
||||
* {@link GrpcServerFactory} that can be used to create an in-process gRPC server.
|
||||
*
|
||||
* @author Chris Bono
|
||||
*/
|
||||
public class InProcessGrpcServerFactory extends DefaultGrpcServerFactory<InProcessServerBuilder> {
|
||||
|
||||
public InProcessGrpcServerFactory(String address,
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2023-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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import io.grpc.ManagedChannel;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link CompositeGrpcChannelFactory}.
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
class CompositeGrpcChannelFactoryTests {
|
||||
|
||||
private TestChannelFactory fooChannelFactory;
|
||||
|
||||
private TestChannelFactory barChannelFactory;
|
||||
|
||||
private CompositeGrpcChannelFactory compositeChannelFactory;
|
||||
|
||||
@BeforeEach
|
||||
void prepareFactories() {
|
||||
this.fooChannelFactory = new TestChannelFactory("foo");
|
||||
this.barChannelFactory = new TestChannelFactory("bar");
|
||||
this.compositeChannelFactory = new CompositeGrpcChannelFactory(List.of(fooChannelFactory, barChannelFactory));
|
||||
}
|
||||
|
||||
@Test
|
||||
void atLeastOneChannelFactoryRequired() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CompositeGrpcChannelFactory(null))
|
||||
.withMessage("composite channel factory requires at least one channel factory");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CompositeGrpcChannelFactory(List.of()))
|
||||
.withMessage("composite channel factory requires at least one channel factory");
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsDependsOnSupportsOfComposedFactories() {
|
||||
assertThat(compositeChannelFactory.supports("foo")).isTrue();
|
||||
assertThat(compositeChannelFactory.supports("bar")).isTrue();
|
||||
assertThat(compositeChannelFactory.supports("zaa")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void firstComposedChannelFactorySupportsTarget() {
|
||||
assertThat(compositeChannelFactory.createChannel("foo")).isNotNull();
|
||||
assertThat(fooChannelFactory.getActualTarget()).isEqualTo("foo");
|
||||
assertThat(barChannelFactory.getActualTarget()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondComposedChannelFactorySupportsTarget() {
|
||||
assertThat(compositeChannelFactory.createChannel("bar")).isNotNull();
|
||||
assertThat(fooChannelFactory.getActualTarget()).isNull();
|
||||
assertThat(barChannelFactory.getActualTarget()).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void noComposedChannelFactorySupportsTarget() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> compositeChannelFactory.createChannel("zaa"))
|
||||
.withMessage("No grpc channel factory found that supports target : zaa");
|
||||
assertThat(fooChannelFactory.getActualTarget()).isNull();
|
||||
assertThat(barChannelFactory.getActualTarget()).isNull();
|
||||
}
|
||||
|
||||
static class TestChannelFactory implements GrpcChannelFactory {
|
||||
|
||||
private String expectedTarget;
|
||||
|
||||
private String actualTarget;
|
||||
|
||||
TestChannelFactory(String expectedTarget) {
|
||||
this.expectedTarget = expectedTarget;
|
||||
}
|
||||
|
||||
public boolean supports(String target) {
|
||||
return target.equals(this.expectedTarget);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedChannel createChannel(String target, ChannelBuilderOptions options) {
|
||||
this.actualTarget = target;
|
||||
return mock();
|
||||
}
|
||||
|
||||
String getActualTarget() {
|
||||
return this.actualTarget;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import org.mockito.ArgumentMatchers;
|
||||
import io.grpc.ClientInterceptor;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
import io.grpc.netty.NettyChannelBuilder;
|
||||
|
||||
/**
|
||||
@@ -159,6 +160,60 @@ class GrpcChannelFactoryTests {
|
||||
.isInstanceOf(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void inProcessChannelFactoryUsesInProcessChannelBuilder() {
|
||||
var channelName = "in-process:foo";
|
||||
var customizer1 = mock(GrpcChannelBuilderCustomizer.class);
|
||||
var channelFactory = new InProcessGrpcChannelFactory(List.of(), mock());
|
||||
channel = channelFactory.createChannel(channelName,
|
||||
ChannelBuilderOptions.defaults().withCustomizer(customizer1));
|
||||
assertThat(channel).isNotNull();
|
||||
verify(customizer1).customize(anyString(),
|
||||
ArgumentMatchers
|
||||
.assertArg((builder) -> assertThat(builder).isInstanceOf(InProcessChannelBuilder.class)
|
||||
.extracting("managedChannelImplBuilder.target")
|
||||
.isEqualTo("directaddress:///foo")));
|
||||
// NOTE: the impl target ending in foo proves the original target was stripped
|
||||
// of 'in-process:' prefix
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class SupportsApiTests {
|
||||
|
||||
@Test
|
||||
void defaultSupportsEverythingExceptInProcess() {
|
||||
var channelFactory = new DefaultGrpcChannelFactory(List.of(), mock());
|
||||
assertThat(channelFactory.supports("foo")).isTrue();
|
||||
assertThat(channelFactory.supports("static:127.0.0.1")).isTrue();
|
||||
assertThat(channelFactory.supports("in-process:foo")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nettySupportsEverythingExceptInProcess() {
|
||||
var channelFactory = new NettyGrpcChannelFactory(List.of(), mock());
|
||||
assertThat(channelFactory.supports("foo")).isTrue();
|
||||
assertThat(channelFactory.supports("static:127.0.0.1")).isTrue();
|
||||
assertThat(channelFactory.supports("in-process:foo")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shadedNettySupportsEverythingExceptInProcess() {
|
||||
var channelFactory = new ShadedNettyGrpcChannelFactory(List.of(), mock());
|
||||
assertThat(channelFactory.supports("foo")).isTrue();
|
||||
assertThat(channelFactory.supports("static:127.0.0.1")).isTrue();
|
||||
assertThat(channelFactory.supports("in-process:foo")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void inProcessSupportsOnlyInProcess() {
|
||||
var channelFactory = new InProcessGrpcChannelFactory(List.of(), mock());
|
||||
assertThat(channelFactory.supports("foo")).isFalse();
|
||||
assertThat(channelFactory.supports("static:127.0.0.1")).isFalse();
|
||||
assertThat(channelFactory.supports("in-process:foo")).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -93,6 +93,11 @@
|
||||
<artifactId>netty-transport-native-epoll</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-inprocess</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot</artifactId>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2025-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.autoconfigure.client;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
|
||||
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.grpc.client.CompositeGrpcChannelFactory;
|
||||
import org.springframework.grpc.client.GrpcChannelFactory;
|
||||
|
||||
import static org.springframework.grpc.autoconfigure.client.CompositeChannelFactoryAutoConfiguration.*;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for a
|
||||
* {@link CompositeGrpcChannelFactory}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@Conditional(MultipleNonPrimaryChannelFactoriesCondition.class)
|
||||
class CompositeChannelFactoryAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
CompositeGrpcChannelFactory compositeChannelFactory(ObjectProvider<GrpcChannelFactory> channelFactoriesProvider) {
|
||||
return new CompositeGrpcChannelFactory(channelFactoriesProvider.orderedStream().toList());
|
||||
}
|
||||
|
||||
static class MultipleNonPrimaryChannelFactoriesCondition extends NoneNestedConditions {
|
||||
|
||||
MultipleNonPrimaryChannelFactoriesCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnMissingBean(GrpcChannelFactory.class)
|
||||
static class NoChannelFactoryCondition {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnSingleCandidate(GrpcChannelFactory.class)
|
||||
static class SingleInjectableChannelFactoryCondition {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import java.util.List;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -27,10 +28,13 @@ import org.springframework.grpc.client.ChannelCredentialsProvider;
|
||||
import org.springframework.grpc.client.ClientInterceptorsConfigurer;
|
||||
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
|
||||
import org.springframework.grpc.client.GrpcChannelFactory;
|
||||
import org.springframework.grpc.client.InProcessGrpcChannelFactory;
|
||||
import org.springframework.grpc.client.NettyGrpcChannelFactory;
|
||||
import org.springframework.grpc.client.ShadedNettyGrpcChannelFactory;
|
||||
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
import io.grpc.netty.NettyChannelBuilder;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
/**
|
||||
* Configurations for {@link GrpcChannelFactory gRPC channel factories}.
|
||||
@@ -40,8 +44,11 @@ import io.grpc.netty.NettyChannelBuilder;
|
||||
class GrpcChannelFactoryConfigurations {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class)
|
||||
@ConditionalOnMissingBean(GrpcChannelFactory.class)
|
||||
@ConditionalOnClass(value = { io.grpc.netty.shaded.io.netty.channel.Channel.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class })
|
||||
@ConditionalOnMissingBean(value = GrpcChannelFactory.class, ignored = InProcessGrpcChannelFactory.class)
|
||||
@ConditionalOnProperty(prefix = "spring.grpc.client.inprocess.", name = "exclusive", havingValue = "false",
|
||||
matchIfMissing = true)
|
||||
@EnableConfigurationProperties(GrpcClientProperties.class)
|
||||
static class ShadedNettyChannelFactoryConfiguration {
|
||||
|
||||
@@ -60,8 +67,10 @@ class GrpcChannelFactoryConfigurations {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(NettyChannelBuilder.class)
|
||||
@ConditionalOnMissingBean(GrpcChannelFactory.class)
|
||||
@ConditionalOnClass(value = { Channel.class, NettyChannelBuilder.class })
|
||||
@ConditionalOnMissingBean(value = GrpcChannelFactory.class, ignored = InProcessGrpcChannelFactory.class)
|
||||
@ConditionalOnProperty(prefix = "spring.grpc.client.inprocess.", name = "exclusive", havingValue = "false",
|
||||
matchIfMissing = true)
|
||||
@EnableConfigurationProperties(GrpcClientProperties.class)
|
||||
static class NettyChannelFactoryConfiguration {
|
||||
|
||||
@@ -79,4 +88,21 @@ class GrpcChannelFactoryConfigurations {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(InProcessChannelBuilder.class)
|
||||
@ConditionalOnMissingBean(InProcessGrpcChannelFactory.class)
|
||||
@ConditionalOnProperty(prefix = "spring.grpc.client.inprocess", name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
static class InProcessChannelFactoryConfiguration {
|
||||
|
||||
@Bean
|
||||
InProcessGrpcChannelFactory inProcessGrpcChannelFactory(ChannelBuilderCustomizers channelBuilderCustomizers,
|
||||
ClientInterceptorsConfigurer interceptorsConfigurer) {
|
||||
List<GrpcChannelBuilderCustomizer<InProcessChannelBuilder>> inProcessBuilderCustomizers = List
|
||||
.of(channelBuilderCustomizers::customize);
|
||||
return new InProcessGrpcChannelFactory(inProcessBuilderCustomizers, interceptorsConfigurer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,12 +34,13 @@ import io.grpc.CompressorRegistry;
|
||||
import io.grpc.DecompressorRegistry;
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
|
||||
@AutoConfiguration
|
||||
@AutoConfiguration(before = CompositeChannelFactoryAutoConfiguration.class)
|
||||
@ConditionalOnGrpcClientEnabled
|
||||
@EnableConfigurationProperties(GrpcClientProperties.class)
|
||||
@Import({ GrpcCodecConfiguration.class, ClientInterceptorsConfiguration.class,
|
||||
GrpcChannelFactoryConfigurations.ShadedNettyChannelFactoryConfiguration.class,
|
||||
GrpcChannelFactoryConfigurations.NettyChannelFactoryConfiguration.class, ClientScanConfiguration.class })
|
||||
GrpcChannelFactoryConfigurations.NettyChannelFactoryConfiguration.class,
|
||||
GrpcChannelFactoryConfigurations.InProcessChannelFactoryConfiguration.class, ClientScanConfiguration.class })
|
||||
public class GrpcClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -64,13 +64,6 @@ public class GrpcServerAutoConfiguration {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@ConditionalOnBean(GrpcServerFactory.class)
|
||||
@ConditionalOnMissingBean
|
||||
@Bean
|
||||
GrpcServerLifecycle grpcServerLifecycle(GrpcServerFactory factory, ApplicationEventPublisher eventPublisher) {
|
||||
return new GrpcServerLifecycle(factory, this.properties.getShutdownGracePeriod(), eventPublisher);
|
||||
}
|
||||
|
||||
@ConditionalOnMissingBean
|
||||
@Bean
|
||||
ServerBuilderCustomizers serverBuilderCustomizers(ObjectProvider<ServerBuilderCustomizer<?>> customizers) {
|
||||
|
||||
@@ -67,7 +67,8 @@ public class GrpcServerFactoryAutoConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import({ GrpcServerFactoryConfigurations.ShadedNettyServerFactoryConfiguration.class,
|
||||
GrpcServerFactoryConfigurations.NettyServerFactoryConfiguration.class })
|
||||
GrpcServerFactoryConfigurations.NettyServerFactoryConfiguration.class,
|
||||
GrpcServerFactoryConfigurations.InProcessServerFactoryConfiguration.class })
|
||||
static class NettyServerFactoryConfiguration {
|
||||
|
||||
}
|
||||
@@ -103,6 +104,12 @@ public class GrpcServerFactoryAutoConfiguration {
|
||||
return servlet;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(GrpcServerFactoryConfigurations.InProcessServerFactoryConfiguration.class)
|
||||
static class InProcessConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class OnGrpcServletCondition extends AllNestedConditions {
|
||||
|
||||
@@ -21,19 +21,25 @@ import java.util.List;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
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.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.grpc.server.GrpcServerFactory;
|
||||
import org.springframework.grpc.server.InProcessGrpcServerFactory;
|
||||
import org.springframework.grpc.server.NettyGrpcServerFactory;
|
||||
import org.springframework.grpc.server.ServerBuilderCustomizer;
|
||||
import org.springframework.grpc.server.ShadedNettyGrpcServerFactory;
|
||||
import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle;
|
||||
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
|
||||
|
||||
import io.grpc.inprocess.InProcessServerBuilder;
|
||||
import io.grpc.netty.NettyServerBuilder;
|
||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
|
||||
@@ -46,7 +52,9 @@ class GrpcServerFactoryConfigurations {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)
|
||||
@ConditionalOnMissingBean(GrpcServerFactory.class)
|
||||
@ConditionalOnMissingBean(value = GrpcServerFactory.class, ignored = InProcessGrpcServerFactory.class)
|
||||
@ConditionalOnProperty(prefix = "spring.grpc.server.inprocess.", name = "exclusive", havingValue = "false",
|
||||
matchIfMissing = true)
|
||||
@EnableConfigurationProperties(GrpcServerProperties.class)
|
||||
static class ShadedNettyServerFactoryConfiguration {
|
||||
|
||||
@@ -71,11 +79,21 @@ class GrpcServerFactoryConfigurations {
|
||||
return factory;
|
||||
}
|
||||
|
||||
@ConditionalOnBean(ShadedNettyGrpcServerFactory.class)
|
||||
@ConditionalOnMissingBean(name = "shadedNettyGrpcServerLifecycle")
|
||||
@Bean
|
||||
GrpcServerLifecycle shadedNettyGrpcServerLifecycle(ShadedNettyGrpcServerFactory factory,
|
||||
GrpcServerProperties properties, ApplicationEventPublisher eventPublisher) {
|
||||
return new GrpcServerLifecycle(factory, properties.getShutdownGracePeriod(), eventPublisher);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(NettyServerBuilder.class)
|
||||
@ConditionalOnMissingBean(GrpcServerFactory.class)
|
||||
@ConditionalOnMissingBean(value = GrpcServerFactory.class, ignored = InProcessGrpcServerFactory.class)
|
||||
@ConditionalOnProperty(prefix = "spring.grpc.server.inprocess.", name = "exclusive", havingValue = "false",
|
||||
matchIfMissing = true)
|
||||
@EnableConfigurationProperties(GrpcServerProperties.class)
|
||||
static class NettyServerFactoryConfiguration {
|
||||
|
||||
@@ -100,6 +118,43 @@ class GrpcServerFactoryConfigurations {
|
||||
return factory;
|
||||
}
|
||||
|
||||
@ConditionalOnBean(NettyGrpcServerFactory.class)
|
||||
@ConditionalOnMissingBean(name = "nettyGrpcServerLifecycle")
|
||||
@Bean
|
||||
GrpcServerLifecycle nettyGrpcServerLifecycle(NettyGrpcServerFactory factory, GrpcServerProperties properties,
|
||||
ApplicationEventPublisher eventPublisher) {
|
||||
return new GrpcServerLifecycle(factory, properties.getShutdownGracePeriod(), eventPublisher);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(InProcessGrpcServerFactory.class)
|
||||
@ConditionalOnMissingBean(InProcessGrpcServerFactory.class)
|
||||
@ConditionalOnProperty(prefix = "spring.grpc.server.inprocess", name = "name")
|
||||
@EnableConfigurationProperties(GrpcServerProperties.class)
|
||||
static class InProcessServerFactoryConfiguration {
|
||||
|
||||
@Bean
|
||||
InProcessGrpcServerFactory inProcessGrpcServerFactory(GrpcServerProperties properties,
|
||||
GrpcServiceDiscoverer grpcServicesDiscoverer, ServerBuilderCustomizers serverBuilderCustomizers) {
|
||||
var mapper = new InProcessServerFactoryPropertyMapper(properties);
|
||||
List<ServerBuilderCustomizer<InProcessServerBuilder>> builderCustomizers = List
|
||||
.of(mapper::customizeServerBuilder, serverBuilderCustomizers::customize);
|
||||
InProcessGrpcServerFactory factory = new InProcessGrpcServerFactory(properties.getInprocess().getName(),
|
||||
builderCustomizers);
|
||||
grpcServicesDiscoverer.findServices().forEach(factory::addService);
|
||||
return factory;
|
||||
}
|
||||
|
||||
@ConditionalOnBean(InProcessGrpcServerFactory.class)
|
||||
@ConditionalOnMissingBean(name = "inProcessGrpcServerLifecycle")
|
||||
@Bean
|
||||
GrpcServerLifecycle inProcessGrpcServerLifecycle(InProcessGrpcServerFactory factory,
|
||||
GrpcServerProperties properties, ApplicationEventPublisher eventPublisher) {
|
||||
return new GrpcServerLifecycle(factory, properties.getShutdownGracePeriod(), eventPublisher);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -72,6 +72,8 @@ public class GrpcServerProperties {
|
||||
|
||||
private final KeepAlive keepAlive = new KeepAlive();
|
||||
|
||||
private final Inprocess inprocess = new Inprocess();
|
||||
|
||||
/**
|
||||
* The address to bind to. could be a host:port combination or a pseudo URL like
|
||||
* static://host:port. Can not be set if host or port are set independently.
|
||||
@@ -143,6 +145,10 @@ public class GrpcServerProperties {
|
||||
return this.keepAlive;
|
||||
}
|
||||
|
||||
public Inprocess getInprocess() {
|
||||
return this.inprocess;
|
||||
}
|
||||
|
||||
public static class Health {
|
||||
|
||||
/**
|
||||
@@ -416,4 +422,35 @@ public class GrpcServerProperties {
|
||||
|
||||
}
|
||||
|
||||
public static class Inprocess {
|
||||
|
||||
/**
|
||||
* The name of the in-process server or null to not start the in-process server.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Whether the inprocess server factory should be the only server factory
|
||||
* available. When the value is true no other server factory will be configured.
|
||||
*/
|
||||
private Boolean exclusive;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Boolean getExclusive() {
|
||||
return exclusive;
|
||||
}
|
||||
|
||||
public void setExclusive(Boolean exclusive) {
|
||||
this.exclusive = exclusive;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2025-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.autoconfigure.server;
|
||||
|
||||
import io.grpc.inprocess.InProcessServerBuilder;
|
||||
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
|
||||
/**
|
||||
* Helper class used to map {@link GrpcServerProperties} to
|
||||
* {@link InProcessServerBuilder}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
*/
|
||||
class InProcessServerFactoryPropertyMapper extends DefaultServerFactoryPropertyMapper<InProcessServerBuilder> {
|
||||
|
||||
InProcessServerFactoryPropertyMapper(GrpcServerProperties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
void customizeServerBuilder(InProcessServerBuilder serverBuilder) {
|
||||
PropertyMapper mapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||
customizeInboundLimits(serverBuilder, mapper);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,6 +47,18 @@
|
||||
"description": "Whether to enable client autoconfiguration.",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.grpc.client.inprocess.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether to configure the in-process channel factory.",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.grpc.client.inprocess.exclusive",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether the inprocess channel factory should be the only channel factory available. When the value is true, no other channel factory will be configured.",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.grpc.client.observations.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
org.springframework.grpc.autoconfigure.client.CompositeChannelFactoryAutoConfiguration
|
||||
org.springframework.grpc.autoconfigure.client.GrpcClientAutoConfiguration
|
||||
org.springframework.grpc.autoconfigure.client.GrpcClientObservationAutoConfiguration
|
||||
org.springframework.grpc.autoconfigure.server.GrpcServerFactoryAutoConfiguration
|
||||
@@ -8,4 +9,4 @@ org.springframework.grpc.autoconfigure.server.GrpcServerReflectionAutoConfigurat
|
||||
org.springframework.grpc.autoconfigure.server.exception.GrpcExceptionHandlerAutoConfiguration
|
||||
org.springframework.grpc.autoconfigure.server.security.GrpcSecurityAutoConfiguration
|
||||
org.springframework.grpc.autoconfigure.server.security.OAuth2ClientAutoConfiguration
|
||||
org.springframework.grpc.autoconfigure.server.security.OAuth2ResourceServerAutoConfiguration
|
||||
org.springframework.grpc.autoconfigure.server.security.OAuth2ResourceServerAutoConfiguration
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2025-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.autoconfigure.client;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.grpc.client.CompositeGrpcChannelFactory;
|
||||
import org.springframework.grpc.client.GrpcChannelFactory;
|
||||
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
import io.grpc.netty.NettyChannelBuilder;
|
||||
|
||||
/**
|
||||
* Tests for {@link CompositeChannelFactoryAutoConfiguration}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
class CompositeChannelFactoryAutoConfigurationTests {
|
||||
|
||||
private ApplicationContextRunner contextRunnerWithoutChannelFactories() {
|
||||
return new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(GrpcClientAutoConfiguration.class, SslAutoConfiguration.class,
|
||||
CompositeChannelFactoryAutoConfiguration.class))
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class,
|
||||
NettyChannelBuilder.class, InProcessChannelBuilder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoChannelFactoriesDoesNotAutoconfigureComposite() {
|
||||
this.contextRunnerWithoutChannelFactories()
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GrpcChannelFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenSingleChannelFactoryDoesNotAutoconfigureComposite() {
|
||||
GrpcChannelFactory channelFactory1 = mock();
|
||||
this.contextRunnerWithoutChannelFactories()
|
||||
.withBean("channelFactory1", GrpcChannelFactory.class, () -> channelFactory1)
|
||||
.run((context) -> assertThat(context).hasSingleBean(GrpcChannelFactory.class)
|
||||
.getBean(GrpcChannelFactory.class)
|
||||
.isNotInstanceOf(CompositeGrpcChannelFactory.class)
|
||||
.isSameAs(channelFactory1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenMultipleChannelFactoriesWithPrimaryDoesNotAutoconfigureComposite() {
|
||||
GrpcChannelFactory channelFactory1 = mock();
|
||||
GrpcChannelFactory channelFactory2 = mock();
|
||||
this.contextRunnerWithoutChannelFactories()
|
||||
.withBean("channelFactory1", GrpcChannelFactory.class, () -> channelFactory1)
|
||||
.withBean("channelFactory2", GrpcChannelFactory.class, () -> channelFactory2, (bd) -> bd.setPrimary(true))
|
||||
.run((context) -> {
|
||||
assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsOnlyKeys("channelFactory1", "channelFactory2");
|
||||
assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isNotInstanceOf(CompositeGrpcChannelFactory.class)
|
||||
.isSameAs(channelFactory2);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenMultipleChannelFactoriesDoesAutoconfigureComposite() {
|
||||
GrpcChannelFactory channelFactory1 = mock();
|
||||
GrpcChannelFactory channelFactory2 = mock();
|
||||
this.contextRunnerWithoutChannelFactories()
|
||||
.withBean("channelFactory1", GrpcChannelFactory.class, () -> channelFactory1)
|
||||
.withBean("channelFactory2", GrpcChannelFactory.class, () -> channelFactory2)
|
||||
.run((context) -> {
|
||||
assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsOnlyKeys("channelFactory1", "channelFactory2", "compositeChannelFactory");
|
||||
assertThat(context).getBean(GrpcChannelFactory.class).isInstanceOf(CompositeGrpcChannelFactory.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void compositeAutoconfiguredAsExpected() {
|
||||
this.contextRunnerWithoutChannelFactories()
|
||||
.withUserConfiguration(MultipleFactoriesTestConfig.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(CompositeGrpcChannelFactory.class)
|
||||
.extracting("channelFactories")
|
||||
.asInstanceOf(InstanceOfAssertFactories.list(GrpcChannelFactory.class))
|
||||
.containsExactly(MultipleFactoriesTestConfig.CHANNEL_FACTORY_BAR,
|
||||
MultipleFactoriesTestConfig.CHANNEL_FACTORY_ZAA,
|
||||
MultipleFactoriesTestConfig.CHANNEL_FACTORY_FOO);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class MultipleFactoriesTestConfig {
|
||||
|
||||
static GrpcChannelFactory CHANNEL_FACTORY_FOO = mock();
|
||||
static GrpcChannelFactory CHANNEL_FACTORY_BAR = mock();
|
||||
static GrpcChannelFactory CHANNEL_FACTORY_ZAA = mock();
|
||||
|
||||
@Bean
|
||||
@Order(3)
|
||||
GrpcChannelFactory channelFactoryFoo() {
|
||||
return CHANNEL_FACTORY_FOO;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
GrpcChannelFactory channelFactoryBar() {
|
||||
return CHANNEL_FACTORY_BAR;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(2)
|
||||
GrpcChannelFactory channelFactoryZaa() {
|
||||
return CHANNEL_FACTORY_ZAA;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,13 +43,16 @@ import org.springframework.core.annotation.Order;
|
||||
import org.springframework.grpc.client.ChannelCredentialsProvider;
|
||||
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
|
||||
import org.springframework.grpc.client.GrpcChannelFactory;
|
||||
import org.springframework.grpc.client.InProcessGrpcChannelFactory;
|
||||
import org.springframework.grpc.client.NettyGrpcChannelFactory;
|
||||
import org.springframework.grpc.client.ShadedNettyGrpcChannelFactory;
|
||||
import org.springframework.grpc.server.GrpcServerFactory;
|
||||
|
||||
import io.grpc.Codec;
|
||||
import io.grpc.CompressorRegistry;
|
||||
import io.grpc.DecompressorRegistry;
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
import io.grpc.kotlin.AbstractCoroutineStub;
|
||||
import io.grpc.netty.NettyChannelBuilder;
|
||||
import io.grpc.stub.AbstractStub;
|
||||
@@ -67,6 +70,10 @@ class GrpcClientAutoConfigurationTests {
|
||||
.withConfiguration(AutoConfigurations.of(GrpcClientAutoConfiguration.class, SslAutoConfiguration.class));
|
||||
}
|
||||
|
||||
private ApplicationContextRunner contextRunnerWithoutInProcessChannelFactory() {
|
||||
return this.contextRunner().withPropertyValues("spring.grpc.client.inprocess.enabled=false");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenGrpcStubNotOnClasspathThenAutoConfigurationIsSkipped() {
|
||||
this.contextRunner()
|
||||
@@ -223,72 +230,169 @@ class GrpcClientAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasUserDefinedChannelFactoryDoesNotAutoConfigureBean() {
|
||||
GrpcChannelFactory customChannelFactory = mock(GrpcChannelFactory.class);
|
||||
void whenInProcessEnabledPropNotSetDoesAutoconfigureInProcess() {
|
||||
this.contextRunner()
|
||||
.run((context) -> assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsKey("inProcessGrpcChannelFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenInProcessEnabledPropSetToTrueDoesAutoconfigureInProcess() {
|
||||
this.contextRunner()
|
||||
.withPropertyValues("spring.grpc.client.inprocess.enabled=true")
|
||||
.run((context) -> assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsKey("inProcessGrpcChannelFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenInProcessEnabledPropSetToFalseDoesNotAutoconfigureInProcess() {
|
||||
this.contextRunner()
|
||||
.withPropertyValues("spring.grpc.client.inprocess.enabled=false")
|
||||
.run((context) -> assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.doesNotContainKey("inProcessGrpcChannelFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenInProcessIsNotOnClasspathDoesNotAutoconfigureInProcess() {
|
||||
this.contextRunner()
|
||||
.withClassLoader(new FilteredClassLoader(InProcessChannelBuilder.class))
|
||||
.run((context) -> assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.doesNotContainKey("inProcessGrpcChannelFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasUserDefinedInProcessChannelFactoryDoesNotAutoConfigureBean() {
|
||||
InProcessGrpcChannelFactory customChannelFactory = mock();
|
||||
this.contextRunner()
|
||||
.withClassLoader(new FilteredClassLoader(NettyChannelBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class))
|
||||
.withBean("customChannelFactory", InProcessGrpcChannelFactory.class, () -> customChannelFactory)
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class).isSameAs(customChannelFactory));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasUserDefinedChannelFactoryDoesNotAutoConfigureNettyOrShadedNetty() {
|
||||
GrpcChannelFactory customChannelFactory = mock();
|
||||
this.contextRunnerWithoutInProcessChannelFactory()
|
||||
.withBean("customChannelFactory", GrpcChannelFactory.class, () -> customChannelFactory)
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class).isSameAs(customChannelFactory));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShadedAndNonShadedNettyOnClasspathShadedNettyFactoryIsAutoConfigured() {
|
||||
void userDefinedChannelFactoryWithInProcessChannelFactory() {
|
||||
GrpcChannelFactory customChannelFactory = mock();
|
||||
this.contextRunner()
|
||||
.withBean("customChannelFactory", GrpcChannelFactory.class, () -> customChannelFactory)
|
||||
.run((context) -> assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsOnlyKeys("customChannelFactory", "inProcessGrpcChannelFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShadedAndNonShadedNettyOnClasspathShadedNettyFactoryIsAutoConfigured() {
|
||||
this.contextRunnerWithoutInProcessChannelFactory()
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(ShadedNettyGrpcChannelFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenOnlyNonShadedNettyOnClasspathNonShadedNettyFactoryIsAutoConfigured() {
|
||||
void shadedNettyWithInProcessChannelFactory() {
|
||||
this.contextRunner()
|
||||
.run((context) -> assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsOnlyKeys("shadedNettyGrpcChannelFactory", "inProcessGrpcChannelFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenOnlyNonShadedNettyOnClasspathNonShadedNettyFactoryIsAutoConfigured() {
|
||||
this.contextRunnerWithoutInProcessChannelFactory()
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class))
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(NettyGrpcChannelFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shadedNettyChannelFactoryAutoConfiguredAsExpected() {
|
||||
channelFactoryAutoConfiguredAsExpected(this.contextRunner(), ShadedNettyGrpcChannelFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nettyChannelFactoryAutoConfiguredAsExpected() {
|
||||
channelFactoryAutoConfiguredAsExpected(this.contextRunner()
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class)),
|
||||
NettyGrpcChannelFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noChannelFactoryAutoConfiguredAsExpected() {
|
||||
void nonShadedNettyWithInProcessChannelFactory() {
|
||||
this.contextRunner()
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class))
|
||||
.run((context) -> assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsOnlyKeys("nettyGrpcChannelFactory", "inProcessGrpcChannelFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShadedNettyAndNettyNotOnClasspathNoChannelFactoryIsAutoConfigured() {
|
||||
this.contextRunnerWithoutInProcessChannelFactory()
|
||||
.withClassLoader(new FilteredClassLoader(NettyChannelBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GrpcChannelFactory.class));
|
||||
}
|
||||
|
||||
private void channelFactoryAutoConfiguredAsExpected(ApplicationContextRunner contextRunner,
|
||||
Class<?> expectedChannelFactoryType) {
|
||||
contextRunner.withPropertyValues("spring.grpc.server.port=0")
|
||||
@Test
|
||||
void noChannelFactoryWithInProcessChannelFactory() {
|
||||
this.contextRunner()
|
||||
.withClassLoader(new FilteredClassLoader(NettyChannelBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class))
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(expectedChannelFactoryType)
|
||||
.isInstanceOf(InProcessGrpcChannelFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shadedNettyChannelFactoryAutoConfiguredAsExpected() {
|
||||
this.contextRunnerWithoutInProcessChannelFactory()
|
||||
.withPropertyValues("spring.grpc.server.port=0")
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(ShadedNettyGrpcChannelFactory.class)
|
||||
.hasFieldOrPropertyWithValue("credentials", context.getBean(NamedChannelCredentialsProvider.class))
|
||||
.extracting("targets")
|
||||
.isInstanceOf(GrpcClientProperties.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nettyChannelFactoryAutoConfiguredAsExpected() {
|
||||
this.contextRunnerWithoutInProcessChannelFactory()
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class))
|
||||
.withPropertyValues("spring.grpc.server.port=0")
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(NettyGrpcChannelFactory.class)
|
||||
.hasFieldOrPropertyWithValue("credentials", context.getBean(NamedChannelCredentialsProvider.class))
|
||||
.extracting("targets")
|
||||
.isInstanceOf(GrpcClientProperties.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void inProcessChannelFactoryAutoConfiguredAsExpected() {
|
||||
this.contextRunner()
|
||||
.withClassLoader(new FilteredClassLoader(NettyChannelBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class))
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(InProcessGrpcChannelFactory.class)
|
||||
.extracting("credentials")
|
||||
.isSameAs(ChannelCredentialsProvider.INSECURE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shadedNettyChannelFactoryAutoConfiguredWithCustomizers() {
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder builder = mock();
|
||||
channelFactoryAutoConfiguredWithCustomizers(this.contextRunner(), builder, ShadedNettyGrpcChannelFactory.class);
|
||||
channelFactoryAutoConfiguredWithCustomizers(this.contextRunnerWithoutInProcessChannelFactory(), builder,
|
||||
ShadedNettyGrpcChannelFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nettyChannelFactoryAutoConfiguredWithCustomizers() {
|
||||
NettyChannelBuilder builder = mock();
|
||||
channelFactoryAutoConfiguredWithCustomizers(this.contextRunner()
|
||||
channelFactoryAutoConfiguredWithCustomizers(this.contextRunnerWithoutInProcessChannelFactory()
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class)),
|
||||
builder, NettyGrpcChannelFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void inProcessChannelFactoryAutoConfiguredWithCustomizers() {
|
||||
InProcessChannelBuilder builder = mock();
|
||||
channelFactoryAutoConfiguredWithCustomizers(
|
||||
this.contextRunner()
|
||||
.withClassLoader(new FilteredClassLoader(NettyChannelBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class)),
|
||||
builder, InProcessGrpcChannelFactory.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends ManagedChannelBuilder<T>> void channelFactoryAutoConfiguredWithCustomizers(
|
||||
ApplicationContextRunner contextRunner, ManagedChannelBuilder<T> mockChannelBuilder,
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.grpc.server.GrpcServerFactory;
|
||||
import org.springframework.grpc.server.InProcessGrpcServerFactory;
|
||||
import org.springframework.grpc.server.NettyGrpcServerFactory;
|
||||
import org.springframework.grpc.server.ServerBuilderCustomizer;
|
||||
import org.springframework.grpc.server.ShadedNettyGrpcServerFactory;
|
||||
@@ -86,7 +87,9 @@ class GrpcServerAutoConfigurationTests {
|
||||
return runner
|
||||
.withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class,
|
||||
GrpcServerFactoryAutoConfiguration.class, SslAutoConfiguration.class))
|
||||
.withBean("noopServerLifecycle", GrpcServerLifecycle.class, Mockito::mock)
|
||||
.withBean("shadedNettyGrpcServerLifecycle", GrpcServerLifecycle.class, Mockito::mock)
|
||||
.withBean("nettyGrpcServerLifecycle", GrpcServerLifecycle.class, Mockito::mock)
|
||||
.withBean("inProcessGrpcServerLifecycle", GrpcServerLifecycle.class, Mockito::mock)
|
||||
.withBean(BindableService.class, () -> service);
|
||||
}
|
||||
|
||||
@@ -130,21 +133,6 @@ class GrpcServerAutoConfigurationTests {
|
||||
.run((context) -> assertThat(context).hasSingleBean(GrpcServerAutoConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasUserDefinedServerLifecycleDoesNotAutoConfigureBean() {
|
||||
GrpcServerLifecycle customServerLifecycle = mock(GrpcServerLifecycle.class);
|
||||
this.contextRunnerWithLifecyle()
|
||||
.withBean("customServerLifecycle", GrpcServerLifecycle.class, () -> customServerLifecycle)
|
||||
.run((context) -> assertThat(context).getBean(GrpcServerLifecycle.class).isSameAs(customServerLifecycle));
|
||||
}
|
||||
|
||||
@Test
|
||||
void serverLifecycleAutoConfiguredAsExpected() {
|
||||
this.contextRunnerWithLifecyle()
|
||||
.run((context) -> assertThat(context).getBean(GrpcServerLifecycle.class)
|
||||
.hasFieldOrPropertyWithValue("factory", context.getBean(GrpcServerFactory.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasUserDefinedGrpcServiceDiscovererDoesNotAutoConfigureBean() {
|
||||
GrpcServiceDiscoverer customGrpcServiceDiscoverer = mock(GrpcServiceDiscoverer.class);
|
||||
@@ -205,6 +193,16 @@ class GrpcServerAutoConfigurationTests {
|
||||
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class).isSameAs(customServerFactory));
|
||||
}
|
||||
|
||||
@Test
|
||||
void userDefinedServerFactoryWithInProcessServerFactory() {
|
||||
GrpcServerFactory customServerFactory = mock(GrpcServerFactory.class);
|
||||
this.contextRunner()
|
||||
.withPropertyValues("spring.grpc.server.inprocess.name=foo")
|
||||
.withBean("customServerFactory", GrpcServerFactory.class, () -> customServerFactory)
|
||||
.run((context) -> assertThat(context).getBeans(GrpcServerFactory.class)
|
||||
.containsOnlyKeys("customServerFactory", "inProcessGrpcServerFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShadedAndNonShadedNettyOnClasspathShadedNettyFactoryIsAutoConfigured() {
|
||||
this.contextRunner()
|
||||
@@ -212,6 +210,14 @@ class GrpcServerAutoConfigurationTests {
|
||||
.isInstanceOf(ShadedNettyGrpcServerFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shadedNettyFactoryWithInProcessServerFactory() {
|
||||
this.contextRunner()
|
||||
.withPropertyValues("spring.grpc.server.inprocess.name=foo")
|
||||
.run((context) -> assertThat(context).getBeans(GrpcServerFactory.class)
|
||||
.containsOnlyKeys("shadedNettyGrpcServerFactory", "inProcessGrpcServerFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenOnlyNonShadedNettyOnClasspathNonShadedNettyFactoryIsAutoConfigured() {
|
||||
this.contextRunner()
|
||||
@@ -221,42 +227,120 @@ class GrpcServerAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shadedNettyServerFactoryAutoConfiguredAsExpected() {
|
||||
serverFactoryAutoConfiguredAsExpected(this.contextRunner(), ShadedNettyGrpcServerFactory.class);
|
||||
void nonShadedNettyFactoryWithInProcessServerFactory() {
|
||||
this.contextRunner()
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.withPropertyValues("spring.grpc.server.inprocess.name=foo")
|
||||
.run((context) -> assertThat(context).getBeans(GrpcServerFactory.class)
|
||||
.containsOnlyKeys("nettyGrpcServerFactory", "inProcessGrpcServerFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void serverFactoryAutoConfiguredWhenServletDisabled() {
|
||||
serverFactoryAutoConfiguredAsExpected(this.contextRunner(new WebApplicationContextRunner())
|
||||
.withPropertyValues("spring.grpc.server.servlet.enabled=false"), GrpcServerFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nettyServerFactoryAutoConfiguredAsExpected() {
|
||||
serverFactoryAutoConfiguredAsExpected(this.contextRunner()
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)),
|
||||
NettyGrpcServerFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noServerFactoryAutoConfiguredAsExpected() {
|
||||
void whenShadedNettyAndNettyNotOnClasspathNoServerFactoryIsAutoConfigured() {
|
||||
this.contextRunner()
|
||||
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerFactory.class));
|
||||
}
|
||||
|
||||
private void serverFactoryAutoConfiguredAsExpected(AbstractApplicationContextRunner<?, ?, ?> contextRunner,
|
||||
Class<?> expectedServerFactoryType) {
|
||||
contextRunner.withPropertyValues("spring.grpc.server.host=myhost", "spring.grpc.server.port=6160")
|
||||
@Test
|
||||
void noServerFactoryWithInProcessServerFactory() {
|
||||
this.contextRunner()
|
||||
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.withPropertyValues("spring.grpc.server.inprocess.name=foo")
|
||||
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class)
|
||||
.isInstanceOf(InProcessGrpcServerFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shadedNettyServerFactoryAutoConfiguredWithCustomLifecycle() {
|
||||
GrpcServerLifecycle customServerLifecycle = mock(GrpcServerLifecycle.class);
|
||||
this.contextRunnerWithLifecyle()
|
||||
.withBean("shadedNettyGrpcServerLifecycle", GrpcServerLifecycle.class, () -> customServerLifecycle)
|
||||
.run((context) -> {
|
||||
assertThat(context).getBean(GrpcServerFactory.class).isInstanceOf(ShadedNettyGrpcServerFactory.class);
|
||||
assertThat(context).getBean("shadedNettyGrpcServerLifecycle", GrpcServerLifecycle.class)
|
||||
.isSameAs(customServerLifecycle);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void nettyServerFactoryAutoConfiguredWithCustomLifecycle() {
|
||||
GrpcServerLifecycle customServerLifecycle = mock(GrpcServerLifecycle.class);
|
||||
this.contextRunnerWithLifecyle()
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.withBean("nettyGrpcServerLifecycle", GrpcServerLifecycle.class, () -> customServerLifecycle)
|
||||
.run((context) -> {
|
||||
assertThat(context).getBean(GrpcServerFactory.class).isInstanceOf(NettyGrpcServerFactory.class);
|
||||
assertThat(context).getBean("nettyGrpcServerLifecycle", GrpcServerLifecycle.class)
|
||||
.isSameAs(customServerLifecycle);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void inProcessServerFactoryAutoConfiguredWithCustomLifecycle() {
|
||||
GrpcServerLifecycle customServerLifecycle = mock(GrpcServerLifecycle.class);
|
||||
this.contextRunnerWithLifecyle()
|
||||
.withPropertyValues("spring.grpc.server.inprocess.name=foo")
|
||||
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.withBean("inProcessGrpcServerLifecycle", GrpcServerLifecycle.class, () -> customServerLifecycle)
|
||||
.run((context) -> {
|
||||
assertThat(context).getBean(GrpcServerFactory.class).isInstanceOf(InProcessGrpcServerFactory.class);
|
||||
assertThat(context).getBean("inProcessGrpcServerLifecycle", GrpcServerLifecycle.class)
|
||||
.isSameAs(customServerLifecycle);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shadedNettyServerFactoryAutoConfiguredAsExpected() {
|
||||
serverFactoryAutoConfiguredAsExpected(
|
||||
this.contextRunner()
|
||||
.withPropertyValues("spring.grpc.server.host=myhost", "spring.grpc.server.port=6160"),
|
||||
ShadedNettyGrpcServerFactory.class, "myhost:6160", "shadedNettyGrpcServerLifecycle");
|
||||
}
|
||||
|
||||
@Test
|
||||
void serverFactoryAutoConfiguredInWebAppWhenServletDisabled() {
|
||||
serverFactoryAutoConfiguredAsExpected(
|
||||
this.contextRunner(new WebApplicationContextRunner())
|
||||
.withPropertyValues("spring.grpc.server.host=myhost", "spring.grpc.server.port=6160")
|
||||
.withPropertyValues("spring.grpc.server.servlet.enabled=false"),
|
||||
GrpcServerFactory.class, "myhost:6160", "shadedNettyGrpcServerLifecycle");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nettyServerFactoryAutoConfiguredAsExpected() {
|
||||
serverFactoryAutoConfiguredAsExpected(this.contextRunner()
|
||||
.withPropertyValues("spring.grpc.server.host=myhost", "spring.grpc.server.port=6160")
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)),
|
||||
NettyGrpcServerFactory.class, "myhost:6160", "nettyGrpcServerLifecycle");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inProcessServerFactoryAutoConfiguredAsExpected() {
|
||||
serverFactoryAutoConfiguredAsExpected(
|
||||
this.contextRunner()
|
||||
.withPropertyValues("spring.grpc.server.inprocess.name=foo")
|
||||
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)),
|
||||
InProcessGrpcServerFactory.class, "foo", "inProcessGrpcServerLifecycle");
|
||||
}
|
||||
|
||||
private void serverFactoryAutoConfiguredAsExpected(AbstractApplicationContextRunner<?, ?, ?> contextRunner,
|
||||
Class<?> expectedServerFactoryType, String expectedAddress, String expectedLifecycleBeanName) {
|
||||
contextRunner.run((context) -> {
|
||||
assertThat(context).getBean(GrpcServerFactory.class)
|
||||
.isInstanceOf(expectedServerFactoryType)
|
||||
.hasFieldOrPropertyWithValue("address", "myhost:6160")
|
||||
.hasFieldOrPropertyWithValue("address", expectedAddress)
|
||||
.extracting("serviceList", InstanceOfAssertFactories.list(ServerServiceDefinition.class))
|
||||
.singleElement()
|
||||
.extracting(ServerServiceDefinition::getServiceDescriptor)
|
||||
.extracting(ServiceDescriptor::getName)
|
||||
.isEqualTo("my-service"));
|
||||
.isEqualTo("my-service");
|
||||
assertThat(context).getBean(expectedLifecycleBeanName, GrpcServerLifecycle.class).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -314,10 +398,11 @@ class GrpcServerAutoConfigurationTests {
|
||||
.withPropertyValues("spring.grpc.server.ssl.bundle=ssltest",
|
||||
"spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks",
|
||||
"spring.ssl.bundle.jks.ssltest.keystore.password=secret",
|
||||
"spring.ssl.bundle.jks.ssltest.key.password=password")
|
||||
"spring.ssl.bundle.jks.ssltest.key.password=password", "spring.grpc.server.host=myhost",
|
||||
"spring.grpc.server.port=6160")
|
||||
.withClassLoader(
|
||||
new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)),
|
||||
NettyGrpcServerFactory.class);
|
||||
NettyGrpcServerFactory.class, "myhost:6160", "nettyGrpcServerLifecycle");
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* 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.List;
|
||||
|
||||
import org.springframework.grpc.client.ClientInterceptorsConfigurer;
|
||||
import org.springframework.grpc.client.DefaultGrpcChannelFactory;
|
||||
|
||||
import io.grpc.ChannelCredentials;
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
|
||||
public class InProcessGrpcChannelFactory extends DefaultGrpcChannelFactory {
|
||||
|
||||
public InProcessGrpcChannelFactory(ClientInterceptorsConfigurer interceptorsConfigurer) {
|
||||
super(List.of(), interceptorsConfigurer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ManagedChannelBuilder<?> newChannelBuilder(String path, ChannelCredentials creds) {
|
||||
return InProcessChannelBuilder.forName(path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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.List;
|
||||
|
||||
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.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.grpc.autoconfigure.client.ClientInterceptorsConfiguration;
|
||||
import org.springframework.grpc.autoconfigure.client.GrpcClientAutoConfiguration;
|
||||
import org.springframework.grpc.autoconfigure.server.GrpcServerFactoryAutoConfiguration;
|
||||
import org.springframework.grpc.client.ClientInterceptorsConfigurer;
|
||||
import org.springframework.grpc.server.ServerBuilderCustomizer;
|
||||
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
|
||||
|
||||
import io.grpc.BindableService;
|
||||
import io.grpc.inprocess.InProcessServerBuilder;
|
||||
|
||||
@AutoConfiguration(before = { GrpcServerFactoryAutoConfiguration.class, GrpcClientAutoConfiguration.class })
|
||||
@ConditionalOnProperty(prefix = "spring.grpc.inprocess", name = "enabled", havingValue = "true")
|
||||
@ConditionalOnClass(BindableService.class)
|
||||
@Import(ClientInterceptorsConfiguration.class)
|
||||
public class InProcessGrpcServerFactoryAutoConfiguration {
|
||||
|
||||
private final String address = InProcessServerBuilder.generateName();
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(BindableService.class)
|
||||
InProcessGrpcServerFactory grpcServerFactory(GrpcServiceDiscoverer grpcServicesDiscoverer,
|
||||
List<ServerBuilderCustomizer<InProcessServerBuilder>> customizers) {
|
||||
InProcessGrpcServerFactory factory = new InProcessGrpcServerFactory(address, customizers);
|
||||
grpcServicesDiscoverer.findServices().forEach(factory::addService);
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
InProcessGrpcChannelFactory grpcChannelFactory(ClientInterceptorsConfigurer interceptorsConfigurer) {
|
||||
InProcessGrpcChannelFactory factory = new InProcessGrpcChannelFactory(interceptorsConfigurer);
|
||||
factory.setVirtualTargets(path -> address);
|
||||
return factory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
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.ConditionalOnProperty;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.grpc.autoconfigure.client.ClientInterceptorsConfiguration;
|
||||
import org.springframework.grpc.autoconfigure.client.GrpcClientAutoConfiguration;
|
||||
import org.springframework.grpc.autoconfigure.server.GrpcServerFactoryAutoConfiguration;
|
||||
import org.springframework.grpc.client.ClientInterceptorsConfigurer;
|
||||
import org.springframework.grpc.client.InProcessGrpcChannelFactory;
|
||||
import org.springframework.grpc.server.InProcessGrpcServerFactory;
|
||||
import org.springframework.grpc.server.ServerBuilderCustomizer;
|
||||
import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle;
|
||||
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
|
||||
|
||||
import io.grpc.BindableService;
|
||||
import io.grpc.ChannelCredentials;
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
import io.grpc.inprocess.InProcessServerBuilder;
|
||||
|
||||
@AutoConfiguration(before = { GrpcServerFactoryAutoConfiguration.class, GrpcClientAutoConfiguration.class })
|
||||
@ConditionalOnProperty(prefix = "spring.grpc.test.inprocess", name = "enabled", havingValue = "true")
|
||||
@ConditionalOnClass(BindableService.class)
|
||||
@Import(ClientInterceptorsConfiguration.class)
|
||||
public class InProcessTestAutoConfiguration {
|
||||
|
||||
private final String address = InProcessServerBuilder.generateName();
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(BindableService.class)
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
TestInProcessGrpcServerFactory testInProcessGrpcServerFactory(GrpcServiceDiscoverer grpcServicesDiscoverer,
|
||||
List<ServerBuilderCustomizer<InProcessServerBuilder>> customizers) {
|
||||
var factory = new TestInProcessGrpcServerFactory(address, customizers);
|
||||
grpcServicesDiscoverer.findServices().forEach(factory::addService);
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
TestInProcessGrpcChannelFactory testInProcessGrpcChannelFactory(
|
||||
ClientInterceptorsConfigurer interceptorsConfigurer) {
|
||||
return new TestInProcessGrpcChannelFactory(address, interceptorsConfigurer);
|
||||
}
|
||||
|
||||
@Bean(name = "inProcessGrpcServerLifecycle")
|
||||
@ConditionalOnBean(InProcessGrpcServerFactory.class)
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
GrpcServerLifecycle inProcessGrpcServerLifecycle(InProcessGrpcServerFactory factory,
|
||||
ApplicationEventPublisher eventPublisher) {
|
||||
return new GrpcServerLifecycle(factory, Duration.ofSeconds(30), eventPublisher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialization of {@link InProcessGrpcServerFactory}.
|
||||
*/
|
||||
public static class TestInProcessGrpcServerFactory extends InProcessGrpcServerFactory {
|
||||
|
||||
public TestInProcessGrpcServerFactory(String address,
|
||||
List<ServerBuilderCustomizer<InProcessServerBuilder>> serverBuilderCustomizers) {
|
||||
super(address, serverBuilderCustomizers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialization of {@link InProcessGrpcChannelFactory} that allows the channel
|
||||
* factory to support all targets, not just those that start with 'in-process:'.
|
||||
*/
|
||||
public static class TestInProcessGrpcChannelFactory extends InProcessGrpcChannelFactory {
|
||||
|
||||
TestInProcessGrpcChannelFactory(String address, ClientInterceptorsConfigurer interceptorsConfigurer) {
|
||||
super(List.of(), interceptorsConfigurer);
|
||||
setVirtualTargets((path) -> address);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @param target the target string as described in method javadocs
|
||||
* @return {@code true} so that the test factory can handle all targets not just
|
||||
* those prefixed with 'in-process:'
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(String target) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* Overrides the parent behavior so that the channel factory can handle all
|
||||
* targets, not just those that prefixed with 'in-process:'.
|
||||
* @param target the target of the channel
|
||||
* @param creds the credentials for the channel which are ignored in this case
|
||||
* @return a new inprocess channel builder instance
|
||||
*/
|
||||
@Override
|
||||
protected InProcessChannelBuilder newChannelBuilder(String target, ChannelCredentials creds) {
|
||||
if (target.startsWith("in-process:")) {
|
||||
return super.newChannelBuilder(target, creds);
|
||||
}
|
||||
return InProcessChannelBuilder.forName(target);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,6 +17,7 @@ package org.springframework.grpc.test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
@@ -39,7 +40,8 @@ public class InProcessTransportEnvironmentPostProcessor implements EnvironmentPo
|
||||
return;
|
||||
}
|
||||
MapPropertySource inProcessTransportPropertySource = new MapPropertySource("inProcessTransportPropertySource",
|
||||
Collections.singletonMap("spring.grpc.inprocess.enabled", "true"));
|
||||
Map.of("spring.grpc.test.inprocess.enabled", "true", "spring.grpc.client.inprocess.exclusive", "true",
|
||||
"spring.grpc.server.inprocess.exclusive", "true"));
|
||||
environment.getPropertySources().addFirst(inProcessTransportPropertySource);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
"groups": [],
|
||||
"properties": [
|
||||
{
|
||||
"name": "spring.grpc.inprocess.enabled",
|
||||
"name": "spring.grpc.test.inprocess.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether to enable the in-process server and client for testing.",
|
||||
"defaultValue": true
|
||||
"defaultValue": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
org.springframework.grpc.test.InProcessGrpcServerFactoryAutoConfiguration
|
||||
|
||||
org.springframework.grpc.test.InProcessTestAutoConfiguration
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2025-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.test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.grpc.autoconfigure.client.GrpcClientAutoConfiguration;
|
||||
import org.springframework.grpc.autoconfigure.server.GrpcServerAutoConfiguration;
|
||||
import org.springframework.grpc.autoconfigure.server.GrpcServerFactoryAutoConfiguration;
|
||||
import org.springframework.grpc.client.GrpcChannelFactory;
|
||||
import org.springframework.grpc.server.GrpcServerFactory;
|
||||
|
||||
import io.grpc.BindableService;
|
||||
import io.grpc.ServerServiceDefinition;
|
||||
|
||||
/**
|
||||
* Tests for {@link InProcessTestAutoConfiguration}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
*/
|
||||
class InProcessTestAutoConfigurationTests {
|
||||
|
||||
private final BindableService service = mock();
|
||||
|
||||
private final ServerServiceDefinition serviceDefinition = ServerServiceDefinition.builder("my-service").build();
|
||||
|
||||
@BeforeEach
|
||||
void prepareForTest() {
|
||||
when(service.bindService()).thenReturn(serviceDefinition);
|
||||
}
|
||||
|
||||
private ApplicationContextRunner contextRunner() {
|
||||
return new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(InProcessTestAutoConfiguration.class,
|
||||
GrpcServerAutoConfiguration.class, GrpcServerFactoryAutoConfiguration.class,
|
||||
SslAutoConfiguration.class, GrpcClientAutoConfiguration.class))
|
||||
.withBean(BindableService.class, () -> service);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenTestInProcessEnabledPropIsSetToTrueDoesAutoConfigureBeans() {
|
||||
this.contextRunner()
|
||||
.withPropertyValues("spring.grpc.test.inprocess.enabled=true", "spring.grpc.server.inprocess.name=foo")
|
||||
.run((context) -> {
|
||||
assertThat(context).getBeans(GrpcServerFactory.class)
|
||||
.containsOnlyKeys("testInProcessGrpcServerFactory", "nettyGrpcServerFactory");
|
||||
assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsOnlyKeys("testInProcessGrpcChannelFactory", "nettyGrpcChannelFactory");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenTestInProcessEnabledPropIsNotSetDoesNotAutoConfigureBeans() {
|
||||
this.contextRunner().withPropertyValues("spring.grpc.server.inprocess.name=foo").run((context) -> {
|
||||
assertThat(context).getBeans(GrpcServerFactory.class)
|
||||
.containsOnlyKeys("inProcessGrpcServerFactory", "nettyGrpcServerFactory");
|
||||
assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsOnlyKeys("inProcessGrpcChannelFactory", "nettyGrpcChannelFactory");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenTestInProcessEnabledPropIsSetToFalseDoesNotAutoConfigureBeans() {
|
||||
this.contextRunner()
|
||||
.withPropertyValues("spring.grpc.test.inprocess.enabled=false", "spring.grpc.server.inprocess.name=foo")
|
||||
.run((context) -> {
|
||||
assertThat(context).getBeans(GrpcServerFactory.class)
|
||||
.containsOnlyKeys("inProcessGrpcServerFactory", "nettyGrpcServerFactory");
|
||||
assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsOnlyKeys("inProcessGrpcChannelFactory", "nettyGrpcChannelFactory");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user