Skip to content

Commit 1d4f7f9

Browse files
authored
GH-3466: Optimize KafkaAdmin creation in KafkaTemplate (#3471)
* GH-3466: Optimize KafkaAdmin creation in KafkaTemplate Fixes: #3466 #3466 Improve bootstrap-server config comparison to avoid unnecessary KafkaAdmin recreation. This addresses inconsistencies between List<String> and String configurations for bootstrap servers. The change ensures that List versions of bootstrap-server configs are converted to regular Strings by removing brackets. This allows for consistent comparison between producer and admin configurations. This optimization is particularly relevant for Spring Boot scenarios where configs may be provided in different formats but represent the same underlying values. * Addressing PR review **Auto-cherry-pick to `3.2.x` & `3.1.x`**
1 parent 4a135a1 commit 1d4f7f9

File tree

2 files changed

+38
-7
lines changed

2 files changed

+38
-7
lines changed

spring-kafka/src/main/java/org/springframework/kafka/core/KafkaTemplate.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
import org.springframework.transaction.support.TransactionSynchronizationManager;
8181
import org.springframework.util.Assert;
8282
import org.springframework.util.CollectionUtils;
83+
import org.springframework.util.StringUtils;
8384

8485
import io.micrometer.observation.Observation;
8586
import io.micrometer.observation.ObservationRegistry;
@@ -484,8 +485,9 @@ public void afterSingletonsInstantiated() {
484485
if (this.kafkaAdmin == null) {
485486
this.kafkaAdmin = this.applicationContext.getBeanProvider(KafkaAdmin.class).getIfUnique();
486487
if (this.kafkaAdmin != null) {
487-
Object producerServers = this.producerFactory.getConfigurationProperties()
488-
.get(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG);
488+
String producerServers = this.producerFactory.getConfigurationProperties()
489+
.get(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG).toString();
490+
producerServers = removeLeadingAndTrailingBrackets(producerServers);
489491
String adminServers = getAdminBootstrapAddress();
490492
if (!producerServers.equals(adminServers)) {
491493
Map<String, Object> props = new HashMap<>(this.kafkaAdmin.getConfigurationProperties());
@@ -509,16 +511,14 @@ else if (this.micrometerEnabled) {
509511
private String getAdminBootstrapAddress() {
510512
// Retrieve bootstrap servers from KafkaAdmin bootstrap supplier if available
511513
String adminServers = this.kafkaAdmin.getBootstrapServers();
512-
513514
// Fallback to configuration properties if bootstrap servers are not set
514515
if (adminServers == null) {
515516
adminServers = this.kafkaAdmin.getConfigurationProperties().getOrDefault(
516517
AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,
517518
""
518519
).toString();
519520
}
520-
521-
return adminServers;
521+
return removeLeadingAndTrailingBrackets(adminServers);
522522
}
523523

524524
@Nullable
@@ -1003,6 +1003,10 @@ public void destroy() {
10031003
}
10041004
}
10051005

1006+
private static String removeLeadingAndTrailingBrackets(String str) {
1007+
return StringUtils.trimTrailingCharacter(StringUtils.trimLeadingCharacter(str, '['), ']');
1008+
}
1009+
10061010
@SuppressWarnings("serial")
10071011
private static final class SkipAbortException extends RuntimeException {
10081012

spring-kafka/src/test/java/org/springframework/kafka/support/micrometer/ObservationTests.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import org.springframework.lang.Nullable;
6969
import org.springframework.test.annotation.DirtiesContext;
7070
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
71+
import org.springframework.util.StringUtils;
7172

7273
import io.micrometer.common.KeyValues;
7374
import io.micrometer.core.instrument.MeterRegistry;
@@ -230,7 +231,7 @@ public KeyValues getLowCardinalityKeyValues(KafkaRecordReceiverContext context)
230231
assertThatListenerHasTimerWithNameAndTags(meterRegistryAssert, OBSERVATION_TEST_2, "obs2", "obs2-0");
231232

232233
assertThat(admin.getConfigurationProperties())
233-
.containsEntry(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, broker.getBrokersAsString());
234+
.containsEntry(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, List.of(broker.getBrokersAsString()));
234235
// producer factory broker different to admin
235236
assertThatAdmin(template, admin, broker.getBrokersAsString() + "," + broker.getBrokersAsString(),
236237
"kafkaAdmin");
@@ -386,6 +387,14 @@ void observationErrorException(@Autowired ExceptionListener listener, @Autowired
386387
.hasMessage("obs5 error");
387388
}
388389

390+
@Test
391+
void kafkaAdminNotRecreatedIfBootstrapServersSameInProducerAndAdminConfig(
392+
@Autowired @Qualifier("reuseAdminBeanKafkaTemplate") KafkaTemplate<Integer, String> template,
393+
@Autowired KafkaAdmin kafkaAdmin) {
394+
// See this issue for more details: https://github.com/spring-projects/spring-kafka/issues/3466
395+
assertThat(template.getKafkaAdmin()).isSameAs(kafkaAdmin);
396+
}
397+
389398
@Configuration
390399
@EnableKafka
391400
public static class Config {
@@ -394,20 +403,30 @@ public static class Config {
394403

395404
@Bean
396405
KafkaAdmin admin(EmbeddedKafkaBroker broker) {
406+
String[] brokers = StringUtils.commaDelimitedListToStringArray(broker.getBrokersAsString());
407+
List<String> brokersAsList = Arrays.asList(brokers);
397408
KafkaAdmin admin = new KafkaAdmin(
398-
Map.of(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, broker.getBrokersAsString()));
409+
Map.of(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, brokersAsList));
399410
admin.setOperationTimeout(42);
400411
return admin;
401412
}
402413

403414
@Bean
415+
@Primary
404416
ProducerFactory<Integer, String> producerFactory(EmbeddedKafkaBroker broker) {
405417
Map<String, Object> producerProps = KafkaTestUtils.producerProps(broker);
406418
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, broker.getBrokersAsString() + ","
407419
+ broker.getBrokersAsString());
408420
return new DefaultKafkaProducerFactory<>(producerProps);
409421
}
410422

423+
@Bean
424+
ProducerFactory<Integer, String> customProducerFactory(EmbeddedKafkaBroker broker) {
425+
Map<String, Object> producerProps = KafkaTestUtils.producerProps(broker);
426+
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, broker.getBrokersAsString());
427+
return new DefaultKafkaProducerFactory<>(producerProps);
428+
}
429+
411430
@Bean
412431
ConsumerFactory<Integer, String> consumerFactory(EmbeddedKafkaBroker broker) {
413432
Map<String, Object> consumerProps = KafkaTestUtils.consumerProps("obs", "false", broker);
@@ -439,6 +458,14 @@ KafkaTemplate<Integer, String> throwableTemplate(ProducerFactory<Integer, String
439458
return template;
440459
}
441460

461+
@Bean
462+
KafkaTemplate<Integer, String> reuseAdminBeanKafkaTemplate(
463+
@Qualifier("customProducerFactory") ProducerFactory<Integer, String> pf) {
464+
KafkaTemplate<Integer, String> template = new KafkaTemplate<>(pf);
465+
template.setObservationEnabled(true);
466+
return template;
467+
}
468+
442469
@Bean
443470
ConcurrentKafkaListenerContainerFactory<Integer, String> kafkaListenerContainerFactory(
444471
ConsumerFactory<Integer, String> cf) {

0 commit comments

Comments
 (0)