让IoC动态解析自定义配置(提供基于Unity的实现)

简介:

在《通过自定义配置实现插件式设计》中,通过在运行时对配置的动态解析实现了真正的“插件式”设计,其本质就是让配置自行提供对配置类型实例的创建。在这篇文章中,我们将更进一步,让自定义配置和IoC集成起来。IoC的目的就是通过解析注册的依赖注入信息,最终创建出我们希望的某个对象。而只有通过配置的方式来定义IoC容器需要的注入信息,才能实现灵活的设计。所以,如果将两者集成起来,让IoC容器能够解析通过配置定义的“依赖注入”信息,具有很大的现实意义。接下来,我们将通过Unity为例,介绍IoC和自定义进行无缝集成的实现方案。例子源代码可以从这里下载(可以直接运行)

目录
一、如果IoC能够解析自定义配置定义的“注入”…
二、整个配置结构的定义
三、配置项如何提供“类型注册”信息
四、如何为UnityContainer进行“类型注册”

一、如果IoC能够解析自定义配置定义的“注入”

我们先来看看如果一个IoC容器能够解析通过自定义配置定义的注入信息,会为我们的设计和编程带来怎样的改变。在这里我采用的是微软Unity这种轻量级的IoC容器,并沿用《.NET的资源并不限于.resx文件,你可以采用任意存储形式》中介绍的应用场景——基于多种存储方式的资源管理框架。现在只关注与资源的读取,我们将基于不同存储形式的资源读取操作实现在相应的ResourceProovider中,它们实现如下一个简单的IResourceProvider接口。

   1: public interface IResourceProvider
   2: {
   3:     object GetObject(string key);
   4: }

然后我们创建两个具体的ResourceProvider:DbResourceProvider和XmlResourceProvider,它们分别基于数据库表和XML文件的资源存储形式。DbResourceProvider需要连接数据库,需要引用配置的连接字符串,所以有一个ConnectionStringName属性;而XmlResourceProvider需要访问具体的XML文件,FileName属性表示文件路径。

   1: [ConfigurationElementType(typeof(DbResourceProviderData))]
   2: public class DbResourceProvider : IResourceProvider
   3: {
   4:     public string ConnectionStringName { get; private set; }
   5:     public DbResourceProvider(string connectionStringName)
   6:     {
   7:         this.ConnectionStringName = connectionStringName;
   8:     }
   9:     public object GetObject(string key)
  10:     {
  11:         throw new NotImplementedException();
  12:     }
  13:     public override string ToString()
  14:     {
  15:         return string.Format("DbResourceProvider (ConnectionStringName = {0})", this.ConnectionStringName);
  16:     }
  17: }
  18:  
  19: [ConfigurationElementType(typeof(XmlResourceProviderData))]
  20: ublic class XmlResourceProvider : IResourceProvider
  21:  
  22:    public string FileName { get; private set; }
  23:    public XmlResourceProvider(string fileName)
  24:    {
  25:        this.FileName = fileName;
  26:    }
  27:    public object GetObject(string key)
  28:    {
  29:        throw new NotImplementedException();
  30:    }
  31:    public override string ToString()
  32:    {
  33:        return string.Format("XmlResourceProvider (FileName = {0})", this.FileName);
  34:    }
  35: }

具体使用哪个ResourceProvider,通过配置来决定。整个配置定义在<artech.resources>配置节中,该配置节具有一个<providers>子节点,它定义了一系列ResourceProvider的列表。每个ResourceProvider配置具有两个相同的属性:Name和Type,以及一些自己专属的配置属性(比如DbResourceProvider的connectionStringName,XmlResourceProvider的fileName)。至于默认采用哪个Provider,则通过配置节的defaultProvider属性来决定。在本例中,我们默认采用的是dbProvider。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:   <configSections>
   4:     <section name="artech.resources" type="Artech.ResourceManagement.Configuration.ResourceSettings, Artech.ResourceManagement"/>
   5:   </configSections>
   6:   <artech.resources defaultProvider="dbProvider">
   7:     <providers>
   8:       <add name="xmlProvider" type="Artech.ResourceManagement.XmlResourceProvider, Artech.ResourceManagement" fileName="C:\resources.xml"/>
   9:       <add name="dbProvider" type="Artech.ResourceManagement.DbResourceProvider, Artech.ResourceManagement" connectionStringName="localSqlSvr"/>
  10:     </providers>
  11:   </artech.resources>
  12: </configuration>

