在应用程序启动时注册所有实现接口的类 (Web API)

Posted

技术标签:

【中文标题】在应用程序启动时注册所有实现接口的类 (Web API)【英文标题】:Registering All Classes That Implement Interface At Application Start (Web API) 【发布时间】:2014-11-20 13:37:14 【问题描述】:

更新:

基本上,这归结为“我如何强制类库在 Web API 站点的应用程序启动时加载,以便我可以通过它们进行一次反映并确保我获得某个类的所有实现。或者,如果没有这样做的好方法,让该库中的类注册自己的最佳方法是什么?

原问题:

我正在尝试在我的 Web API 中注册在应用程序启动时实现某个接口的所有类,并将它们放在一个列表中,以便以后可以找到它们,而无需在每次调用时通过程序集进行反映。

这似乎相当简单,虽然我以前从未这样做过。因此,经过一番谷歌搜索和阅读其他一些 Stack Overflow 问题后,我创建了一个容器类并放置了一个方法来注册所有具体实现。简化的解决方案如下所示:

public static void RegisterAllBots()
        
            var type = typeof(IRobot);
            var types = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(s => s.GetTypes())
                .Where(type.IsAssignableFrom);
            foreach (var t in types)
            
                TypeRepo.Add(t.Name.ToLower(), t);
            
        

然后我将我的 RegisterAllBots() 放入 Global.asax 的 Application_Start() 中。

问题是,有时(但不总是)如果我开始“冷”调试解决方案,它不会找到实现,只有接口本身。如果我在运行之前转到 Build -> Rebuild Solution,它会找到它们。所以我假设这是 Visual Studio 启动 WebHost 项目而不重建其他类库项目的问题。

所以我在这里有几个问题。

    我对这里的原因是否正确? 我该如何阻止这种情况? 截至目前,这可以在生产中发生吗?我已经将它部署到了一个测试站点,它似乎可以工作,但这可能只是运气,因为有时它确实找到了具体的实现。

【问题讨论】:

【参考方案1】:

AppDomain.CurrentDomain.GetAssemblies() 将仅返回已加载到当前应用程序域中的程序集,当使用该程序集的类型时加载程序集。

过去每当我需要这样做时,我都只是维护了一个程序集列表,即

var assemblies = new [] 

    typeof(TypeFromAssemblyA).Assembly,
    typeof(TypeFromAssemblyB).Assembly
;

这只需要包含来自每个程序集的一种类型,以确保程序集被加载到当前应用程序域中。

另一个选项是强制使用Assembly.GetReferencedAssemblies()Assembly.Load(AssemblyName) 加载所有引用的程序集。有关此方法的更多详细信息,请参阅this question。在此之后,您可以使用现有代码,因为所有程序集都将加载到应用程序域中。

第三种选择是使用目录中程序集的 Managed Extensibility Framework (MEF) 到 Export 以及随后的 Import 类型。

您选择哪个选项取决于您的解决方案结构以及您希望如何发现实现。我喜欢第一种方法,因为它明确说明将扫描哪些程序集,但最后一种方法无需重新编译即可实现更大的可扩展性。

【讨论】:

【参考方案2】:

这是我通常在实用程序类中使用的一种方法。

    public static List<Type> GetTypes<T>()
    
        var results = new List<Type>();
        var assemblies = AppDomain.CurrentDomain.GetAssemblies();
        foreach (var assembly in assemblies)
        
            var types = assembly.GetTypes()
                .Where(t => t.IsAbstract == false
                    && (typeof(T).IsInterface == false || t.GetInterfaces().Contains(typeof(T)))
                    && (typeof(T).IsClass == false || t.BaseType == typeof(T)))
                .ToList();
            results.AddRange(types);
        
        return results;
    

【讨论】:

