.NET Core实现轻量级Ioc容器
一、目的
一是正在开发的通用Api快速开发框架需要一个Ioc容器模块
二是正好借机继续深入学习.netcore
二、背景
事实上,网络上有很多现成能提供我们所需功能的产品;比方说autofac,功能相当强大,但同时也比较臃肿,而我们希望的是一款契合我们框架的简单易用、轻量级的产品;并且,我们也希望在实现这个产品的时候更深入的学习.netcore。
三、功能需求
1. 可以指定程序集搜索路径,默认是当前进程执行路径(已实现)
2. 可以根据类全名称创建一个实例,并且可根据传入的不同构造参数,调用不同的构造方法(已实现)
3. 可以根据配置文件的id创建一个实例(已实现)
4. 可以对生成的实例设置指定属性值(部分实现,简单类型属性)
5. 可以对生成的实例自动调用指定方法(尚未实现)
四、技术实现
1. 设置程序集搜索路径
这个比较简单,只是将用户设置的搜索路径存储起来以备后用,直接上代码
/// <summary> /// 添加程序集搜索位置 /// </summary> public static void AddSearchPath(string path) { AssemblyUtils.AddSearchPath(path); } /// <summary> /// 移除程序集搜索位置 /// </summary> public static void RemoveSearchPath(string path) { AssemblyUtils.RemoveSearchPath(path); }
private static List<string> sSearchPath = new List<string>(); private static object sSearchPathLock = new object(); public static void AddSearchPath(string path) { lock (sSearchPathLock) { sSearchPath.Add(path.ToLower()); } } public static void RemoveSearchPath(string path) { lock (sSearchPathLock) { sSearchPath.Remove(path.ToLower()); } }
2. 根据类全名和参数创建对象实例
/// <summary> /// 创建实例 /// </summary> public static object GetObject(string classFullName, params object[] args) { return AssemblyUtils.CreateInstance(classFullName, args); } private static ConcurrentDictionary<string, object> sSingleInstances = new ConcurrentDictionary<string, object>(); private static object sSingleLock = new object(); /// <summary> /// 创建单例实例 /// </summary> public static object GetSingleObject(string classFullName, params object[] args) { object result = null; if (!sSingleInstances.TryGetValue(classFullName.ToLower(), out result)) { lock (sSingleLock) { if (!sSingleInstances.TryGetValue(classFullName.ToLower(), out result)) { result = AssemblyUtils.CreateInstance(classFullName, args); if (result != null) { sSingleInstances.AddOrUpdate(classFullName.ToLower(), result, (oldKey, oldValue) => { return result; }); } } } } return result; }
用户使用IocUtils方法,即可创建指定对象的实例或者单例实例,具体逻辑在AssemblyUtils.CreateInstance中,下面继续看代码
/// <summary> /// 创建对象实例 /// </summary> /// <param name="classFullName"></param> 对象全名称 /// <param name="args"></param> 构造参数 /// <returns></returns> 创建成功的实例 public static object CreateInstance(string classFullName, object[] args = null) { object result = null; Assembly assembly = GetAssemblyByClassFullName(classFullName); //在当前AppDomain已加载程序集中搜索classFullName指定名称的程序集 if (assembly == null) { assembly = FindAssemblyInSearchPath(classFullName); //遍历用户指定的搜索位置,查找匹配classFullName的程序集 } if (assembly != null) { //调用程序集的CreateInstance方法创建对象实例,根据是否传入构造参数区别调用 if (args == null || args.Length == 0) { result = assembly.CreateInstance(classFullName, true); } else { result = assembly.CreateInstance(classFullName, true, BindingFlags.CreateInstance, null, args, null, null); } } return result; } /// <summary> /// 在当前AppDomain已加载程序集中搜索classFullName指定名称的程序集 /// </summary> /// <param name="classFullName"></param> /// <returns></returns> private static Assembly GetAssemblyByClassFullName(string classFullName) { Assembly result = null; if (!sTypeAssemblies.TryGetValue(classFullName.ToLower(), out result)) { Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < loadedAssemblies.Length; i++) { Assembly currAssembly = loadedAssemblies[i]; if (HasType(currAssembly, classFullName)) { result = currAssembly; sTypeAssemblies.AddOrUpdate(classFullName.ToLower(), result, (oldKey, oldValue) => { return result; }); break; } } } return result; } /// <summary> /// 遍历用户指定的搜索位置,查找匹配classFullName的程序集 /// </summary> /// <param name="classFullName"></param> /// <returns></returns> private static Assembly FindAssemblyInSearchPath(string classFullName) { List<string> searchDirs = new List<string>(); searchDirs.Add(AppDomain.CurrentDomain.BaseDirectory); lock (sSearchPathLock) { searchDirs.AddRange(sSearchPath); } bool loadedAssemblyObj; foreach (string dir in searchDirs) { IEnumerable<string> dllFiles = Directory.EnumerateFiles(dir, "*.dll", SearchOption.AllDirectories); IEnumerator<string> fileIterator = dllFiles.GetEnumerator(); while (fileIterator.MoveNext()) { string filename = fileIterator.Current.ToLower(); if (!sLoadedAssemblies.TryGetValue(filename, out loadedAssemblyObj)) { Assembly assembly = Assembly.LoadFrom(filename); sLoadedAssemblies.TryAdd(filename, true); if (HasType(assembly, classFullName)) { return assembly; } } } } return null; }
代码注释已经清楚描述了具体逻辑,简单来说就是首先在当前域已加载程序集列表中查找指定类名称的程序集,如果未找到,继续在用户指定的搜索位置进行遍历,加载所有dll文件,进行匹配,找到对应的程序集后,调用程序集方法创建实例并返回,否则的话,直接返回null。
3. 加载对象配置文件
通过配置文件,用户可以直接选择创建对象的构造函数,设置构造函数的参数,初始化属性值等等,配置文件采用XML格式,具体文件格式如下:
<?xml version="1.0" encoding="utf-8" ?> <objects> <object id="object1" class="TestLibrary.Person"> <constructor-arg>"wangxm"</constructor-arg> <property name="name">王小明</property> </object> </objects>
上面的配置定义了一个Id为object1的对象,采用一个字符串参数的构造参数,参数值为wangxm,同时设置实例的name属性属性为王小明。
constructor-arg支持所有基本类型,以及List、Array等类型。
property目前只支持简单类型。
具体实现逻辑和写法请下载源码,查看测试案例。
4.根据配置文件Id创建对象实例
/// <summary> /// 根据配置文件创建对象实例 /// </summary> /// <param name="objectId"></param> 配置文件Id /// <returns></returns> public static object GetObjectById(string objectId) { IocConfig.ObjectConfig item = IocConfig.GetObjectConfig(objectId); //根据配置文件Id获取配置信息 AssertObjectConfig(item, objectId); object[] cps = BuildConstructorArgs(item); //根据配置信息生成构造参数数组 object result = GetObject(item.Class, cps); //直接调用GteObject方法创建对象实例 SetProperties(item, result); //根据配置的属性信息对实例属性进行设置 return result; }
到这里,我们已经可以通过IocUtils.GetObject、IocUtils.GetObjectById创建我们需要的对象实例,既可以简单实用对象名称,也可以通过配置文件直接初始化更复杂的对象。
另外,IocUtils中还提供了GetSingleObject、GetSingleObjectById创建单例实例,GetObject<T>、GetObjectById<T>创建指定类型的对象实例返回。
五、后续
目前代码中配置文件还未支持方法调用,性能如何也未作测试,有兴趣的同学可以试下性能如何。
项目中加载配置文件逻辑较复杂,用到了CodeM.Common.Tools包,可以在我的github中找到。
github源码,欢迎start