现在我们通过创建的UnityContainer来创建相应的ResourceProvider。第一个Resolve<IResourceProvider>方法没有指定任何参数,表明创建的是配置的默认ResourceProvider(即DbProvider),后面两个通过指定ResourceProvider的配置名称(xmlProvider和dbProvider)来创建相应的ResourceProvider。

   1: static void Main(string[] args)
   2: {
   3:     IUnityContainer container = new UnityContainer();
   4:     ResourceSettings.ConfigureContainer(container, ConfigurationSourceFactory.Create());
   5:     Console.WriteLine(container.Resolve<IResourceProvider>());
   6:     Console.WriteLine(container.Resolve<IResourceProvider>("xmlProvider"));
   7:     Console.WriteLine(container.Resolve<IResourceProvider>("dbProvider"));
   8: }

执行结果:

   1: DbResourceProvider (ConnectionStringName = localSqlSvr)
   2: XmlResourceProvider (FileName = C:\resources.xml)
   3: DbResourceProvider (ConnectionStringName = localSqlSvr)

二、整个配置结构的定义

接下来,我们先来看看上面给出的那段配置是如何定义的。我们整个配置定义在如下一个ResourceSettings类型中。ResourceSettings继承自SerializableConfigurationSection,该类型定义在EnterLib(实际上本文介绍的配置与IoC继承的方案来自于EnterLib)中,并实现了接口ITypeRegistrationsProvider。对接口ITypeRegistrationsProvider的实现很重要,我们放在下面一节进行单独介绍。

   1: public class ResourceSettings : SerializableConfigurationSection, ITypeRegistrationsProvider
   2: {
   3:     public const string ConfigSectionName = "artech.resources";
   4:  
   5:     [ConfigurationProperty("defaultProvider", IsRequired = true)]
   6:     public string DefaultProvider
   7:     { 
   8:         get { return (string)this["defaultProvider"]; } 
   9:     }
  10:  
  11:     [ConfigurationProperty("providers", IsRequired = true)]
  12:     public NameTypeConfigurationElementCollection<ResourceProviderDataBase, ResourceProviderDataBase> Providers
  13:     {
  14:         get { return (NameTypeConfigurationElementCollection<ResourceProviderDataBase, ResourceProviderDataBase>)this["providers"]; }
  15:     } 
  16:  
  17:     public IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource)
  18:     {
  19:        //省略...
  20:     }
  21:  
  22:     public IEnumerable<TypeRegistration> GetUpdatedRegistrations(IConfigurationSource configurationSource)
  23:     {
  24:         return this.GetRegistrations(configurationSource);
  25:     }
  26:  
  27:     public static void ConfigureContainer(IUnityContainer container, IConfigurationSource configurationSource)
  28:     {
  29:         //省略...
  30:     }
  31: }

配置属性DefaultProvider表示默认的ResourceProvider配置名称,而Providers则表示配置的ResourceProvider列表,这是一个NameTypeConfigurationElementCollection<ResourceProviderDataBase, ResourceProviderDataBase>类型的集合。该集合的每一个元素类型为ResourceProviderDataBase,它表示所有ResourceProvider配置类型的基类。ResourceProviderDataBase定义如下,它继承自NameTypeConfigurationElement类型(该类型定义在EnterLib配置系统中)。ResourceProviderDataBase定义的两个需方法用于类型注册之用,我们在下面一节进行单独介绍。

   1: public class ResourceProviderDataBase : NameTypeConfigurationElement
   2: {
   3:     protected virtual Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource)
   4:     {
   5:         throw new NotImplementedException();
   6:     }
   7:     public virtual IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource)
   8:     {
   9:         //省略...
  10:     }
  11: }

具体的DbResourceProvider和XmlResourceProvider继承自ResourceProviderDataBase。它们除了定义自己对应的配置属性(connectionStringName和fileName),还需要重写GetCreationExpression这个虚方法。

   1: public class DbResourceProviderData : ResourceProviderDataBase
   2: {
   3:     [ConfigurationProperty("connectionStringName", IsRequired = true)]
   4:     public string ConnectionStringName
   5:     {
   6:         get { return (string)this["connectionStringName"]; }
   7:     }
   8:  
   9:     protected override Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource)
  10:     {
  11:         //省略...
  12:     }
  13: }
  14:  
  15: public class XmlResourceProviderData : ResourceProviderDataBase
  16: {
  17:     [ConfigurationProperty("fileName", IsRequired = true)]
  18:     public string FileName
  19:     {
  20:         get { return (string)this["fileName"]; }
  21:     }
  22:     protected override Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource)
  23:     {
  24:         //省略...
  25:     }
  26: }

三、配置项如何提供“类型注册”信息?

