• MEF(可扩展框架)使用总结


    一般情况下,我们对系统要求:

    1.对扩展开发对修改关闭

    2.高层模块不应该依赖于低层模块,应该依赖于抽象

    实际上,这是遵循了面向对象的设计原则中的开放封闭原则和依赖倒置原则,其所做的事情就是为了提高系统的可扩展能力和对代码解耦。

    为了满足上述的要求,我们引用了微软的MEF框架,让他来帮助我们做这些事情。

    一、概念

      MEF (Managed Extensibility Framework) 使开发人员能够在其 .NET 应用程序中提供挂钩,以供用户和第三方扩展。可以将 MEF 看成一个通用应用程序扩展实用工具。

      MEF 使开发人员能够动态创建扩展,无需扩展应用程序,也不需要任何特定于扩展内容的知识。这可以确保在编译时两者之间不存在耦合,使应用程序能够在运行时扩展,不需要重新编译。MEF 还可以在将扩展加载到应用程序之前,查看扩展程序集的元数据,这是一种速度更快的方法。

    二、原理

      将被我们特殊标记的方法,属性,字段,类型,导入到一个容器中,我们再通过MEF内置的反射来使用容器中被我们导入的各种方法,属性,字段,类型。

    三、示例

    1.类型

    public interface IDBConn
    {
        void Conn();
    }
    
    [Export("SqlConn", typeof(IDBConn))]
     public class SqlConn : IDBConn
     {
        public void Conn()
        {
            Console.WriteLine("this is SqlServer connection");
        }
     }

      我们的数据库连接可能会是多种连接,如果我们直接写死为SqlServer的连接,显然不符合开放关闭原则,所以我们提供了一个数据库连接的接口IDBConn,然后用SqlServer的连接SqlConn ,去实现数据库连接接口。

      然后利用MEF中的Export将该类型导出至MEF的容器中。

      调用:

    public string Test()
    {
        // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
        var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
        //创建容器
        var Container = new CompositionContainer(assemblyCatalog);
        //执行组合
        Container.ComposeParts();
        //获得容器中的Export
        IDBConn _conn = IOCContainer.Container.GetExportedValue<IDBConn>("SqlConn");
        _conn.Conn();
        return "";
    }    

      这是我们的一种方式,当我们的导出到容器中的类过于庞大的时候,我们可以换一种方式来获得导出。

      MEF提供Lazy加载,Lazy的好处在于,我们可以先声明,到了需要使用的时候才会实际去加载,代码如下:

    public string TestLazy()
            {
                // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
                var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
                //创建容器
                var Container = new CompositionContainer(assemblyCatalog);
                //执行组合
                Container.ComposeParts();
                //lazy
                var export =Container.GetExport<IDBConn>("SqlConn");
                IDBConn _conn = export.Value;
                _conn.Conn();
                return "";
            }

      到此,我们类型说的就差不多了,但是似乎有一点小问题,如果我需要根据某些特定的东西来判断加载哪个类型怎么办?

      首先,我们先看下Export的定义:

            //
            // 摘要:
            //     通过在指定协定名称下导出指定类型,初始化 System.ComponentModel.Composition.ExportAttribute 类的新实例。
            //
            // 参数:
            //   contractName:
            //     用于导出使用此特性标记的类型或成员的协定名称,或 null 或空字符串 ("") 以使用默认协定名称。
            //
            //   contractType:
            //     要导出的类型。
            public ExportAttribute(string contractName, Type contractType);   

      使用contractName可以满足这个需求,但是MEF提供了更加优雅的方式----元组。

      代码如下:

    public interface IDBConnMetadata
        {
            string Version { get; }
        }
    
        [Export("SqlConn", typeof(IDBConn))]
        [ExportMetadata("Version", "v1.0.1")]
        public class SqlConn : IDBConn
        {
            public void Conn()
            {
                Console.WriteLine("this is SqlServer connection");
            }
        }
    
    public string TestMetadata()
            {
                // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
                var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
                //创建容器
                var Container = new CompositionContainer(assemblyCatalog);
                //执行组合
                Container.ComposeParts();
    
                var export = Container.GetExports<IDBConn, IDBConnMetadata>();
                //筛选version为v1.0.1的
                IDBConn _conn = export.Where(p => p.Metadata.Version.Equals("v1.0.1")).FirstOrDefault().Value;
    
                _conn.Conn();
                return "";
            }

      到此类型就完全ok了,接下来看一下方法。

    2.方法

      方法需要对匿名委托需要有一些了解,不懂的,可以自行查一下,很简单。

    public class MySqlConn : IDBConn
        {
            private MySqlConn()
            {
    
            }
    
            [Export("Conn", typeof(Action))]
            public void Conn()
            {
                Console.WriteLine("this is MySqlConn connection");
            }
    
            [Export("GetStr", typeof(Func<string>))]
            private string GetStr()
            { return "随便试试字符串"; }
    
            [Export("GetLength", typeof(Func<string, int>))]
            public int GetLength(string str)
            {
                return str.Length;
            }
    
        }

      等一下,上面代码是不是有问题啊?构造方法是私有的,也没有用单例,这怎么调用啊?

      首先这又不是静态方法,不能用类名.方法名的方式调用,并且不能实例化,这样岂不是很蒙圈。

      那么我们试着用MEF写一下调用,代码如下:

    public string TestAction()
            {
                TestActionNoPara();
                TestActionWithPara();
                TestActionWithParaAndReturn();
    
                return "";
            }
    
    
            #region  Import Action
    
            #region Import 无参无返回方法
            private void TestActionNoPara()
            {
                // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
                var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
                //创建容器
                var Container = new CompositionContainer(assemblyCatalog);
                //执行组合
                Container.ComposeParts();
                var action = Container.GetExport<Action>("Conn");
                action.Value.Invoke();
            }
            #endregion
    
            #region Import 无参有返回方法
            private void TestActionWithPara()
            {
                // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
                var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
                //创建容器
                var Container = new CompositionContainer(assemblyCatalog);
                //执行组合
                Container.ComposeParts();
                var action = Container.GetExport<Func<string>>("GetStr");
                string message = action.Value.Invoke();
            }
            #endregion
    
            #region Import 有参有返回方法
            private void TestActionWithParaAndReturn()
            {
                // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
                var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
                //创建容器
                var Container = new CompositionContainer(assemblyCatalog);
                //执行组合
                Container.ComposeParts();
                var action = Container.GetExport<Func<string, int>>("GetLength");
    
                int length = action.Value.Invoke("GetLength");
            }
            #endregion
    
            #endregion

      完全可以访问,并没有因为私有构造方法而造成不能访问,并且上面的GetStr方法也是私有的,依旧可以访问。

      这就恶心了,方法的访问修饰符完全被MEF破坏了。。。

      感觉这应该算一个bug吧,不过MEF提供了一个属性,可以让方法或者类不能被使用[PartNotDiscoverable]。

    3.属性和字段

      研究了好久也不知道这个属性和字段应该应用到哪里,有什么实际的应用场景,不过,还是写下这个怎么用。

    [Export("SqlConn", typeof(IDBConn))]
    public
    class SqlConn : IDBConn { [Export("Description")] public string Description { get { return "this is SqlServer connection"; } } public void Conn() { Console.WriteLine("this is SqlServer connection"); } }
    public string TestAttribute()
            {
                // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
                var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
                //创建容器
                var Container = new CompositionContainer(assemblyCatalog);
                //执行组合
                Container.ComposeParts();
    
                var desc = Container.GetExportedValue<string>("Description");
    
                return desc;
            }
    
      //字段
      [Import("SqlConn")]
      private IDBConn dbConn;

    4.构造方法注入

      我们也可以用构造方法的方式完成注入,代码如下:

    #region 构造注入
        [Export("TestB")]
        public class TestB
        {
            private IDBConn _conn;
            [ImportingConstructor]
            public TestB([Import("SqlConn")]IDBConn conn)
            {
                _conn = conn;
            }
    
            public bool IsInject()
            {
                if (_conn == null)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
        }
        #endregion
    
    public string TestInject()
            {
                // 创建一个程序集目录,用于从一个程序集获取所有的组件定义 
                var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
                //创建容器
                var Container = new CompositionContainer(assemblyCatalog);
                //执行组合
                Container.ComposeParts();
                var x = Container.GetExportedValue<TestB>("TestB");
                bool result = x.IsInject();
    
                return result.ToString();
            }

    四、改进

      上面就是MEF的一些基本使用方法,但是写了这么久发现了似乎有一点问题啊。

      每次调用都去创建目录,创建容器,执行组合,似乎太繁琐了,而且重复这些操作,明显也会浪费资源,所以我们可以做一下改进,将这些重复步骤提炼出来一个单独的方法,供其他方法调用。

      

    public class IOCContainer
        {
            public static CompositionContainer Container { get; private set; }
    
            private static IOCContainer instance = new IOCContainer();
    
            private IOCContainer()
            {
                if (Container == null)
                {
                    //AssemblyCatalog:表示从程序集中搜索部件的目录
                    // 创建一个程序集目录,用于从一个程序集获取所有的组件定义
                    var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    
                    //指定加载某个程序集
                    //var directoryCatalog = new DirectoryCatalog("要加载dll的路径");
                    //AggregateCatalog:聚合目录,可以添加上面所说的所有目录,从而进行多方面的部件搜索
                    //var aggregateCatalog = new AggregateCatalog(assemblyCatalog, directoryCatalog);
    
                    //创建容器
                    Container = new CompositionContainer(assemblyCatalog);
                    //container = new CompositionContainer(aggregateCatalog);
                    //container.GetExportedValue
    
                    //执行组合
                    Container.ComposeParts();
                }
            }
    
            public static IOCContainer RegisterContainer()
            {
                return instance;
            }
    
        }

      写好了容器,我们可以在Global.asax中调用RegisterContainer方法,其他使用容器的地方,改为IOCContainer.Container.GetExport就ok了,既简化了代码,也节约了资源。

      到这里MEF的总结,就全部完成了,欢迎交流。

  • 相关阅读:
    构建之法:第二次心得
    构建之法:第一次心得
    tomcat配置限制ip和建立图片服务器
    tomcat8.5优化配置
    java 操作 csv文件
    jsoup教学系列
    (转)js实现倒计时效果(年月日时分秒)
    本地启动tomcat的时候报java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: PermGen space
    使用mybatis执行oracle存储过程
    java 获取web登录者的ip地址
  • 原文地址:https://www.cnblogs.com/Yuuuuu/p/9198017.html
Copyright © 2020-2023  润新知