使用AddAzureClients与ClientFactory实现Azure ServiceBus依赖注入是否优于单例模式?
Azure ServiceBus 两种服务实现方案对比
我有两种可正常运行的Web应用与Azure ServiceBus交互的服务类实现方案,想对比二者的优势,以及哪种更符合最佳实践。以下是两种方案的代码示例:
方案一:标准单例服务类
这种方案在类内部实例化并释放ServiceBus Client和Sender,启动时通过.NET DI注册为单例。
启动代码
services.AddSingleton<IAIMarkingMessageService>(s => new AIMarkingMessageServiceSingleton(aiMarkingSettings.ServiceBusNamespace, aiMarkingSettings.InitQueueName, aiMarkingSettings.FeedbackQueueName, siteSettings.DefaultManagedIdentityClientId));
服务类代码(简化版)
public class AIMarkingMessageServiceSingleton : IAIMarkingMessageService, IAsyncDisposable { readonly ServiceBusClient _client; readonly ServiceBusSender _initSender; readonly ServiceBusSender _feedbackSender; public AIMarkingMessageServiceSingleton(string serviceBusNameSpace, string initQueueName, string feedbackQueueName, string userAssignedClientId) { var clientOptions = new ServiceBusClientOptions { TransportType = ServiceBusTransportType.AmqpWebSockets }; _client = new ServiceBusClient($"{serviceBusNameSpace}.servicebus.windows.net", new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = userAssignedClientId }), clientOptions); _initSender = _client.CreateSender(initQueueName); _feedbackSender = _client.CreateSender(feedbackQueueName); } public async Task<string> SendInitSubmissionAsync(long taskId, string requestUID) { ... } public async Task<List<long>> SendInitSubmissionBatchAsync(IEnumerable<(long taskId, string requestUID)> tasksToInit) { ... } public async ValueTask DisposeAsync() { await _client.DisposeAsync(); // 此调用会同时释放由该客户端创建的发送者和接收者 GC.SuppressFinalize(this); } }
方案二:使用AddAzureClients DI方法
这种方案在启动代码中配置ServiceBus Client和Sender,服务类通过注入IAzureClientFactory触发Sender的延迟实例化,服务类可按需注册为作用域或单例,是微软最新文档推荐的实现方式。
启动代码
services.AddAzureClients(clientBuilder => { // 注册客户端 clientBuilder.AddServiceBusClientWithNamespace($"{aiMarkingSettings.ServiceBusNamespace}.servicebus.windows.net") .ConfigureOptions(x => new ServiceBusClientOptions { TransportType = ServiceBusTransportType.AmqpTcp }); clientBuilder.UseCredential(new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = siteSettings.DefaultManagedIdentityClientId })); // 注册子客户端(队列发送者) // 初始化发送者 clientBuilder.AddClient<ServiceBusSender, ServiceBusClientOptions>( (_, _, provider) => provider.GetService<ServiceBusClient>() .CreateSender(aiMarkingSettings.InitQueueName)).WithName(aiMarkingSettings.InitQueueName); // 反馈发送者 clientBuilder.AddClient<ServiceBusSender, ServiceBusClientOptions>( (_, _, provider) => provider.GetService<ServiceBusClient>() .CreateSender(aiMarkingSettings.FeedbackQueueName)).WithName(aiMarkingSettings.FeedbackQueueName); }); // 可注册为作用域或单例 services.AddScoped<IAIMarkingMessageService, AIMarkingMessageService>();
服务类代码(简化版)
public class AIMarkingMessageService : IAIMarkingMessageService { readonly ServiceBusSender _initSender; readonly ServiceBusSender _feedbackSender; public AIMarkingMessageService(IAzureClientFactory<ServiceBusSender> senderFactory, IOptions<AIMarkingSettings> config ) { var _config = config.Value; _initSender = senderFactory.CreateClient(_config.InitQueueName); _feedbackSender = senderFactory.CreateClient(_config.FeedbackQueueName); } public async Task<string> SendInitSubmissionAsync(long taskId, string requestUID) { ... } public async Task<List<long>> SendInitSubmissionBatchAsync(IEnumerable<(long taskId, string requestUID)> tasksToInit) { ... } }
两种方案的优势对比
方案一(标准单例)的优势
- 封装性强:ServiceBus客户端的创建、配置、释放逻辑都封装在服务类内部,外部只需依赖
IAIMarkingMessageService接口,业务逻辑与ServiceBus实现细节解耦。 - 实现简单:无需引入额外的Azure ClientFactory依赖包,代码结构直观,上手快。
- 资源管控直接:自己掌控ServiceBusClient的生命周期,明确资源释放时机,适合对资源管控有明确需求的场景。
方案二(AddAzureClients)的优势
- 官方推荐:遵循微软最新的Azure SDK最佳实践,后续版本兼容性、官方支持更有保障。
- 配置集中:所有ServiceBus相关的配置都集中在启动代码中,修改配置无需改动服务类,便于统一管理。
- 延迟实例化:Sender在实际需要时才创建,避免应用启动时初始化不必要的资源,提升启动速度。
- 服务类灵活:服务类可自由注册为作用域或单例,依赖注入更符合.NET生态的规范,无需自己处理单例的异步释放逻辑(ClientFactory会自动管理客户端生命周期)。
- 资源复用高效:ServiceBusClient由ClientFactory统一管理,可被多个Sender/Receiver复用,避免重复创建客户端导致的连接资源浪费。
最佳实践建议
第二种方案(AddAzureClients)更符合当前的最佳实践。它不仅遵循官方推荐的实现方式,还在配置管理、资源复用、依赖注入灵活性上有明显优势,后续维护和扩展也更方便。
如果现有第一种方案运行稳定且没有维护痛点,也可以继续使用;但新开发的项目或需要重构的场景,建议优先采用第二种方案。
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

