diff --git a/spring-grpc-docs/src/main/antora/modules/ROOT/pages/server.adoc b/spring-grpc-docs/src/main/antora/modules/ROOT/pages/server.adoc index 306b31d..b502e21 100644 --- a/spring-grpc-docs/src/main/antora/modules/ROOT/pages/server.adoc +++ b/spring-grpc-docs/src/main/antora/modules/ROOT/pages/server.adoc @@ -5,12 +5,15 @@ This section describes core concepts that Spring gRPC uses on the server side. W You only need to provide one or more beans of type `BindableService` to create a gRPC server, provided the classpath contains an implementation of a `Server`. The `BindableService` is a gRPC service that can be bound to a server. The `Server` is the gRPC server that listens for incoming requests and routes them to the appropriate service implementation. -== Create a `BindableService` +== Create a gRPC Service -To create a gRPC server, you need to provide one or more beans of type `BindableService`. +To create a gRPC server, you need to provide one or more beans of type `BindableService` or `ServerServiceDefinition`. There are some `BindableServices` available off the shelf that you could include in your application (an example is the reflection service from the `grpc-services` artifact which allows clients to browse the metadata of your services and download the Portobuf files). Very commonly, you will create your own `BindableService` by extending the generated service implementation from your Protobuf file. -The easiest way to do it is to simply add a Spring `@Service` annotation to the implementation class and have it picked up by the `@ComponentScan` in your Spring Boot application. +The easiest way to activate it is to simply add a Spring `@Service` annotation to the implementation class and have it picked up by the `@ComponentScan` in your Spring Boot application. +Alternatively, you could create an instance of it in a `@Configuration` class and return it as a bean, or return it's `ServerServiceDefinition`. +The last option is useful if you need to add specific behaviour, like an interceptor, to the service. +It is an error to have more than one instance of the same service name, so don't create both a `BindableService` and a `ServerServiceDefinition` bean for the same service. == Netty Server diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/DefaultGrpcServiceDiscoverer.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/DefaultGrpcServiceDiscoverer.java index a3e0095..c39d8ed 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/DefaultGrpcServiceDiscoverer.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/DefaultGrpcServiceDiscoverer.java @@ -16,6 +16,7 @@ package org.springframework.grpc.autoconfigure.server; +import java.util.ArrayList; import java.util.List; import io.grpc.BindableService; @@ -35,13 +36,20 @@ class DefaultGrpcServiceDiscoverer implements GrpcServiceDiscoverer { private final ObjectProvider grpcServicesProvider; - DefaultGrpcServiceDiscoverer(ObjectProvider grpcServicesProvider) { + private ObjectProvider grpcServiceDefinitionsProvider; + + DefaultGrpcServiceDiscoverer(ObjectProvider grpcServicesProvider, + ObjectProvider grpcServiceDefinitionsProvider) { this.grpcServicesProvider = grpcServicesProvider; + this.grpcServiceDefinitionsProvider = grpcServiceDefinitionsProvider; } @Override public List findServices() { - return grpcServicesProvider.orderedStream().map(BindableService::bindService).toList(); + List list = new ArrayList<>( + grpcServicesProvider.orderedStream().map(BindableService::bindService).toList()); + list.addAll(grpcServiceDefinitionsProvider.orderedStream().toList()); + return list; } } diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfiguration.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfiguration.java index 53cff8d..2255c50 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfiguration.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfiguration.java @@ -36,6 +36,7 @@ import io.grpc.BindableService; import io.grpc.CompressorRegistry; import io.grpc.DecompressorRegistry; import io.grpc.ServerBuilder; +import io.grpc.ServerServiceDefinition; /** * {@link EnableAutoConfiguration Auto-configuration} for gRPC server-side components. @@ -75,8 +76,9 @@ public class GrpcServerAutoConfiguration { @ConditionalOnMissingBean @Bean - GrpcServiceDiscoverer grpcServiceDiscoverer(ObjectProvider bindableServicesProvider) { - return new DefaultGrpcServiceDiscoverer(bindableServicesProvider); + GrpcServiceDiscoverer grpcServiceDiscoverer(ObjectProvider bindableServicesProvider, + ObjectProvider serviceDefinitionsProvider) { + return new DefaultGrpcServiceDiscoverer(bindableServicesProvider, serviceDefinitionsProvider); } @ConditionalOnBean(CompressorRegistry.class) diff --git a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java index 300c340..2f99afb 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java +++ b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java @@ -134,6 +134,17 @@ class GrpcServerAutoConfigurationTests { .containsExactly(this.serviceDefinition)); } + @Test + void grpcServiceDiscovererWithServiceDefinitionAsExpected() { + ServerServiceDefinition serviceDefinition = ServerServiceDefinition.builder("my-other-service").build(); + this.contextRunnerWithLifecyle() + .withBean(ServerServiceDefinition.class, () -> serviceDefinition) + .run((context) -> assertThat(context).getBean(GrpcServiceDiscoverer.class) + .extracting(GrpcServiceDiscoverer::findServices, + InstanceOfAssertFactories.list(ServerServiceDefinition.class)) + .containsExactly(this.serviceDefinition, serviceDefinition)); + } + @Test void whenHasUserDefinedServerBuilderCustomizersDoesNotAutoConfigureBean() { ServerBuilderCustomizers customCustomizers = mock(ServerBuilderCustomizers.class);