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:
Chris Bono
2025-06-07 14:02:54 -05:00
committed by Dave Syer
parent fcd70ee4e3
commit a087eac776
30 changed files with 1215 additions and 188 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,2 +1 @@
org.springframework.grpc.test.InProcessGrpcServerFactoryAutoConfiguration
org.springframework.grpc.test.InProcessTestAutoConfiguration

View File

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