什么能让这段代码在我的不能工作的地方工作?除了过滤掉接口等(我在实践中确实这样做,顺便说一下,我只是展示一个简化版本)之外,我没有看到它做任何特别不同的事情。真的,我不确定我的三个问题中的任何一个都能被这个回答。 AppDomain.CurrentDomain 并不总是加载外部类库,直到您从类库中引用一个对象。您可以在调用此类方法之前手动加载它们,也可以将代码放入类库中。我希望您的问题会在生产中发生,但是一旦有人从类库中命中代码,强制该库加载到当前域中,它就会开始工作。【参考方案3】:

首先,你不能从every类库中获得一个类的所有实现,类实现总是有可能只存在于dll中存储在桌面下方的拇指驱动器上。因此,您必须确定要查找的那些实现的来源。 您的实现以当前的AppDomain 作为源,因此寻找已经由运行时加载的实现。我猜你正在寻找那些没有加载的实现。如果是这样,您必须将自己限制在一组目录(或从外部配置加载该组描述)以查找包含实现的库(Environment.CurrentDirectory 可能就足够了)。你可以有两种情况

你现在了解每个实现

例如,您可以在数据库或配置文件中存储程序集名称和完整类型标识符的列表。如果是这样,您可以尝试定位这些特定程序集并通过反射 API 加载所需的实现。但是,错误信息存在问题:您可以拥有列表中未提及的实现,或者,您可以在列表中获得有关实现的信息,但运行时不存在。此外,您可以拥有引用每个实现的辅助 dll,因此当您加载它时 - 您也会将每个实现加载到 AppDomain 中(从这一点开始,您可以使用您的初始解决方案)

你现在不了解每个实现

在这种情况下,只有一种方法。对于源目录中的每个 dll:

尝试将程序集从该 dll 加载到当前 AppDomain 获取所有类型,在程序集中定义,看看是否有预期的实现 如果有,请将其装入所需的容器中。如果不是 - 卸载程序集,否则您将有巨大的内存开销而没有任何好处。

如果您可以对实现实施额外的代码支持(这是您自己的代码,或者您对第三方实现者有严格的指导方针),您可以使用Management Extensibility Framework 为您处理此任务:您必须通过以下方式标记每个实现Export 属性,定义组合目录以查找包含实现的库,并启动组合以将每个实现加载到由ImportMany 属性标记的组合容器。否则,您将不得不自己准确地执行这些操作,但要注意有问题的情况:

程序集可能无法加载 - 因此请注意异常 程序集可以引用另一个程序集,因此在加载它时,加载引用的程序集是好的。检查实现时很容易错过这些程序集

即使在第一种情况下,我个人也更喜欢使用第二种方式,因为我不喜欢将对孩子的隐式依赖引入父母的想法。

【讨论】:

【参考方案4】:

如果尚未加载程序集,您可以使用 Assembly.LoadAssembly 在运行时加载程序集。很简单的例子:

        List<Assembly> loadedAssemblies = new List<Assembly>();
        DirectoryInfo di = new DirectoryInfo("path to my assemblies");

        foreach (FileInfo fi in di.GetFiles("*.dll"))
        
            try
            
                loadedAssemblies.Add(Assembly.LoadFrom(fi.FullName));
            
            catch (Exception ex)
            
                // handle problems loading the assemblies here - there are a boatload of possible failures
            
        

        foreach (Assembly a in loadedAssemblies)
        
            // Use reflection to do whatever it is that you wanted to do
        

MSDN 上提供了其他文档:http://msdn.microsoft.com/en-us/library/1009fa28%28v=vs.110%29.aspx

【讨论】:

以上是关于在应用程序启动时注册所有实现接口的类 (Web API)的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 启动预加载数据 CommandLineRunner

使用@import导入实现了ImportBeanDefinitionRegistrar接口的类,不能被注册为bean

Spring BootSpring Boot之使用ImportBeanDefinitionRegistrar类实现动态注册Bean

ImportBeanDefinitionRegistrar怎么注入Bean

lua 类实现

如何让spring mvc web应用启动时就执行特定处理