diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java index 510b7f9..4e89fea 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java @@ -12,44 +12,46 @@ * 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.server; -import static java.util.Objects.requireNonNull; +package org.springframework.grpc.server; import java.util.LinkedHashSet; import java.util.List; +import java.util.Objects; import java.util.Set; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import com.google.common.collect.Lists; - import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.ServerServiceDefinition; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +/** + * Default implementation of {@link GrpcServerFactory}. + * + * @param the type of server builder + */ public class DefaultGrpcServerFactory> implements GrpcServerFactory { private static final String ANY_IP_ADDRESS = "*"; - /** Logger available to subclasses. */ + // VisibleForSubclass protected final Log logger = LogFactory.getLog(getClass()); private final List serviceList = Lists.newLinkedList(); - private final List serverConfigurers; - private final String address; private final int port; - public DefaultGrpcServerFactory(String address, int port, final List serverConfigurers) { - this.serverConfigurers = requireNonNull(serverConfigurers, "serverConfigurers"); + private final List serverBuilderCustomizers; + + public DefaultGrpcServerFactory(String address, int port, List serverBuilderCustomizers) { this.address = address; this.port = port; + this.serverBuilderCustomizers = Objects.requireNonNull(serverBuilderCustomizers, "serverBuilderCustomizers"); } protected String getAddress() { @@ -62,11 +64,16 @@ public class DefaultGrpcServerFactory> implements Grp @Override public Server createServer() { - final T builder = newServerBuilder(); - configure(builder); + T builder = newServerBuilder(); + configure(builder, this.serviceList); return builder.build(); } + @Override + public void addService(ServerServiceDefinition service) { + this.serviceList.add(service); + } + /** * Creates a new server builder. * @return The newly created server builder. @@ -113,38 +120,35 @@ public class DefaultGrpcServerFactory> implements Grp } /** - * Configures the given server builder. This method can be overwritten to add features - * that are not yet supported by this library or use a {@link GrpcServerConfigurer} - * instead. - * @param builder The server builder to configure. + * Configures the server builder by adding service definitions and applying + * customizers to the builder. + *

+ * Subclasses can override this to add features that are not yet supported by this + * library. + * @param builder the server builder to configure + * @param serviceDefinitions the service definitions to add to the builder */ - protected void configure(final T builder) { - configureServices(builder); - for (final GrpcServerConfigurer serverConfigurer : this.serverConfigurers) { - serverConfigurer.accept(builder); - } + protected void configure(T builder, List serviceDefinitions) { + configureServices(builder, serviceDefinitions); + this.serverBuilderCustomizers.forEach((c) -> c.customize(builder)); } /** - * Configures the services that should be served by the server. - * @param builder The server builder to configure. + * Configure the services to be served by the server. + * @param builder the server builder to add the services to + * @param serviceDefinitions the service definitions to configure and add to the + * builder */ - protected void configureServices(final T builder) { - final Set serviceNames = new LinkedHashSet<>(); - - for (final ServerServiceDefinition service : this.serviceList) { - final String serviceName = service.getServiceDescriptor().getName(); + protected void configureServices(T builder, List serviceDefinitions) { + Set serviceNames = new LinkedHashSet<>(); + serviceDefinitions.forEach((service) -> { + String serviceName = service.getServiceDescriptor().getName(); if (!serviceNames.add(serviceName)) { throw new IllegalStateException("Found duplicate service implementation: " + serviceName); } logger.info("Registered gRPC service: " + serviceName); builder.addService(service); - } - } - - @Override - public void addService(final ServerServiceDefinition service) { - this.serviceList.add(service); + }); } } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/GrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/GrpcServerFactory.java index dcf490b..816b667 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/GrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/GrpcServerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -12,7 +12,7 @@ * 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. - * + * * Partial copy from net.devh:grpc-spring-boot-starter. */ @@ -21,10 +21,30 @@ package org.springframework.grpc.server; import io.grpc.Server; import io.grpc.ServerServiceDefinition; +import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle; + +/** + * Factory interface that can be used to create a {@link Server gRPC Server}. + * + * @author David Syer + * @author Chris Bono + */ public interface GrpcServerFactory { + /** + * Gets a new fully configured but not started {@link Server} instance. Clients should + * not be able to connect to the returned server until {@link Server#start()} is + * called (which happens when the {@code GrpcServerLifecycle} is started). + * @return a fully configured not started {@link Server} + * @see GrpcServerLifecycle + */ Server createServer(); + /** + * Adds a service definition to the server. Must be called prior to + * {@link Server#start()}. + * @param service the service definition to add + */ void addService(ServerServiceDefinition service); } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/GrpcServerConfigurer.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/ServerBuilderCustomizer.java similarity index 69% rename from spring-grpc-core/src/main/java/org/springframework/grpc/server/GrpcServerConfigurer.java rename to spring-grpc-core/src/main/java/org/springframework/grpc/server/ServerBuilderCustomizer.java index 1e692c3..d1ba610 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/GrpcServerConfigurer.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/ServerBuilderCustomizer.java @@ -15,11 +15,20 @@ */ package org.springframework.grpc.server; -import java.util.function.Consumer; - import io.grpc.ServerBuilder; +/** + * Callback interface that can be used to customize a {@link ServerBuilder}. + * + * @author Chris Bono + */ @FunctionalInterface -public interface GrpcServerConfigurer extends Consumer> { +public interface ServerBuilderCustomizer { + + /** + * Callback to customize a {@link ServerBuilder} instance. + * @param serverBuilder the builder to customize + */ + void customize(ServerBuilder serverBuilder); } 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 9dd260f..08bb4b6 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 @@ -15,8 +15,6 @@ */ package org.springframework.grpc.autoconfigure.server; -import java.util.List; - import io.grpc.BindableService; import org.springframework.beans.factory.ObjectProvider; @@ -29,8 +27,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.grpc.server.DefaultGrpcServerFactory; -import org.springframework.grpc.server.GrpcServerConfigurer; import org.springframework.grpc.server.GrpcServerFactory; +import org.springframework.grpc.server.ServerBuilderCustomizer; import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle; /** @@ -57,9 +55,9 @@ public class GrpcServerAutoConfiguration { @ConditionalOnMissingBean(GrpcServerFactory.class) @Bean DefaultGrpcServerFactory defaultGrpcServerFactory(ObjectProvider grpcServicesProvider, - List serverConfigurers) { + ObjectProvider builderCustomizersProvider) { DefaultGrpcServerFactory factory = new DefaultGrpcServerFactory<>(this.properties.getAddress(), - this.properties.getPort(), serverConfigurers); + this.properties.getPort(), builderCustomizersProvider.orderedStream().toList()); grpcServicesProvider.orderedStream().map(BindableService::bindService).forEach(factory::addService); return factory; } diff --git a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/server/GrpcServerAutoConfigurationTests.java b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/server/GrpcServerAutoConfigurationTests.java index f52dc62..1c25780 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/server/GrpcServerAutoConfigurationTests.java +++ b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/server/GrpcServerAutoConfigurationTests.java @@ -20,12 +20,17 @@ import java.util.List; import io.grpc.BindableService; import io.grpc.ServerServiceDefinition; +import io.grpc.ServiceDescriptor; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.boot.autoconfigure.AutoConfigurations; 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.autoconfigure.server.GrpcServerAutoConfiguration; import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle; @@ -89,7 +94,22 @@ class GrpcServerAutoConfigurationTests { .run((context) -> assertThat(context).getBean(DefaultGrpcServerFactory.class) .hasFieldOrPropertyWithValue("address", "myhost") .hasFieldOrPropertyWithValue("port", 6160) - .hasFieldOrPropertyWithValue("serverConfigurers", List.of())); + .hasFieldOrPropertyWithValue("serverBuilderCustomizers", List.of()) + .extracting("serviceList", InstanceOfAssertFactories.list(ServerServiceDefinition.class)) + .singleElement() + .extracting(ServerServiceDefinition::getServiceDescriptor) + .extracting(ServiceDescriptor::getName) + .isEqualTo("my-service")); + } + + @Test + void serverFactoryAutoConfiguredWithCustomizers() { + this.validContextRunner() + .withUserConfiguration(ServerFactoryCustomizersConfig.class) + .run((context) -> assertThat(context).getBean(DefaultGrpcServerFactory.class) + .extracting("serverBuilderCustomizers", InstanceOfAssertFactories.list(ServerBuilderCustomizer.class)) + .containsExactly(ServerFactoryCustomizersConfig.CUSTOMIZER_BAR, + ServerFactoryCustomizersConfig.CUSTOMIZER_FOO)); } @Test @@ -99,4 +119,27 @@ class GrpcServerAutoConfigurationTests { .hasFieldOrPropertyWithValue("factory", context.getBean(DefaultGrpcServerFactory.class))); } + @Configuration(proxyBeanMethods = false) + static class ServerFactoryCustomizersConfig { + + static ServerBuilderCustomizer CUSTOMIZER_FOO = (serverBuilder) -> { + }; + + static ServerBuilderCustomizer CUSTOMIZER_BAR = (serverBuilder) -> { + }; + + @Bean + @Order(200) + ServerBuilderCustomizer customizerFoo() { + return CUSTOMIZER_FOO; + } + + @Bean + @Order(100) + ServerBuilderCustomizer customizerBar() { + return CUSTOMIZER_BAR; + } + + } + }