Skip to content

Commit eb9454e

Browse files
committed
Add Spring gRPC support
This commit introduces support for Spring gRPC by moving the autoconfiguration, test, and starter modules from Spring gRPC to Spring Boot (here). The relavant docs from Spring gRPC are not included in this commit and will be available in a subsequent commit. Signed-off-by: onobc <[email protected]>
1 parent 3a9ab15 commit eb9454e

File tree

112 files changed

+9538
-5
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

112 files changed

+9538
-5
lines changed

buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ void documentConfigurationProperties() throws IOException {
5858
Snippets snippets = new Snippets(this.configurationPropertyMetadata);
5959
snippets.add("application-properties.core", "Core Properties", this::corePrefixes);
6060
snippets.add("application-properties.cache", "Cache Properties", this::cachePrefixes);
61+
snippets.add("application-properties.grpc", "gRPC Properties", this::grpcPrefixes);
6162
snippets.add("application-properties.mail", "Mail Properties", this::mailPrefixes);
6263
snippets.add("application-properties.json", "JSON Properties", this::jsonPrefixes);
6364
snippets.add("application-properties.data", "Data Properties", this::dataPrefixes);
@@ -159,6 +160,10 @@ private void dataMigrationPrefixes(Config prefix) {
159160
prefix.accept("spring.sql.init");
160161
}
161162

163+
private void grpcPrefixes(Config prefix) {
164+
prefix.accept("spring.grpc");
165+
}
166+
162167
private void integrationPrefixes(Config prefix) {
163168
prefix.accept("spring.activemq");
164169
prefix.accept("spring.artemis");

config/checkstyle/checkstyle-suppressions.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
<suppress files="OutputCaptureRuleTests" checks="SpringJUnit5" />
5151
<suppress files="[\\/]smoke-test[\\/]" checks="SpringJavadoc" message="\@since" />
5252
<suppress files="[\\/]smoke-test[\\/]spring-boot-smoke-test-testng[\\/]" checks="SpringJUnit5" />
53+
<suppress files="[\\/]smoke-test[\\/]spring-boot-smoke-test-grpc[\\/]build[\\/]generated[\\/]sources[\\/]proto" checks=".*" />
5354
<suppress files="[\\/]test-support[\\/]" checks="SpringJavadoc" message="\@since" />
5455
<suppress files="[\\/]src[\\/]dockerTest[\\/]java[\\/]" checks="JavadocPackage" />
5556
<suppress files="[\\/]src[\\/]dockerTest[\\/]java[\\/]" checks="SpringJavadoc" message="\@since" />

documentation/spring-boot-docs/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ dependencies {
9393
implementation(project(path: ":module:spring-boot-data-elasticsearch"))
9494
implementation(project(path: ":module:spring-boot-data-neo4j"))
9595
implementation(project(path: ":module:spring-boot-devtools"))
96+
implementation(project(path: ":module:spring-boot-grpc-client"))
97+
implementation(project(path: ":module:spring-boot-grpc-server"))
9698
implementation(project(path: ":module:spring-boot-health"))
9799
implementation(project(path: ":module:spring-boot-hibernate"))
98100
implementation(project(path: ":module:spring-boot-http-converter"))
@@ -178,6 +180,7 @@ dependencies {
178180
implementation("org.springframework.data:spring-data-r2dbc")
179181
implementation("org.springframework.graphql:spring-graphql")
180182
implementation("org.springframework.graphql:spring-graphql-test")
183+
implementation("org.springframework.grpc:spring-grpc-core")
181184
implementation("org.springframework.kafka:spring-kafka")
182185
implementation("org.springframework.kafka:spring-kafka-test")
183186
implementation("org.springframework.pulsar:spring-pulsar")
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the License);
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
plugins {
18+
id "java-library"
19+
id "org.springframework.boot.auto-configuration"
20+
id "org.springframework.boot.configuration-properties"
21+
id "org.springframework.boot.deployed"
22+
id "org.springframework.boot.optional-dependencies"
23+
}
24+
25+
description = "Spring Boot gRPC Client"
26+
27+
28+
dependencies {
29+
api(project(":core:spring-boot"))
30+
api("org.springframework.grpc:spring-grpc-core")
31+
32+
compileOnly("com.fasterxml.jackson.core:jackson-annotations")
33+
34+
optional(project(":core:spring-boot-autoconfigure"))
35+
optional(project(":module:spring-boot-actuator"))
36+
optional(project(":module:spring-boot-actuator-autoconfigure"))
37+
optional(project(":module:spring-boot-health"))
38+
optional(project(":module:spring-boot-micrometer-observation"))
39+
optional(project(":module:spring-boot-security"))
40+
optional(project(":module:spring-boot-security-oauth2-client"))
41+
optional(project(":module:spring-boot-security-oauth2-resource-server"))
42+
optional("io.grpc:grpc-servlet-jakarta")
43+
optional("io.grpc:grpc-stub")
44+
optional("io.grpc:grpc-netty")
45+
optional("io.grpc:grpc-netty-shaded")
46+
optional("io.grpc:grpc-inprocess")
47+
optional("io.grpc:grpc-kotlin-stub") {
48+
exclude group: "javax.annotation", module: "javax.annotation-api"
49+
}
50+
optional("io.micrometer:micrometer-core")
51+
optional("io.netty:netty-transport-native-epoll")
52+
optional("io.projectreactor:reactor-core")
53+
optional("jakarta.servlet:jakarta.servlet-api")
54+
optional("org.springframework:spring-web")
55+
optional("org.springframework.security:spring-security-config")
56+
optional("org.springframework.security:spring-security-oauth2-client")
57+
optional("org.springframework.security:spring-security-oauth2-resource-server")
58+
optional("org.springframework.security:spring-security-oauth2-jose")
59+
optional("org.springframework.security:spring-security-web")
60+
61+
testCompileOnly("com.fasterxml.jackson.core:jackson-annotations")
62+
63+
testImplementation(project(":core:spring-boot-test"))
64+
testImplementation(project(":test-support:spring-boot-test-support"))
65+
66+
testRuntimeOnly("ch.qos.logback:logback-classic")
67+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.grpc.client.autoconfigure;
18+
19+
import java.util.ArrayList;
20+
import java.util.Collections;
21+
import java.util.List;
22+
23+
import io.grpc.ManagedChannelBuilder;
24+
25+
import org.springframework.boot.util.LambdaSafe;
26+
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
27+
28+
/**
29+
* Invokes the available {@link GrpcChannelBuilderCustomizer} instances for a given
30+
* {@link ManagedChannelBuilder}.
31+
*
32+
* @author Chris Bono
33+
* @since 4.0.0
34+
*/
35+
public class ChannelBuilderCustomizers {
36+
37+
private final List<GrpcChannelBuilderCustomizer<?>> customizers;
38+
39+
ChannelBuilderCustomizers(List<? extends GrpcChannelBuilderCustomizer<?>> customizers) {
40+
this.customizers = (customizers != null) ? new ArrayList<>(customizers) : Collections.emptyList();
41+
}
42+
43+
/**
44+
* Customize the specified {@link ManagedChannelBuilder}. Locates all
45+
* {@link GrpcChannelBuilderCustomizer} beans able to handle the specified instance
46+
* and invoke {@link GrpcChannelBuilderCustomizer#customize} on them.
47+
* @param <T> the type of channel builder
48+
* @param authority the target authority of the channel
49+
* @param channelBuilder the builder to customize
50+
* @return the customized builder
51+
*/
52+
@SuppressWarnings("unchecked")
53+
<T extends ManagedChannelBuilder<?>> T customize(String authority, T channelBuilder) {
54+
LambdaSafe.callbacks(GrpcChannelBuilderCustomizer.class, this.customizers, channelBuilder)
55+
.withLogger(ChannelBuilderCustomizers.class)
56+
.invoke((customizer) -> customizer.customize(authority, channelBuilder));
57+
return channelBuilder;
58+
}
59+
60+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.grpc.client.autoconfigure;
18+
19+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
20+
import org.springframework.context.ApplicationContext;
21+
import org.springframework.context.annotation.Bean;
22+
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.grpc.client.ClientInterceptorsConfigurer;
24+
25+
/**
26+
* Configuration for {@link ClientInterceptorsConfigurer}.
27+
*
28+
* @author Chris Bono
29+
* @since 4.0.0
30+
*/
31+
@Configuration(proxyBeanMethods = false)
32+
public class ClientInterceptorsConfiguration {
33+
34+
@Bean
35+
@ConditionalOnMissingBean
36+
ClientInterceptorsConfigurer clientInterceptorsConfigurer(ApplicationContext applicationContext) {
37+
return new ClientInterceptorsConfigurer(applicationContext);
38+
}
39+
40+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.grpc.client.autoconfigure;
18+
19+
import java.time.Duration;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
import java.util.concurrent.TimeUnit;
23+
import java.util.function.BiConsumer;
24+
import java.util.function.Consumer;
25+
26+
import io.grpc.ManagedChannelBuilder;
27+
28+
import org.springframework.boot.context.properties.PropertyMapper;
29+
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.ChannelConfig;
30+
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
31+
import org.springframework.grpc.client.interceptor.DefaultDeadlineSetupClientInterceptor;
32+
import org.springframework.util.unit.DataSize;
33+
34+
/**
35+
* A {@link GrpcChannelBuilderCustomizer} that maps {@link GrpcClientProperties client
36+
* properties} to a channel builder.
37+
*
38+
* @param <T> the type of the builder
39+
* @author David Syer
40+
* @author Chris Bono
41+
*/
42+
class ClientPropertiesChannelBuilderCustomizer<T extends ManagedChannelBuilder<T>>
43+
implements GrpcChannelBuilderCustomizer<T> {
44+
45+
private final GrpcClientProperties properties;
46+
47+
ClientPropertiesChannelBuilderCustomizer(GrpcClientProperties properties) {
48+
this.properties = properties;
49+
}
50+
51+
@Override
52+
public void customize(String authority, T builder) {
53+
ChannelConfig channel = this.properties.getChannel(authority);
54+
PropertyMapper mapper = PropertyMapper.get();
55+
mapper.from(channel.getUserAgent()).to(builder::userAgent);
56+
if (!authority.startsWith("unix:") && !authority.startsWith("in-process:")) {
57+
mapper.from(channel.getDefaultLoadBalancingPolicy()).to(builder::defaultLoadBalancingPolicy);
58+
}
59+
mapper.from(channel.getMaxInboundMessageSize()).asInt(DataSize::toBytes).to(builder::maxInboundMessageSize);
60+
mapper.from(channel.getMaxInboundMetadataSize()).asInt(DataSize::toBytes).to(builder::maxInboundMetadataSize);
61+
mapper.from(channel.getKeepAliveTime()).to(durationProperty(builder::keepAliveTime));
62+
mapper.from(channel.getKeepAliveTimeout()).to(durationProperty(builder::keepAliveTimeout));
63+
mapper.from(channel.getIdleTimeout()).to(durationProperty(builder::idleTimeout));
64+
mapper.from(channel.isKeepAliveWithoutCalls()).to(builder::keepAliveWithoutCalls);
65+
Map<String, Object> defaultServiceConfig = new HashMap<>(channel.getServiceConfig());
66+
if (channel.getHealth().isEnabled()) {
67+
String serviceNameToCheck = (channel.getHealth().getServiceName() != null)
68+
? channel.getHealth().getServiceName() : "";
69+
defaultServiceConfig.put("healthCheckConfig", Map.of("serviceName", serviceNameToCheck));
70+
}
71+
if (!defaultServiceConfig.isEmpty()) {
72+
builder.defaultServiceConfig(defaultServiceConfig);
73+
}
74+
if (channel.getDefaultDeadline() != null && channel.getDefaultDeadline().toMillis() > 0L) {
75+
builder.intercept(new DefaultDeadlineSetupClientInterceptor(channel.getDefaultDeadline()));
76+
}
77+
}
78+
79+
Consumer<Duration> durationProperty(BiConsumer<Long, TimeUnit> setter) {
80+
return (duration) -> setter.accept(duration.toNanos(), TimeUnit.NANOSECONDS);
81+
}
82+
83+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.grpc.client.autoconfigure;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import org.jspecify.annotations.Nullable;
23+
24+
import org.springframework.beans.BeansException;
25+
import org.springframework.beans.factory.BeanFactory;
26+
import org.springframework.beans.factory.BeanFactoryAware;
27+
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
29+
import org.springframework.boot.context.properties.bind.Binder;
30+
import org.springframework.boot.grpc.client.autoconfigure.ClientScanConfiguration.DefaultGrpcClientRegistrations;
31+
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.ChannelConfig;
32+
import org.springframework.context.EnvironmentAware;
33+
import org.springframework.context.annotation.Configuration;
34+
import org.springframework.context.annotation.Import;
35+
import org.springframework.core.env.Environment;
36+
import org.springframework.core.type.AnnotationMetadata;
37+
import org.springframework.grpc.client.AbstractGrpcClientRegistrar;
38+
import org.springframework.grpc.client.GrpcClientFactory;
39+
import org.springframework.grpc.client.GrpcClientFactory.GrpcClientRegistrationSpec;
40+
import org.springframework.util.Assert;
41+
42+
@Configuration(proxyBeanMethods = false)
43+
@ConditionalOnMissingBean(GrpcClientFactory.class)
44+
@Import(DefaultGrpcClientRegistrations.class)
45+
public class ClientScanConfiguration {
46+
47+
static class DefaultGrpcClientRegistrations extends AbstractGrpcClientRegistrar
48+
implements EnvironmentAware, BeanFactoryAware {
49+
50+
private @Nullable Environment environment;
51+
52+
private @Nullable BeanFactory beanFactory;
53+
54+
@Override
55+
public void setEnvironment(Environment environment) {
56+
this.environment = environment;
57+
}
58+
59+
@Override
60+
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
61+
this.beanFactory = beanFactory;
62+
}
63+
64+
@Override
65+
protected GrpcClientRegistrationSpec[] collect(AnnotationMetadata meta) {
66+
Assert.notNull(this.environment, "Environment must not be null");
67+
Assert.notNull(this.beanFactory, "BeanFactory must not be null");
68+
Binder binder = Binder.get(this.environment);
69+
boolean hasDefaultChannel = binder.bind("spring.grpc.client.default-channel", ChannelConfig.class)
70+
.isBound();
71+
if (hasDefaultChannel) {
72+
List<String> packages = new ArrayList<>();
73+
if (AutoConfigurationPackages.has(this.beanFactory)) {
74+
packages.addAll(AutoConfigurationPackages.get(this.beanFactory));
75+
}
76+
GrpcClientProperties props = binder.bind("spring.grpc.client", GrpcClientProperties.class)
77+
.orElseGet(GrpcClientProperties::new);
78+
79+
return new GrpcClientRegistrationSpec[] { GrpcClientRegistrationSpec.of("default")
80+
.factory(props.getDefaultStubFactory())
81+
.packages(packages.toArray(new String[0])) };
82+
}
83+
return new GrpcClientRegistrationSpec[0];
84+
}
85+
86+
}
87+
88+
}

0 commit comments

Comments
 (0)