所有类型的IoC容器的作用无外乎通过解析注册的各种依赖注入(构造器注入、属性注入和方法注入)通过基类或者接口创建和初始化某个具体类型的实例。Unity可以通过一个特殊的类型来表示依赖注入 信息:TypeRegistration。TypeRegistration定义如下,由于篇幅所限,在这里就不多作介绍了。

   1: public class TypeRegistration
   2: {
   3:     public TypeRegistration(LambdaExpression expression);
   4:     public TypeRegistration(LambdaExpression expression, Type serviceType);
   5:  
   6:     public IEnumerable<ParameterValue> ConstructorParameters { get; }
   7:     public Type ImplementationType { get; }
   8:     public IEnumerable<InjectedProperty> InjectedProperties { get; }
   9:     public bool IsDefault { get; set; }
  10:     public bool IsPublicName { get; set; }
  11:     public LambdaExpression LambdaExpression { get; }
  12:     public TypeRegistrationLifetime Lifetime { get; set; }
  13:     public string Name { get; set; }
  14:     public NewExpression NewExpressionBody { get; }
  15:     public Type ServiceType { get; }
  16:  
  17:     public static string DefaultName<TServiceType>();
  18:     public static string DefaultName(Type serviceType);
  19: }

现在我们的目的是让UnityContainer来解析定义在ResourceSettings这个配置类型中的“注入信息”,那么就需要ResourceSettings对象能够提供它一个完备的TypeRegistration列表,这些列表帮助UnityContainer进行正确的实例创建或者获取决策。现在我们就来介绍ResourceSettings如果为UnityContainer提供类型注册信息的,现在我们将关注点放在上面给出的代码中的省略部分。先来看看ResourceProviderDataBase的定义。

   1: public class ResourceProviderDataBase : NameTypeConfigurationElement
   2: {
   3:     protected virtual Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource)
   4:     {
   5:         throw new NotImplementedException();
   6:     }
   7:     public virtual IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource)
   8:     {
   9:         Expression<Func<IResourceProvider>> creationExpression = GetCreationExpression(configurationSource);
  10:         TypeRegistration providerTypeRegistration = new TypeRegistration<IResourceProvider>(creationExpression)
  11:         {
  12:             Name         = this.Name,
  13:             IsPublicName = true,
  14:             Lifetime     = TypeRegistrationLifetime.Singleton
  15:         };
  16:         yield return providerTypeRegistration;
  17:     }
  18: }

需方法GetRegistrationsIEnumerable<TypeRegistration>类型的方式获取所有基于该配置元素的类型注册信息,这里提供默认的实现——基于具体ResourceProvider类型的类型注册。在这里我们通过一个Expression<Func<IResourceProvider>>对象作为参数创建TypeRegistration对象,而传入的表达式用于具体ResourceProvider的创建。该Expression<Func<IResourceProvider>>对象通过需方法GetCreationExpression方法获取,所有具体的子类需要重写这个方法。(P.S. 为了避免对ResourceProvider的频繁创建,我通过Lifetime将其设置成Singleton形式)

对于继承自ResourceProviderDataBase的DbResourceProviderData和XmlResourceProviderData重写了GetCreationExpression方法,实现了对DbResourceProvider和XmlResourceProvider的创建。

   1: public class DbResourceProviderData : ResourceProviderDataBase
   2: {
   3:      //Others
   4:     protected override Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource)
   5:     {
   6:         return () => new DbResourceProvider(this.ConnectionStringName);
   7:     }
   8: }
   9:  
  10: public class XmlResourceProviderData : ResourceProviderDataBase
  11: {
  12:     //Others
  13:     protected override Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource)
  14:     {
  15:         return () => new XmlResourceProvider(this.FileName);
  16:     }
  17: }

我们说ResourceSettings实现了对类型注册信息的提供,具体体现在对接口ITypeRegistrationsProvider的实现。该接口定义两个方法GetRegistrations和GetUpdatedRegistrations,两个方法返回的TypeRegistration列表最终用于对UnityContainer的配置,而后者会在配置被改动后被调用。

   1: public interface ITypeRegistrationsProvider
   2: {
   3:     IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource);
   4:     IEnumerable<TypeRegistration> GetUpdatedRegistrations(IConfigurationSource configurationSource);
   5: }

现在ResourceSettings采用如下的方式提供了对ITypeRegistrationsProvider的实现。获取所有基于ResourceProvider的TypeRegistration,如果和配置的默认ResourceProvider名称相同,则将IsDefault设置为true(那么创建的时候就无需指定类型注册名称)。

   1: public class ResourceSettings : SerializableConfigurationSection, ITypeRegistrationsProvider
   2: {
   3:     //Others...
   4:     public IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource)
   5:     {
   6:         var registrations = this.Providers.SelectMany<ResourceProviderDataBase,TypeRegistration>(provider=>provider.GetRegistrations(configurationSource));
   7:         foreach (var registration in registrations)
   8:         {
   9:             if (registration.Name == this.DefaultProvider)
  10:             {
  11:                 registration.IsDefault = true;
  12:             }
  13:             yield return registration;
  14:         }
  15:     }
  16:  
  17:     public IEnumerable<TypeRegistration> GetUpdatedRegistrations(IConfigurationSource configurationSource)
  18:     {
  19:         return this.GetRegistrations(configurationSource);
  20:     }
  21: }

