Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ void documentConfigurationProperties() throws IOException {
Snippets snippets = new Snippets(this.configurationPropertyMetadata);
snippets.add("application-properties.core", "Core Properties", this::corePrefixes);
snippets.add("application-properties.cache", "Cache Properties", this::cachePrefixes);
snippets.add("application-properties.grpc", "gRPC Properties", this::grpcPrefixes);
snippets.add("application-properties.mail", "Mail Properties", this::mailPrefixes);
snippets.add("application-properties.json", "JSON Properties", this::jsonPrefixes);
snippets.add("application-properties.data", "Data Properties", this::dataPrefixes);
Expand Down Expand Up @@ -159,6 +160,10 @@ private void dataMigrationPrefixes(Config prefix) {
prefix.accept("spring.sql.init");
}

private void grpcPrefixes(Config prefix) {
prefix.accept("spring.grpc");
}

private void integrationPrefixes(Config prefix) {
prefix.accept("spring.activemq");
prefix.accept("spring.artemis");
Expand Down
1 change: 1 addition & 0 deletions config/checkstyle/checkstyle-suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<suppress files="OutputCaptureRuleTests" checks="SpringJUnit5" />
<suppress files="[\\/]smoke-test[\\/]" checks="SpringJavadoc" message="\@since" />
<suppress files="[\\/]smoke-test[\\/]spring-boot-smoke-test-testng[\\/]" checks="SpringJUnit5" />
<suppress files="[\\/]smoke-test[\\/]spring-boot-smoke-test-grpc[\\/]build[\\/]generated[\\/]sources[\\/]proto" checks=".*" />
<suppress files="[\\/]test-support[\\/]" checks="SpringJavadoc" message="\@since" />
<suppress files="[\\/]src[\\/]dockerTest[\\/]java[\\/]" checks="JavadocPackage" />
<suppress files="[\\/]src[\\/]dockerTest[\\/]java[\\/]" checks="SpringJavadoc" message="\@since" />
Expand Down
3 changes: 3 additions & 0 deletions documentation/spring-boot-docs/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ dependencies {
implementation(project(path: ":module:spring-boot-data-redis-test"))
implementation(project(path: ":module:spring-boot-devtools"))
implementation(project(path: ":module:spring-boot-graphql-test"))
implementation(project(path: ":module:spring-boot-grpc-client"))
implementation(project(path: ":module:spring-boot-grpc-server"))
implementation(project(path: ":module:spring-boot-health"))
implementation(project(path: ":module:spring-boot-hibernate"))
implementation(project(path: ":module:spring-boot-http-converter"))
Expand Down Expand Up @@ -195,6 +197,7 @@ dependencies {
implementation("org.springframework.data:spring-data-r2dbc")
implementation("org.springframework.graphql:spring-graphql")
implementation("org.springframework.graphql:spring-graphql-test")
implementation("org.springframework.grpc:spring-grpc-core")
implementation("org.springframework.kafka:spring-kafka")
implementation("org.springframework.kafka:spring-kafka-test")
implementation("org.springframework.pulsar:spring-pulsar")
Expand Down
67 changes: 67 additions & 0 deletions module/spring-boot-grpc-client/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2012-present 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.
*/

plugins {
id "java-library"
id "org.springframework.boot.auto-configuration"
id "org.springframework.boot.configuration-properties"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
}

description = "Spring Boot gRPC Client"


dependencies {
api(project(":core:spring-boot"))
api("org.springframework.grpc:spring-grpc-core")

compileOnly("com.fasterxml.jackson.core:jackson-annotations")

optional(project(":core:spring-boot-autoconfigure"))
optional(project(":module:spring-boot-actuator"))
optional(project(":module:spring-boot-actuator-autoconfigure"))
optional(project(":module:spring-boot-health"))
optional(project(":module:spring-boot-micrometer-observation"))
optional(project(":module:spring-boot-security"))
optional(project(":module:spring-boot-security-oauth2-client"))
optional(project(":module:spring-boot-security-oauth2-resource-server"))
optional("io.grpc:grpc-servlet-jakarta")
optional("io.grpc:grpc-stub")
optional("io.grpc:grpc-netty")
optional("io.grpc:grpc-netty-shaded")
optional("io.grpc:grpc-inprocess")
optional("io.grpc:grpc-kotlin-stub") {
exclude group: "javax.annotation", module: "javax.annotation-api"
}
optional("io.micrometer:micrometer-core")
optional("io.netty:netty-transport-native-epoll")
optional("io.projectreactor:reactor-core")
optional("jakarta.servlet:jakarta.servlet-api")
optional("org.springframework:spring-web")
optional("org.springframework.security:spring-security-config")
optional("org.springframework.security:spring-security-oauth2-client")
optional("org.springframework.security:spring-security-oauth2-resource-server")
optional("org.springframework.security:spring-security-oauth2-jose")
optional("org.springframework.security:spring-security-web")

testCompileOnly("com.fasterxml.jackson.core:jackson-annotations")

testImplementation(project(":core:spring-boot-test"))
testImplementation(project(":test-support:spring-boot-test-support"))

testRuntimeOnly("ch.qos.logback:logback-classic")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2012-present 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.boot.grpc.client.autoconfigure;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import io.grpc.ManagedChannelBuilder;

import org.springframework.boot.util.LambdaSafe;
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;

/**
* Invokes the available {@link GrpcChannelBuilderCustomizer} instances for a given
* {@link ManagedChannelBuilder}.
*
* @author Chris Bono
*/
class ChannelBuilderCustomizers {

private final List<GrpcChannelBuilderCustomizer<?>> customizers;

ChannelBuilderCustomizers(List<? extends GrpcChannelBuilderCustomizer<?>> customizers) {
this.customizers = (customizers != null) ? new ArrayList<>(customizers) : Collections.emptyList();
}

/**
* Customize the specified {@link ManagedChannelBuilder}. Locates all
* {@link GrpcChannelBuilderCustomizer} beans able to handle the specified instance
* and invoke {@link GrpcChannelBuilderCustomizer#customize} on them.
* @param <T> the type of channel builder
* @param authority the target authority of the channel
* @param channelBuilder the builder to customize
* @return the customized builder
*/
@SuppressWarnings("unchecked")
<T extends ManagedChannelBuilder<?>> T customize(String authority, T channelBuilder) {
LambdaSafe.callbacks(GrpcChannelBuilderCustomizer.class, this.customizers, channelBuilder)
.withLogger(ChannelBuilderCustomizers.class)
.invoke((customizer) -> customizer.customize(authority, channelBuilder));
return channelBuilder;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright 2012-present 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.boot.grpc.client.autoconfigure;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import io.grpc.ManagedChannelBuilder;

import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.ChannelConfig;
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
import org.springframework.grpc.client.interceptor.DefaultDeadlineSetupClientInterceptor;
import org.springframework.util.unit.DataSize;

/**
* A {@link GrpcChannelBuilderCustomizer} that maps {@link GrpcClientProperties client
* properties} to a channel builder.
*
* @param <T> the type of the builder
* @author David Syer
* @author Chris Bono
*/
class ClientPropertiesChannelBuilderCustomizer<T extends ManagedChannelBuilder<T>>
implements GrpcChannelBuilderCustomizer<T> {

private final GrpcClientProperties properties;

ClientPropertiesChannelBuilderCustomizer(GrpcClientProperties properties) {
this.properties = properties;
}

@Override
public void customize(String authority, T builder) {
ChannelConfig channel = this.properties.getChannel(authority);
PropertyMapper mapper = PropertyMapper.get();
mapper.from(channel.getUserAgent()).to(builder::userAgent);
if (!authority.startsWith("unix:") && !authority.startsWith("in-process:")) {
mapper.from(channel.getDefaultLoadBalancingPolicy()).to(builder::defaultLoadBalancingPolicy);
}
mapper.from(channel.getMaxInboundMessageSize()).asInt(DataSize::toBytes).to(builder::maxInboundMessageSize);
mapper.from(channel.getMaxInboundMetadataSize()).asInt(DataSize::toBytes).to(builder::maxInboundMetadataSize);
mapper.from(channel.getKeepAliveTime()).to(durationProperty(builder::keepAliveTime));
mapper.from(channel.getKeepAliveTimeout()).to(durationProperty(builder::keepAliveTimeout));
mapper.from(channel.getIdleTimeout()).to(durationProperty(builder::idleTimeout));
mapper.from(channel.isKeepAliveWithoutCalls()).to(builder::keepAliveWithoutCalls);
Map<String, Object> defaultServiceConfig = new HashMap<>(channel.getServiceConfig());
if (channel.getHealth().isEnabled()) {
String serviceNameToCheck = (channel.getHealth().getServiceName() != null)
? channel.getHealth().getServiceName() : "";
defaultServiceConfig.put("healthCheckConfig", Map.of("serviceName", serviceNameToCheck));
}
if (!defaultServiceConfig.isEmpty()) {
builder.defaultServiceConfig(defaultServiceConfig);
}
if (channel.getDefaultDeadline() != null && channel.getDefaultDeadline().toMillis() > 0L) {
builder.intercept(new DefaultDeadlineSetupClientInterceptor(channel.getDefaultDeadline()));
}
}

Consumer<Duration> durationProperty(BiConsumer<Long, TimeUnit> setter) {
return (duration) -> setter.accept(duration.toNanos(), TimeUnit.NANOSECONDS);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2012-present 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.boot.grpc.client.autoconfigure;

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;

/**
* {@link EnableAutoConfiguration Auto-configuration} for a
* {@link CompositeGrpcChannelFactory}.
*
* @author Chris Bono
* @since 4.0.0
*/
@AutoConfiguration
@ConditionalOnGrpcClientEnabled
@Conditional(CompositeChannelFactoryAutoConfiguration.MultipleNonPrimaryChannelFactoriesCondition.class)
public final 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 {

}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2012-present 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.boot.grpc.client.autoconfigure;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import io.grpc.stub.AbstractStub;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Conditional;

/**
* {@link Conditional @Conditional} that only matches when the {@code io.grpc:grpc-stub}
* module is in the classpath and the {@code spring.grpc.client.enabled} property is not
* explicitly set to {@code false}.
*
* @author Freeman Freeman
* @author Chris Bono
* @since 4.0.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@ConditionalOnClass(AbstractStub.class)
@ConditionalOnProperty(prefix = "spring.grpc.client", name = "enabled", matchIfMissing = true)
public @interface ConditionalOnGrpcClientEnabled {

}
Loading