Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 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
8 changes: 8 additions & 0 deletions sdk/spring/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ This section includes changes in `spring-cloud-azure-autoconfigure` module.

- Upgrade to Jackson 3 to align with Spring Boot 4 ([#49538](https://github.com/Azure/azure-sdk-for-java/issues/49538)).

### Spring Cloud Azure Service

This section includes changes in `spring-cloud-azure-service` module.

#### Bugs Fixed

- Fixed the Service Bus producer, consumer and processor sub-client builders overwriting configuration already set on the underlying `ServiceBusClientBuilder` through an `AzureServiceClientBuilderCustomizer<ServiceBusClientBuilder>` (for example the `ClientOptions` carrying `TracingOptions`). The customizers are now applied to the underlying builder as the last step, so their configuration is preserved ([#49742](https://github.com/Azure/azure-sdk-for-java/issues/49742)).

### Spring Messaging Azure

This section includes changes in `spring-messaging-azure` module.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ public class AzureMessagingListenerAutoConfiguration {
@Configuration
@ConditionalOnBean(EventHubsProcessorFactory.class)
static class EventHubsConfiguration {
@Bean(name = "azureEventHubsListenerContainerFactory")
@ConditionalOnMissingBean(name = "azureEventHubsListenerContainerFactory")
@Bean(
name = EventHubsListenerAnnotationBeanPostProcessor.DEFAULT_EVENT_HUBS_LISTENER_CONTAINER_FACTORY_BEAN_NAME)
@ConditionalOnMissingBean(
name = EventHubsListenerAnnotationBeanPostProcessor.DEFAULT_EVENT_HUBS_LISTENER_CONTAINER_FACTORY_BEAN_NAME)
public MessageListenerContainerFactory<? extends MessageListenerContainer> azureEventHubsListenerContainerFactory(
EventHubsProcessorFactory eventHubsProcessorFactory,
ObjectProvider<AzureMessageConverter<EventData, EventData>> messageConverterProvider) {
Expand All @@ -73,8 +75,10 @@ static class EnableEventHubsConfiguration {
@Configuration
@ConditionalOnBean(ServiceBusProcessorFactory.class)
static class ServiceBusConfiguration {
@Bean(name = "azureServiceBusListenerContainerFactory")
@ConditionalOnMissingBean(name = "azureServiceBusListenerContainerFactory")
@Bean(
name = ServiceBusListenerAnnotationBeanPostProcessor.DEFAULT_SERVICE_BUS_LISTENER_CONTAINER_FACTORY_BEAN_NAME)
@ConditionalOnMissingBean(
name = ServiceBusListenerAnnotationBeanPostProcessor.DEFAULT_SERVICE_BUS_LISTENER_CONTAINER_FACTORY_BEAN_NAME)
public MessageListenerContainerFactory<? extends MessageListenerContainer> azureServiceBusListenerContainerFactory(
ServiceBusProcessorFactory serviceBusProcessorFactory,
ObjectProvider<AzureMessageConverter<ServiceBusReceivedMessage, ServiceBusMessage>> messageConverterProvider) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ abstract class AbstractServiceBusSubClientBuilderFactory<T, P extends ServiceBus
private ServiceBusClientBuilder serviceBusClientBuilder;
private final boolean shareServiceBusClientBuilder;
private ServiceBusClientBuilderFactory serviceBusClientBuilderFactory;
private final List<AzureServiceClientBuilderCustomizer<ServiceBusClientBuilder>> serviceBusClientBuilderCustomizers;

/**
* Create a {@link AbstractServiceBusSubClientBuilderFactory} instance with the properties and the collection of
Comment thread
moarychan marked this conversation as resolved.
* @{link ServiceBusClientBuilder} customizers.
* {@link AzureServiceClientBuilderCustomizer} customizers.
* @param properties the properties describing the service bus sub client, which could be a sender, a receiver or
* a processor.
* @param serviceClientBuilderCustomizers the collection of customizers for the service bus client builder.
Expand Down Expand Up @@ -70,11 +71,16 @@ protected AbstractServiceBusSubClientBuilderFactory(ServiceBusClientBuilder serv
this.serviceBusClientBuilder = serviceBusClientBuilder;
this.shareServiceBusClientBuilder = true;
this.serviceBusClientBuilderFactory = null;
// The shared ServiceBusClientBuilder is configured and customized by its own factory, so there are no
// customizers for this sub-client builder factory to re-apply.
this.serviceBusClientBuilderCustomizers = null;
} else {
this.serviceBusClientBuilderFactory = new ServiceBusClientBuilderFactory(properties);
if (serviceBusClientBuilderCustomizers != null) {
serviceBusClientBuilderCustomizers.forEach(this.serviceBusClientBuilderFactory::addBuilderCustomizer);
}
// Remember the customizers instead of adding them to the nested factory, so they are applied to the
// underlying ServiceBusClientBuilder as the last step of each build() call. This ensures they win over
// the property-derived configuration this factory redirects onto the shared builder (for example
// ClientOptions carrying TracingOptions). See #49742.
this.serviceBusClientBuilderCustomizers = serviceBusClientBuilderCustomizers;
Comment thread
moarychan marked this conversation as resolved.
// Don't build yet - defer until first use when ApplicationContext is available
this.serviceBusClientBuilder = null;
this.shareServiceBusClientBuilder = false;
Expand All @@ -90,6 +96,23 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
}
}

@Override
public T build() {
// super.build() creates the sub-client builder and, in the non-shared path, lazily builds and configures
// the underlying ServiceBusClientBuilder from the properties (including the Spring identifier).
// The customizers are deliberately not applied during that step.
T builder = super.build();
// Apply the ServiceBusClientBuilder customizers as the last step of this build() call,
// so they take precedence over the property-derived configuration redirected onto the underlying builder.
// See #49742.
if (!isShareServiceBusClientBuilder() && this.serviceBusClientBuilderCustomizers != null) {
ServiceBusClientBuilder parentServiceBusClientBuilder = getServiceBusClientBuilder();
this.serviceBusClientBuilderCustomizers.forEach(customizer ->
customizer.customize(parentServiceBusClientBuilder));
}
Comment thread
moarychan marked this conversation as resolved.
Comment thread
moarychan marked this conversation as resolved.
return builder;
}

@Override
protected void configureCredential(T builder) {
// skip to avoid overriding the parent builder's credentials.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@

/**
* Helper class to control the lifecycle of a {@link ServiceBusProcessorClient}.
* This class implements {@code SmartLifecycle} to auto start {@link ServiceBusProcessorClient} when the Spring Application Context starts,
* And stop the {@link ServiceBusProcessorClient} when the Spring Application Context stops.
* NOTE, there is not need to call {@link #start()} or {@link #stop()} explicitly, as the {@link ServiceBusProcessorClient} will be started and stopped automatically.
* And since the {@link ServiceBusProcessorClient} is a {@link AutoCloseable}, there is no need to call {@link ServiceBusProcessorClient#close()} explicitly.
* This class implements {@code SmartLifecycle} to automatically start {@link ServiceBusProcessorClient} when the
* Spring Application Context starts and stops it when the Spring Application Context stops.
* NOTE: There is no need to call {@link #start()} or {@link #stop()} explicitly, as the
* {@link ServiceBusProcessorClient} will be started and stopped automatically.
* Since {@link ServiceBusProcessorClient} is {@link AutoCloseable}, there is no need to call
* {@link ServiceBusProcessorClient#close()} explicitly.
*/
public class ServiceBusProcessorClientLifecycleManager implements SmartLifecycle {
private final Logger logger = LoggerFactory.getLogger(ServiceBusProcessorClientLifecycleManager.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@

package com.azure.spring.cloud.service.implementation.servicebus.factory;

import com.azure.core.util.ClientOptions;
import com.azure.messaging.servicebus.ServiceBusClientBuilder;
import com.azure.spring.cloud.core.customizer.AzureServiceClientBuilderCustomizer;
import com.azure.spring.cloud.service.implementation.servicebus.properties.ServiceBusSenderClientTestProperties;
import com.azure.spring.cloud.service.servicebus.properties.ServiceBusEntityType;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.InOrder;
import org.mockito.Mockito;

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

import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doReturn;
Expand Down Expand Up @@ -34,6 +42,29 @@ void queueConfigured() {
verify(builder, times(1)).queueName("test-queue");
}

@Test
void serviceBusClientBuilderCustomizerAppliedAsLastStep() {
ServiceBusSenderClientTestProperties properties = createMinimalServiceProperties();
properties.setShareServiceBusClientBuilder(false);

ServiceBusClientBuilder rootBuilder = mock(ServiceBusClientBuilder.class);
@SuppressWarnings("unchecked")
AzureServiceClientBuilderCustomizer<ServiceBusClientBuilder> customizer =
mock(AzureServiceClientBuilderCustomizer.class);

ServiceBusSenderClientBuilderFactory factory =
new CustomizerTestFactory(properties, Collections.singletonList(customizer), rootBuilder);

factory.build();

InOrder inOrder = Mockito.inOrder(rootBuilder, customizer);
inOrder.verify(rootBuilder, atLeast(1))
.clientOptions(ArgumentMatchers.any(ClientOptions.class));
inOrder.verify(customizer, times(1)).customize(rootBuilder);
inOrder.verify(rootBuilder, Mockito.never())
.clientOptions(ArgumentMatchers.any(ClientOptions.class));
}

@Override
Comment thread
moarychan marked this conversation as resolved.
protected ServiceBusSenderClientTestProperties createMinimalServiceProperties() {
ServiceBusSenderClientTestProperties properties = new ServiceBusSenderClientTestProperties();
Expand Down Expand Up @@ -100,4 +131,24 @@ protected ServiceBusClientBuilder getServiceBusClientBuilder() {
return this.serviceBusClientBuilder;
}
}

static class CustomizerTestFactory extends ServiceBusSenderClientBuilderFactory {
private final ServiceBusClientBuilder serviceBusClientBuilder;
CustomizerTestFactory(ServiceBusSenderClientTestProperties properties,
List<AzureServiceClientBuilderCustomizer<ServiceBusClientBuilder>> customizers,
ServiceBusClientBuilder serviceBusClientBuilder) {
super(properties, customizers);
this.serviceBusClientBuilder = serviceBusClientBuilder;
}

@Override
public ServiceBusClientBuilder.ServiceBusSenderClientBuilder createBuilderInstance() {
return mock(ServiceBusClientBuilder.ServiceBusSenderClientBuilder.class);
}

@Override
protected ServiceBusClientBuilder getServiceBusClientBuilder() {
return this.serviceBusClientBuilder;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* specified {@link #destination}. The {@link #containerFactory} identifies the
* {@link MessageListenerContainerFactory} to use to build
* the Azure listener container. If not set, a <em>default</em> container factory is
* assumed to be available with a bean name of {@code eventHubsListenerContainerFactory}
* assumed to be available with a bean name of {@code azureEventHubsListenerContainerFactory}
* unless an explicit default has been provided through configuration.
*
* <p>Processing of {@code @EventHubsListener} annotations is performed by registering a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
public class EventHubsListenerAnnotationBeanPostProcessor extends AzureListenerAnnotationBeanPostProcessorAdapter<EventHubsListener> {

public static final String DEFAULT_EVENT_HUBS_LISTENER_ANNOTATION_BPP_BEAN_NAME = "eventHubsListenerAnnotationBeanPostProcessor";
private static final String DEFAULT_EVENT_HUBS_LISTENER_CONTAINER_FACTORY_BEAN_NAME = "azureEventHubsListenerContainerFactory";
public static final String DEFAULT_EVENT_HUBS_LISTENER_CONTAINER_FACTORY_BEAN_NAME = "azureEventHubsListenerContainerFactory";

/**
* Construct an {@link EventHubsListenerAnnotationBeanPostProcessor} instance with default configuration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* specified {@link #destination}. The {@link #containerFactory} identifies the
* {@link MessageListenerContainerFactory} to use to build
* the Azure listener container. If not set, a <em>default</em> container factory is
* assumed to be available with a bean name of {@code serviceBusListenerContainerFactory}
* assumed to be available with a bean name of {@code azureServiceBusListenerContainerFactory}
* unless an explicit default has been provided through configuration.
*
* <p>Processing of {@code @ServiceBusListener} annotations is performed by registering a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
public class ServiceBusListenerAnnotationBeanPostProcessor extends AzureListenerAnnotationBeanPostProcessorAdapter<ServiceBusListener> {

public static final String DEFAULT_SERVICE_BUS_LISTENER_ANNOTATION_BPP_BEAN_NAME = "serviceBusListenerAnnotationBeanPostProcessor";
private static final String DEFAULT_SERVICE_BUS_LISTENER_CONTAINER_FACTORY_BEAN_NAME = "azureServiceBusListenerContainerFactory";
public static final String DEFAULT_SERVICE_BUS_LISTENER_CONTAINER_FACTORY_BEAN_NAME = "azureServiceBusListenerContainerFactory";

/**
* Construct an {@link ServiceBusListenerAnnotationBeanPostProcessor} instance with default configuration.
Expand All @@ -64,7 +64,7 @@ protected AzureListenerEndpoint createAndConfigureMethodListenerEndpoint(Service
BeanFactory beanFactory,
MessageHandlerMethodFactory messageHandlerMethodFactory) {
MethodServiceBusListenerEndpoint endpoint = new MethodServiceBusListenerEndpoint();

endpoint.setBean(bean);
endpoint.setMethod(method);
endpoint.setBeanFactory(beanFactory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import com.azure.spring.messaging.listener.MessageListenerContainer;

/**
* Model for a Azure listener endpoint. Can be used against a {@link AzureListenerConfigurer AzureListenerConfigurer} to
* register endpoints programmatically.
* Model for an Azure listener endpoint. Can be used against a {@link AzureListenerConfigurer AzureListenerConfigurer}
* to register endpoints programmatically.
*/
public interface AzureListenerEndpoint {

Expand Down
Loading