四、如何为UnityContainer进行“类型注册”

最后,我们在看看最开始我们给出的程序,你当然知道了这其中的机关在于对ResourceSettings静态方法CongureContainer的调用。

   1: static void Main(string[] args)
   2: {
   3:     IUnityContainer container = new UnityContainer();
   4:     ResourceSettings.ConfigureContainer(container, ConfigurationSourceFactory.Create());
   5:     Console.WriteLine(container.Resolve<IResourceProvider>());
   6:     Console.WriteLine(container.Resolve<IResourceProvider>("xmlProvider"));
   7:     Console.WriteLine(container.Resolve<IResourceProvider>("dbProvider"));
   8: }

该方法定义如下,这其中涉及到两个重要的对象UnityContainerConfiguratorConfigSectionLocator。ConfigSectionLocator会根据指定配置节名称得到配置节对象,如果配置节类型实现了ITypeRegistrationsProvider接口,会调用GetRegistrations得到所需的TypeRegistration列表,最终借助于UnityContainerConfigurator对具体的UnityContiainer进行类型注册。

   1: public class ResourceSettings : SerializableConfigurationSection, ITypeRegistrationsProvider
   2: {
   3:      //Others...
   4:     public static void ConfigureContainer(IUnityContainer container, IConfigurationSource configurationSource)
   5:     {
   6:         UnityContainerConfigurator configurator = new UnityContainerConfigurator(container);
   7:         configurator.RegisterAll(configurationSource, new ConfigSectionLocator(ConfigSectionName));
   8:     }
   9: }

调用过ResourceSettings静态方法CongureContainer方法后,UnitiyContainer就具有了DbResourceProvider和XmlResourceProvider的类型注册信息,我们就能通过Resolve方法创建它们。


作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
844 0
|
7月前
|
存储 缓存 网络协议
阿里云特惠云服务器99元与199元配置与性能和适用场景解析:高性价比之选
2025年,阿里云长效特惠活动继续推出两款极具吸引力的特惠云服务器套餐:99元1年的经济型e实例2核2G云服务器和199元1年的通用算力型u1实例2核4G云服务器。这两款云服务器不仅价格亲民,而且性能稳定可靠,为入门级用户和普通企业级用户提供了理想的选择。本文将对这两款云服务器进行深度剖析,包括配置介绍、实例规格、使用场景、性能表现以及购买策略等方面,帮助用户更好地了解这两款云服务器,以供参考和选择。
|
5月前
|
域名解析 应用服务中间件 Shell
使用nps配置内网穿透加域名解析
使用nps配置内网穿透加域名解析
705 76
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
830 2
|
7月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
7月前
|
Web App开发 移动开发 前端开发
React音频播放器样式自定义全解析:从入门到避坑指南
在React中使用HTML5原生&lt;audio&gt;标签时,开发者常面临视觉一致性缺失、样式定制局限和交互体验割裂等问题。通过隐藏原生控件并构建自定义UI层,可以实现完全可控的播放器视觉风格,避免状态不同步等典型问题。结合事件监听、进度条拖拽、浏览器兼容性处理及性能优化技巧,可构建高性能、可维护的音频组件,满足跨平台需求。建议优先使用成熟音频库(如react-player),仅在深度定制需求时采用原生方案。
267 12
|
8月前
|
域名解析 网络协议 Ubuntu
DHCP与DNS的配置
通过这些步骤,您可以在Linux环境下成功配置和验证DHCP和DNS服务。希望这些内容对您的学习和工作有所帮助。
719 27
|
8月前
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
971 12
|
8月前
|
存储 人工智能 并行计算
2025年阿里云弹性裸金属服务器架构解析与资源配置方案
🚀 核心特性与技术创新:提供100%物理机性能输出,支持NVIDIA A100/V100 GPU直通,无虚拟化层损耗。网络与存储优化,400万PPS吞吐量,ESSD云盘IOPS达100万,RDMA延迟<5μs。全球部署覆盖华北、华东、华南及海外节点,支持跨地域负载均衡。典型应用场景包括AI训练、科学计算等,支持分布式训练和并行计算框架。弹性裸金属服务器+OSS存储+高速网络综合部署,满足高性能计算需求。
|
10月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能

推荐镜像

更多
  • DNS