Skip to content

Commit 3902767

Browse files
committed
Fix Event Hubs dedicated builder polluting shared section injection (#49245)
The root EventHubClientBuilder used a type-based @ConditionalOnMissingBean, so when either DedicatedConsumerConnectionConfiguration or DedicatedProducerConnectionConfiguration activated, its named-but-EventHubClientBuilder-typed bean suppressed the root builder. The opposite section's SharedConsumer/SharedProducerConnectionConfiguration then satisfied its @ConditionalOnBean(EventHubClientBuilder.class) against the dedicated bean and injected it via type, so the shared client targeted the wrong event hub. - Name the root builder springCloudAzureEventHubsClientBuilder and use @ConditionalOnMissingBean(name = ...) so dedicated builders no longer suppress it. - Switch SharedConsumer/SharedProducerConnectionConfiguration to @ConditionalOnBean(name = ...) and inject EventHubClientBuilder via @qualifier(EVENT_HUB_CLIENT_BUILDER_BEAN_NAME). - Add regression tests for asymmetric (one-side dedicated, one-side shared) configurations in both consumer and producer test classes.
1 parent 0699467 commit 3902767

7 files changed

Lines changed: 111 additions & 10 deletions

File tree

sdk/spring/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This section includes changes in `spring-cloud-azure-autoconfigure` module.
1313

1414
#### Bugs Fixed
1515

16+
- Fixed Event Hubs autoconfiguration where a dedicated `EventHubClientBuilder` registered by `consumer`-only or `producer`-only sub-level overrides (`connection-string` / `namespace` / `event-hub-name`) suppressed the root builder and got injected into the opposite shared section, causing the shared client to target the other section's event hub. The root builder is now registered under bean name `springCloudAzureEventHubsClientBuilder` with a name-based `@ConditionalOnMissingBean`, and the shared consumer/producer sections gate on and inject that specific bean via `@Qualifier`. ([#49245](https://github.com/Azure/azure-sdk-for-java/issues/49245))
1617
- Fixed JDBC/Azure Database and Redis passwordless connection scope defaulting using the wrong `azure.scopes` value for Azure China and Azure US Government when `spring.cloud.azure.profile.cloud-type` is set to `azure_china` or `azure_us_government`. The scopes are now correctly derived from the merged cloud type. ([#47096](https://github.com/Azure/azure-sdk-for-java/issues/47096))
1718

1819
### Spring Cloud Azure Stream Binder Service Bus

sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/context/AzureContextUtils.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ private AzureContextUtils() {
3434
public static final String EVENT_HUB_CLIENT_BUILDER_FACTORY_BEAN_NAME =
3535
"springCloudAzureEventHubsClientBuilderFactory";
3636

37+
/**
38+
* Event Hubs client builder bean name.
39+
*/
40+
public static final String EVENT_HUB_CLIENT_BUILDER_BEAN_NAME =
41+
"springCloudAzureEventHubsClientBuilder";
42+
3743
/**
3844
* Event Hubs consumer client builder factory bean name.
3945
*/

sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/eventhubs/AzureEventHubsClientBuilderConfiguration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.context.annotation.Configuration;
2323
import org.springframework.context.annotation.Import;
2424

25+
import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME;
2526
import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_FACTORY_BEAN_NAME;
2627

2728
/**
@@ -39,8 +40,8 @@ class AzureEventHubsClientBuilderConfiguration {
3940
this.eventHubsProperties = eventHubsProperties;
4041
}
4142

42-
@Bean
43-
@ConditionalOnMissingBean
43+
@Bean(EVENT_HUB_CLIENT_BUILDER_BEAN_NAME)
44+
@ConditionalOnMissingBean(name = EVENT_HUB_CLIENT_BUILDER_BEAN_NAME)
4445
EventHubClientBuilder eventHubClientBuilder(@Qualifier(EVENT_HUB_CLIENT_BUILDER_FACTORY_BEAN_NAME)
4546
EventHubClientBuilderFactory factory) {
4647
return factory.build();

sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/eventhubs/AzureEventHubsConsumerClientConfiguration.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.context.annotation.Configuration;
2525
import org.springframework.context.annotation.Import;
2626

27+
import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME;
2728
import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_CONSUMER_CLIENT_BUILDER_BEAN_NAME;
2829
import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_CONSUMER_CLIENT_BUILDER_FACTORY_BEAN_NAME;
2930

@@ -43,12 +44,13 @@
4344
class AzureEventHubsConsumerClientConfiguration {
4445

4546
@ConditionalOnMissingProperty(prefix = "spring.cloud.azure.eventhubs.consumer", name = { "connection-string", "namespace", "event-hub-name" })
46-
@ConditionalOnBean(EventHubClientBuilder.class)
47+
@ConditionalOnBean(name = EVENT_HUB_CLIENT_BUILDER_BEAN_NAME)
4748
@Configuration(proxyBeanMethods = false)
4849
static class SharedConsumerConnectionConfiguration {
4950

5051
private final EventHubClientBuilder builder;
51-
SharedConsumerConnectionConfiguration(AzureEventHubsProperties properties, EventHubClientBuilder builder) {
52+
SharedConsumerConnectionConfiguration(AzureEventHubsProperties properties,
53+
@Qualifier(EVENT_HUB_CLIENT_BUILDER_BEAN_NAME) EventHubClientBuilder builder) {
5254
this.builder = builder;
5355

5456
PropertyMapper mapper = PropertyMapper.get();
@@ -64,7 +66,7 @@ EventHubConsumerAsyncClient eventHubConsumerAsyncClient() {
6466

6567
@Bean
6668
@ConditionalOnMissingBean
67-
EventHubConsumerClient eventHubConsumerClient(EventHubClientBuilder builder) {
69+
EventHubConsumerClient eventHubConsumerClient() {
6870
return this.builder.buildConsumerClient();
6971
}
7072
}

sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/eventhubs/AzureEventHubsProducerClientConfiguration.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.springframework.context.annotation.Bean;
2222
import org.springframework.context.annotation.Configuration;
2323

24+
import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME;
2425
import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_PRODUCER_CLIENT_BUILDER_BEAN_NAME;
2526
import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_PRODUCER_CLIENT_BUILDER_FACTORY_BEAN_NAME;
2627

@@ -35,18 +36,18 @@
3536
class AzureEventHubsProducerClientConfiguration {
3637

3738
@ConditionalOnMissingProperty(prefix = "spring.cloud.azure.eventhubs.producer", name = { "connection-string", "namespace", "event-hub-name" })
38-
@ConditionalOnBean(EventHubClientBuilder.class)
39+
@ConditionalOnBean(name = EVENT_HUB_CLIENT_BUILDER_BEAN_NAME)
3940
@Configuration(proxyBeanMethods = false)
4041
static class SharedProducerConnectionConfiguration {
4142
@Bean
4243
@ConditionalOnMissingBean
43-
EventHubProducerAsyncClient eventHubProducerAsyncClient(EventHubClientBuilder builder) {
44+
EventHubProducerAsyncClient eventHubProducerAsyncClient(@Qualifier(EVENT_HUB_CLIENT_BUILDER_BEAN_NAME) EventHubClientBuilder builder) {
4445
return builder.buildAsyncProducerClient();
4546
}
4647

4748
@Bean
4849
@ConditionalOnMissingBean
49-
EventHubProducerClient eventHubProducerClient(EventHubClientBuilder builder) {
50+
EventHubProducerClient eventHubProducerClient(@Qualifier(EVENT_HUB_CLIENT_BUILDER_BEAN_NAME) EventHubClientBuilder builder) {
5051
return builder.buildProducerClient();
5152
}
5253
}

sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/eventhubs/AzureEventHubsConsumerClientConfigurationTests.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ void withGlobalEventHubConnectionSetShouldConfigureShared() {
6060
"spring.cloud.azure.eventhubs.consumer.consumer-group=" + consumerGroupName
6161
)
6262
.withUserConfiguration(AzureEventHubsPropertiesTestConfiguration.class)
63-
.withBean(EventHubClientBuilder.class, () -> clientBuilder)
63+
.withBean(AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME, EventHubClientBuilder.class, () -> clientBuilder)
6464
.run(
6565
context -> {
6666
assertThat(context).doesNotHaveBean(AzureEventHubsConsumerClientConfiguration.DedicatedConsumerConnectionConfiguration.class);
@@ -71,6 +71,51 @@ void withGlobalEventHubConnectionSetShouldConfigureShared() {
7171
);
7272
}
7373

74+
@Test
75+
void producerOnlyDedicatedOverrideShouldNotActivateSharedConsumer() {
76+
// Regression for issue #49245: producer dedicated builder must not satisfy the shared consumer condition,
77+
// nor be injected into the shared consumer path. Without sub-level overrides on the consumer side and
78+
// without the global event-hub-name, the consumer config simply should not activate.
79+
contextRunner
80+
.withPropertyValues(
81+
"spring.cloud.azure.eventhubs.namespace=test-namespace",
82+
"spring.cloud.azure.eventhubs.producer.event-hub-name=override-eventhub",
83+
"spring.cloud.azure.eventhubs.consumer.consumer-group=test-consumer-group"
84+
)
85+
.withBean(AzureGlobalProperties.class, AzureGlobalProperties::new)
86+
.withUserConfiguration(AzureEventHubsAutoConfiguration.class)
87+
.run(
88+
context -> {
89+
assertThat(context).doesNotHaveBean(AzureEventHubsConsumerClientConfiguration.class);
90+
assertThat(context).doesNotHaveBean(EventHubConsumerClient.class);
91+
assertThat(context).doesNotHaveBean(EventHubConsumerAsyncClient.class);
92+
}
93+
);
94+
}
95+
96+
@Test
97+
void sharedConsumerInjectsRootBuilderWhenProducerHasDedicatedOverride() {
98+
// Regression for issue #49245: when both a global event-hub-name and a producer-only override exist,
99+
// the shared consumer should still bind to the root builder, not the producer's dedicated builder.
100+
contextRunner
101+
.withPropertyValues(
102+
"spring.cloud.azure.eventhubs.namespace=test-namespace",
103+
"spring.cloud.azure.eventhubs.event-hub-name=base-eventhub",
104+
"spring.cloud.azure.eventhubs.consumer.consumer-group=test-consumer-group",
105+
"spring.cloud.azure.eventhubs.producer.event-hub-name=override-eventhub"
106+
)
107+
.withBean(AzureGlobalProperties.class, AzureGlobalProperties::new)
108+
.withUserConfiguration(AzureEventHubsAutoConfiguration.class)
109+
.run(
110+
context -> {
111+
assertThat(context).hasSingleBean(AzureEventHubsConsumerClientConfiguration.SharedConsumerConnectionConfiguration.class);
112+
assertThat(context).doesNotHaveBean(AzureEventHubsConsumerClientConfiguration.DedicatedConsumerConnectionConfiguration.class);
113+
assertThat(context).hasBean(AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME);
114+
assertThat(context).hasSingleBean(EventHubConsumerClient.class);
115+
}
116+
);
117+
}
118+
74119
@Test
75120
void withDedicatedEvenHubConnectionSetShouldConfigureDedicated() {
76121
contextRunner

sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/eventhubs/AzureEventHubsProducerClientConfigurationTests.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ void withGlobalEventHubConnectionSetShouldConfigureShared() {
5858
"spring.cloud.azure.eventhubs.event-hub-name=" + eventHubName
5959
)
6060
.withUserConfiguration(AzureEventHubsPropertiesTestConfiguration.class)
61-
.withBean(EventHubClientBuilder.class, () -> clientBuilder)
61+
.withBean(AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME, EventHubClientBuilder.class, () -> clientBuilder)
6262
.run(
6363
context -> {
6464
assertThat(context).doesNotHaveBean(AzureEventHubsProducerClientConfiguration.DedicatedProducerConnectionConfiguration.class);
@@ -69,6 +69,51 @@ void withGlobalEventHubConnectionSetShouldConfigureShared() {
6969
);
7070
}
7171

72+
@Test
73+
void consumerOnlyDedicatedOverrideShouldNotActivateSharedProducer() {
74+
// Regression for issue #49245: a consumer-dedicated builder must not satisfy the shared producer condition.
75+
// Without sub-level overrides on the producer side and without a global event-hub-name, the producer
76+
// config simply should not activate.
77+
contextRunner
78+
.withPropertyValues(
79+
"spring.cloud.azure.eventhubs.namespace=test-namespace",
80+
"spring.cloud.azure.eventhubs.consumer.event-hub-name=override-eventhub",
81+
"spring.cloud.azure.eventhubs.consumer.consumer-group=test-consumer-group"
82+
)
83+
.withBean(AzureGlobalProperties.class, AzureGlobalProperties::new)
84+
.withUserConfiguration(AzureEventHubsAutoConfiguration.class)
85+
.run(
86+
context -> {
87+
assertThat(context).doesNotHaveBean(AzureEventHubsProducerClientConfiguration.class);
88+
assertThat(context).doesNotHaveBean(EventHubProducerClient.class);
89+
assertThat(context).doesNotHaveBean(EventHubProducerAsyncClient.class);
90+
}
91+
);
92+
}
93+
94+
@Test
95+
void sharedProducerInjectsRootBuilderWhenConsumerHasDedicatedOverride() {
96+
// Regression for issue #49245: when both a global event-hub-name and a consumer-only override exist,
97+
// the shared producer should still bind to the root builder, not the consumer's dedicated builder.
98+
contextRunner
99+
.withPropertyValues(
100+
"spring.cloud.azure.eventhubs.namespace=test-namespace",
101+
"spring.cloud.azure.eventhubs.event-hub-name=base-eventhub",
102+
"spring.cloud.azure.eventhubs.consumer.event-hub-name=override-eventhub",
103+
"spring.cloud.azure.eventhubs.consumer.consumer-group=test-consumer-group"
104+
)
105+
.withBean(AzureGlobalProperties.class, AzureGlobalProperties::new)
106+
.withUserConfiguration(AzureEventHubsAutoConfiguration.class)
107+
.run(
108+
context -> {
109+
assertThat(context).hasSingleBean(AzureEventHubsProducerClientConfiguration.SharedProducerConnectionConfiguration.class);
110+
assertThat(context).doesNotHaveBean(AzureEventHubsProducerClientConfiguration.DedicatedProducerConnectionConfiguration.class);
111+
assertThat(context).hasBean(AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME);
112+
assertThat(context).hasSingleBean(EventHubProducerClient.class);
113+
}
114+
);
115+
}
116+
72117
@Test
73118
void withDedicatedEvenHubConnectionSetShouldConfigureDedicated() {
74119
contextRunner

0 commit comments

Comments
 (0)