• C#通过反射方法实现依赖注入


            看了很多依赖注入的插件,有时候一直在想,是不是都需要定义一个容器来绑定依赖注入的动态库,难道就不能按需注入?我这里的诉求其实很简单,希望注入的实体,在项目中没有任何一个地方是需要强引用的。

            这里以切换关系数据库为例子。我在开发中定义,不允许在数据库中写任何编程性的代码,例如视图、存储过程、方法等,这样做也是为了迁移数据库做准备的,这个是大前提,否则,我接下来举例就没有意义了。我认为,数据库就是提供高速存储服务就足够了。

            在dotnet中,很多orm都支持多种数据库的支持,也都支持大部分的关系数据库,例如Oracle,Mssql,Mysql,Postgresql,Sqlite等,我把不同的数据库操作封装成不同的数据库访问层基类,例如 cyb.DAL.SQLServer,cyb.DAL.Oracle,....类似这样,这些实体都实现接口cyb.IDAL

    1. //mssql的数据库访问实体
    2. public abstract class BaseDAL<T> : IBaseDAL<T>
    3. where T:class
    4. {
    5. //orm数据库访问实体
    6. protected mssqlContext dbContext {get;private set;}
    7. }
    8. 接口
    9. public interface IBaseDAL<T>:IBaseDAL
    10. where T:class
    11. {
    12. }

    所有的DAL基类都实现接口IBaseDAL,那么这里就已经定义好了DAL的规范,包括增删改查等等,再往下派生,定义一个员工表

            namespace cyb.IDAL.Basic

            public interface IDALEmployee : IBaseDAL,IDALEmployee

            {

            }

            namespace cyb.DAL.Basic

            public class DALEmployee : BaseDAL,IDALEmployee

            {

            }

    到此,类DALEmployee和接口IDALEmployee已经产生了关系,且在整个系统中,只存在这么一种关系,DALEmployee是接口IDALEmployee唯一实现实体类。

    为了加快搜索注入的程序集,我把程序集cyb.DAL.Basic放到目录 bin\DAL中。

    在系统中定义一个IOCHelper工具类,此了要完成两个事情,第一,搜索接口的实现类;第二,缓存完成匹配的实现类;

    public static createObject(string path="");

    参数path:是搜索实现类的位置,例如DAL或者其他什么,这个没有关系了,就是方便搜索,留空是在整个appdomain中搜索,就是第一次的时候会稍微慢一点点,毕竟搜索的程序集会比较多。

    在工具类内部定义一个字典容器 Dictionary,其中key是接口,value是实体类。

    方法体也简单,第一步就是找文件,当然是找dll文件,如果你写的程序集后缀不是dll,那么自己改改就好了,这里不贴出代码,其实代码挺多的,后边慢慢分析。大致思路如下:
     

    1. //iType是接口类型
    2. if(cache.TryGetValue(iType,out Type impType)
    3. {
    4. var imp = Activator.CreateInstance(typ);
    5. return imp;
    6. }
    7. var dir = new DirectoryInfo(这里是搜索的目录);
    8. var fis = dir.GetFiles("*.dll");
    9. foreach(var fi in fis)
    10. {
    11. //这里一定要加错误陷进,有时候,搜索到的dll文件不一定是dotnet的程序集,或者导出类时候是有问题的。
    12. try
    13. {
    14. var asm = Assembly.Load(fi.FullName);
    15. //这里就把实现类找出来了
    16. var types = asm.GetExportedTypes().Where(t => iType.IsAssignableFrom(t) && !t.IsInterface && t.IsAbstract == false).ToList();
    17. if(types.Count == 0)
    18. {
    19. //这里报个错,没找到
    20. return null;
    21. }
    22. if(types.Count > 1)
    23. {
    24. //这里找到多个实现类,也报个错
    25. return null;
    26. }
    27. cache[iType]=types[0];
    28. var imp = Activator.CreateInstance(typ);
    29. return imp;
    30. }
    31. catch(Exception ex)
    32. {
    33. logHelper.Error(ex);
    34. }
    35. }

    其实代码可以改进一下,先从AppDomain中找已经加载的程序集,没找到再去找dll文件,细节就不提了,这里只是举例而已。

    关键一步来了,看java实现注入的写法,挺爽的,于是也就有样学样了,做一个标注。在业务层对象BLLEmployee : IBLLEmployee中定义:

            [Autowire]

            protected IDALEmployee dal;

            创建这个业务层的时候,同时要调用一下createObject("");方法,这个方法怎么调用,也很讲究。其实可以在业务层的基类中发起,为什么要定义成protected?因为基类通过反射找Field是找不到private的,这里也可以优化的,但是涉及到比较复杂的框架设计了,为了这个歌定义讲这么多划不来,呵呵。

            好嘛,控制层(Controler)创建的时候,就创建业务层,那么一层层往下创建,也就能一层层注入了。

            最难的就是调用注入方法createObject(""),我的框架封装好了这些麻烦事,程序员是不需要操心了,只是操碎了我的心。

            在dotnet core中,这种方法好像行不通,好吧,那就网上查看一下微软的文档,找到一个很好的办法,在加载注入dll时候,把依赖的文件也加载进来

            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);

            其中 MyResolveEventHandler方法就是处理加载依赖的事件方法,里边怎么写网上也有代码,抄一下改一下就好了,我是改了很多的。

            另外为了知道注入过程中出错的时候到底发生了什么,要在关键代码处输出最详细的日志,例如加载了哪个dll文件,导出类的时候是什么问题导致失败,创建实体对象的时候是什么原因出现异常等等,总之多写日志就是有好处的,简化了后续的调试,我遇到最多的就是找不到依赖文件,其次就是写实体类的时候,构造函数、属性、成员的初始化有问题,通过记录日志就非常方便的发现问题并能快速解决。一切问题都解决后,日志也就没有了。

            最后,不要尝试对注入的容器做持久化,其实没有什么意义,在没有加载程序集的时候,实现类是不可能呢出现的,持久化重新加载的时候必然报错,不要为了那么一点点时间而干难堪的事情。

            我也用样的方法尝试按常规来做依赖注入,就是使用网上常说的几种方法,都会有些问题,所以我就放弃了,另外,这样的注入方法自己写的,支持Framework和core,在不同架构下使用相同方法,对于程序员来说,没有任何不适。

            在我的框架中,使用反射的机会还是很高的,但是同学们也要自己注意,毕竟IO操作还是有风险的,特别是linux下,要放开权限给程序,这里是相当于一个安全漏洞了,目前我还没更好的方法解决。动态加载文件,也很可能被恶意注入程序,所以,在使用的时候也要确保服务器的安全,我认为那是网管的事情,做一回甩锅侠。

            好了,大致也就这样了,框架的应用就是想要程序员开发代码的时候,不要太过于关注技术问题,把精力都放到两个方面,一个是业务实现,一个是和产品经理斗智斗勇。如果看过我前边写的文章就知道了,我把redis,mq,内部通讯,分布式事务,内存缓存等都封装成了组件。

            我还把与其他微服务交互的操作都封装成了cyb.DAL.Api程序集,当然是针对自己框架的情况下使用,因为这里封装了分布式事务的关键几个操作,程序员就象写一般DAL一样来操作WebApi,查询也是使用lambda。

            谢谢大家关注我的框架设计和开发之旅!

  • 相关阅读:
    【Audio音频开发】音频基础知识及PCM技术详解
    C++ XML文件和解析
    JVM 面试必问的 CMS,你懂了吗?
    Python:实现logistic regression逻辑回归算法(附完整源码)
    MySQL学习笔记5
    使用eolink优雅地进行API接口管理
    2022年,对于跨境独立站,选择shopify,magento,fecify,fecmall,工具对比评测
    规则引擎QLExpress表达式计算数学公式
    计算机毕业设计Java精品在线试题库系统(源码+mysql数据库+系统+lw文档)
    深度学习之tf.keras.preprocessing.image_dataset_from_directory()函数
  • 原文地址:https://blog.csdn.net/kaka9/article/details/132624434