Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -93,6 +93,8 @@ dependencies {
implementation(project(path: ":module:spring-boot-data-elasticsearch"))
implementation(project(path: ":module:spring-boot-data-neo4j"))
implementation(project(path: ":module:spring-boot-devtools"))
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 @@ -178,6 +180,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,60 @@
/*
* 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
* @since 4.0.0
*/
public 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,40 @@
/*
* 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.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.grpc.client.ClientInterceptorsConfigurer;

/**
* Configuration for {@link ClientInterceptorsConfigurer}.
*
* @author Chris Bono
* @since 4.0.0
*/
@Configuration(proxyBeanMethods = false)
public class ClientInterceptorsConfiguration {

@Bean
@ConditionalOnMissingBean
ClientInterceptorsConfigurer clientInterceptorsConfigurer(ApplicationContext applicationContext) {
return new ClientInterceptorsConfigurer(applicationContext);
}

}
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,88 @@
/*
* 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.List;

import org.jspecify.annotations.Nullable;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.grpc.client.autoconfigure.ClientScanConfiguration.DefaultGrpcClientRegistrations;
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.ChannelConfig;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.grpc.client.AbstractGrpcClientRegistrar;
import org.springframework.grpc.client.GrpcClientFactory;
import org.springframework.grpc.client.GrpcClientFactory.GrpcClientRegistrationSpec;
import org.springframework.util.Assert;

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(GrpcClientFactory.class)
@Import(DefaultGrpcClientRegistrations.class)
public class ClientScanConfiguration {

static class DefaultGrpcClientRegistrations extends AbstractGrpcClientRegistrar
implements EnvironmentAware, BeanFactoryAware {

private @Nullable Environment environment;

private @Nullable BeanFactory beanFactory;

@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}

@Override
protected GrpcClientRegistrationSpec[] collect(AnnotationMetadata meta) {
Assert.notNull(this.environment, "Environment must not be null");
Assert.notNull(this.beanFactory, "BeanFactory must not be null");
Binder binder = Binder.get(this.environment);
boolean hasDefaultChannel = binder.bind("spring.grpc.client.default-channel", ChannelConfig.class)
.isBound();
if (hasDefaultChannel) {
List<String> packages = new ArrayList<>();
if (AutoConfigurationPackages.has(this.beanFactory)) {
packages.addAll(AutoConfigurationPackages.get(this.beanFactory));
}
GrpcClientProperties props = binder.bind("spring.grpc.client", GrpcClientProperties.class)
.orElseGet(GrpcClientProperties::new);

return new GrpcClientRegistrationSpec[] { GrpcClientRegistrationSpec.of("default")
.factory(props.getDefaultStubFactory())
.packages(packages.toArray(new String[0])) };
}
return new GrpcClientRegistrationSpec[0];
}

}

}
Loading
Loading