You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

是否需要依赖注入实现消息处理器的自动注册?

问题:自动注册消息处理器是否必须依赖DI?

我有一个RegisterMessageHandlers方法,用于按消息名称注册消息处理器。现在想通过反射自动发现并注册所有处理器——给每个消息标注MessageAttribute,通过反射获取消息名称。但问题是,用Activator实例化处理器时,必须手动传入所有构造函数依赖项。

我目前的解决方案是:把所有依赖注册到DI容器,将IServiceProvider传入MainManagerClass的构造函数,再用ActivatorUtilities.CreateInstance反射实例化每个发现的处理器。但我知道DI和服务定位器有区别,服务定位器是反模式,搞不清二者的界限,所以想问:实现这个需求必须用DI吗?

原始代码:

public class MainManagerClass
{
    private Dictionary<string, MessageHandler> _handlers;

    private void RegisterMessageHandlers()
    {
        _messageHandlers["msg1"] = new MessageHandler1(new Service1());
        _messageHandlers["msg2"] = new MessageHandler2(new Service1(), new Service2());
    }
}
   
public class MessageHandler1 : MessageHandler<Message1>
{
    public MessageHandler1(IService1 service){}
}

public class MessageHandler2 : MessageHandler<Message2>
{
   public MessageHandler2(IService1 service1, IService2 service2){}
}

public abstract class MessageHandler<T> : MessageHandler
{
}

public abstract class MessageHandler
{
}

[Message("msg1")]
public class Message1
{
}

更新后的实现代码:

public class MainManagerClass
{
    private Dictionary<string, MessageHandler> _handlers;
    private readonly IServiceProvider _serviceProvider;


    public MainManagerClass(IServiceProvider serviceProvider)
    {
        _handlers = new Dictionary<string, MessageHandler>();
        RegisterMessageHandlers();
    }

    private void RegisterMessageHandlers()
    {         
        var messageHandlers = Assembly.GetCallingAssembly()
            .GetTypes()
            .Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(MessageHandler))).ToList();
        foreach (var handler in messageHandlers)
        {
            var messageType = handler.BaseType.GenericTypeArguments[0];
            var msgAttribute = messageType.CustomAttributes
                     .Where(t => t.AttributeType == typeof(MessageAttribute))
                     .FirstOrDefault();
            if (msgAttribute == null)
                throw new Exception($"Message name not defined for message type {messageType.Name}");

            var msgName = msgAttribute.ConstructorArguments[0].Value.ToString();

            _handlers[msgName] = ActivatorUtilities.CreateInstance(_serviceProvider, handler) as MessageHandler;
        }
    }
}

internal class Program
{      
    static async Task Main(string[] args)
    {      
        var host = CreateHostBuilder().Build();
        var manager = new MainManagerClass(host.Services);
        ...
    }

    private static IHostBuilder CreateHostBuilder()
    {
        return Host.CreateDefaultBuilder()
           .ConfigureServices((_, services) =>
           {
               services
                   .AddSingleton<IService1, Service1>()
                   .AddSingleton<IService2, Service2>()
                   .AddSingleton<IService3, Service3>()
                   ;
           });
    }
}

回答

你的需求不是必须依赖DI,但DI是当前场景下最合理、可维护的方案,而且你当前的实现并没有陷入服务定位器反模式,属于DI的合理使用场景。

先明确DI和服务定位器的核心区别

  • DI(依赖注入):类只声明自己需要什么依赖,由外部容器主动注入,类本身不关心依赖的来源,符合依赖倒置原则,降低耦合。
  • 服务定位器:类主动从容器中索取依赖,导致类与容器强耦合,难以测试,且违背依赖倒置,这才是被诟病的反模式。

你的实现为什么不算服务定位器反模式

你只是在应用初始化阶段(注册处理器时)使用IServiceProvider实例化处理器,这属于DI容器的基础设施代码范畴——服务定位器反模式的核心是在业务逻辑中随处调用容器获取依赖,而你并没有在业务处理流程中依赖容器,只是一次性完成注册工作,完全没问题。

不用DI的替代方案(不推荐)

确实存在不用DI的方式,但会带来严重的维护问题:

  • 手动实例化依赖:像你最初的代码那样,直接new所有依赖,但一旦依赖层级变深(比如Service1依赖Service4,Service4又依赖Service5),手动实例化会变成“依赖地狱”,代码扩展性为零。
  • 自己实现简易依赖管理:本质上就是造一个简易DI容器,重复造轮子,稳定性、扩展性都远不如成熟的DI框架。

优化你的当前实现

可以进一步优化,让代码更符合DI规范:

  1. 将MainManagerClass注册到DI容器
    不要手动new MainManagerClass(host.Services),而是把它也纳入DI管理:

    // 在ConfigureServices中添加
    services.AddSingleton<MainManagerClass>();
    
    // 在Main中从容器获取
    var manager = host.Services.GetRequiredService<MainManagerClass>();
    
  2. 自动注册所有处理器到DI,直接注入集合
    可以用DI框架的批量注册功能,把所有MessageHandler实现注册到容器,然后在MainManagerClass构造函数中直接注入IEnumerable<MessageHandler>,省去手动实例化的步骤:

    // 在ConfigureServices中批量注册处理器
    services.Scan(scan => scan
        .FromCallingAssembly()
        .AddClasses(classes => classes.AssignableTo<MessageHandler>())
        .AsSelf()
        .WithSingletonLifetime());
    
    // 修改MainManagerClass
    public class MainManagerClass
    {
        private Dictionary<string, MessageHandler> _handlers;
    
        public MainManagerClass(IEnumerable<MessageHandler> handlers)
        {
            _handlers = new Dictionary<string, MessageHandler>();
            foreach (var handler in handlers)
            {
                var messageType = handler.GetType().BaseType.GenericTypeArguments[0];
                var msgAttribute = messageType.GetCustomAttribute<MessageAttribute>();
                if (msgAttribute == null)
                    throw new Exception($"Message name not defined for message type {messageType.Name}");
                var msgName = msgAttribute.ConstructorArguments[0].Value.ToString();
                _handlers[msgName] = handler;
            }
        }
    }
    

总结

  • 实现需求不是必须用DI,但不用DI会带来大量维护成本,得不偿失。
  • 你当前的实现不属于服务定位器反模式,是DI的合理使用。
  • 进一步优化后,代码会更简洁、符合DI规范,扩展性和可维护性更强。

内容的提问来源于stack exchange,提问作者theateist

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 02:30:59