一. 控制反转和依赖注入:
- 控制反转的前提, 是依赖倒置原则, 系统架构时,高层模块不应该依赖于低层模块,二者通过抽象来依赖 (依赖抽象,而不是细节)
- 如果要想做到控制反转(IOC), 就必须要使用依赖注入(DI), 也就是说控制反转是要实现的目的, 而依赖注入是达到这种目的的一种技术手段
- DI依赖注入, 在构造对象时,可以自动的去初始化当前需要构造的这个对象所需要的资源, 将其依赖的所有资源自动全部初始化, 有三种形式, 通过构造函数注入, 通过属性注入 通过方法注入(依赖注入的三种方式)
- 有了依赖注入,才可能做到无限层级的依赖抽象,才能做到控制反转
二. 一个简单的容器雏形, 部分代码示例:
a) 这里相当于UI, 可以看做是高层
1 //全部都依赖细节 2 AndroidPhone phone = new AndroidPhone(); //这边的细节类, 可以看做是低层 3 //左边依赖于抽象, 但是右边还是在依赖细节 4 IPhone phone = new AndroidPhone(); 5 //左边依赖于抽象, 右边交给工厂 6 // 高层本来是依赖低层,但是可以通过工厂(容器)来决定细节,去掉了对低层的依赖, 这就是IOC控制反转:把高层对低层的依赖,转移到第三方决定,避免高层对低层的直接依赖(是一种目的) 7 IPhone phone = ObjectFactory.CreatePhone();//容器的雏形
b) 工厂这里相当于第三方
1 public class ObjectFactory 2 { 3 /// <summary> 4 /// 简单工厂+配置文件+反射 5 /// </summary> 6 /// <returns></returns> 7 public static IPhone CreatePhone() 8 { 9 string classModule = ConfigurationManager.AppSettings["iPhoneType"]; 10 Assembly assemly = Assembly.Load(classModule.Split(',')[1]); 11 Type type = assemly.GetType(classModule.Split(',')[0]); 12 return (IPhone)Activator.CreateInstance(type); 13 } 14 } 15 //配置文件中的内容: 16 <appSettings> 17 <add key="iPhoneType" value=" MyIOCTest.Service.ApplePhone, MyIOCTest.Service" /> 18 </appSettings>
三. 使用Unity容器实现IOC
a) 最简单的容器使用, 给一个接口注册一种类型:
1 //最简单的Unity的使用演示, 给一个接口注册一种类型 2 Console.WriteLine("*************** Unity容器的初步应用***************"); 3 //1 声明一个容器 4 IUnityContainer container = new UnityContainer(); 5 //2. 初始化容器 注册类型 告诉容器, 如果遇到IPhone, 就帮我创建一个AndroidPhone的实例出来 6 container.RegisterType<IPhone, AndroidPhone>(); 7 //3 容器内部通过反射创建对象 8 IPhone phone = container.Resolve<IPhone>(); 9 phone.Call();
b) 容器生成实例的各种方法 :在Unity容器中, 使用别名创建不同的子类
1 //容器的基本使用; 使用别名创建子类 2 Console.WriteLine("***************1. Unity容器的初步应用; 使用别名创建不同的子类***************"); 3 IUnityContainer container = new UnityContainer(); 4 5 //只要碰到IPhone这个接口, 都用AndroidPhone这个实例来生成 6 container.RegisterType<IPhone, AndroidPhone>();//接口 7 8 container.RegisterType<AbstractPad, ApplePad>();//抽象类 接口和抽象类,都可以被实例化 9 //container.RegisterType<AbstractPad, ApplePadChild>();//这个会覆盖上面的, 如果不想覆盖, 就要用别名 10 11 //container.RegisterType<ApplePad, ApplePadChild>();//父子类 会覆盖<AbstractPad, ApplePad> 因为这个也是AbstractPad 12 13 //上面的写法会被覆盖, 所有如果一个接口想生成多个子类的实例, 那么就应该在创建类型的时候, 使用别名, 象下面这样; 在创建的时候使用字符串别名 14 container.RegisterType<AbstractPad, ApplePad>("child");//1对多, 一个抽象类对应多个子类 15 container.RegisterType<AbstractPad, ApplePadChild>("grandchild");//1对多 16 17 //只要碰到ITV在这个接口, 都使用AppleTV(123)来创建实例, 这种写法跟直接使用细节, 没有什么区别了. 18 container.RegisterInstance<ITV>(new AppleTV(123));//注册实例; 不常用,依赖细节 19 20 IPhone phone = container.Resolve<IPhone>(); 21 AbstractPad pad = container.Resolve<AbstractPad>(); 22 ApplePad applePad = container.Resolve<ApplePad>(); 23 24 //调用方法; 调用的时候也要传入别名字符串, 要不会分不清创建的那个类实例 25 var childPad = container.Resolve<AbstractPad>("child"); 26 var grandchildPad = container.Resolve<AbstractPad>("grandchild"); 27 var tv = container.Resolve<ITV>();
c) 多层依赖, 依次注入,下面的代码演示, 当需要构造一个ApplePhone的时候, 这个ApplePhone所需要的资源都会被依次的自动注入
i. ApplePhone代码如下:
1 /// <summary> 2 /// 1. 在构造本类的时候, 优先调用带有[InjectionConstructor]特性的构造函数 3 /// 2. 容器在完成构造函数之后, 会检查当前类的所有属性(public的), 如果当前属性带有[Dependency]这个特性, 容器就会对其进行构造 4 /// 3. 构造完 构造函数和属性 之后, 容器开始检查方法, 如果方法是public 且上面添加了[InjectionMethod]特性, 容器也会帮其构造出来 5 /// </summary> 6 public class ApplePhone : IPhone 7 { 8 [Dependency]//二. 属性注入: 对容器有依赖 9 public IMicrophone iMicrophone { get; set; } 10 public IHeadphone iHeadphone { get; set; } 11 public IPower iPower { get; set; } 12 13 /// <summary> 14 /// 这个属性没有添加[Dependency]特性, 所以不会被构造 15 /// </summary> 16 public ITV iTV { get; set; } 17 18 //[InjectionConstructor] 19 public ApplePhone() 20 { 21 Console.WriteLine("{0}构造函数", this.GetType().Name); 22 } 23 24 //[InjectionConstructor] 25 //一. 构造函数注入:最好的, 在没有[InjectionConstructor] 特性的时候,默认找参数最多的构造函数; 26 public ApplePhone(IHeadphone headphone) 27 { 28 this.iHeadphone = headphone; 29 Console.WriteLine("{0}带参数构造函数", this.GetType().Name); 30 } 31 32 public void Call() 33 { 34 Console.WriteLine("{0}打电话", this.GetType().Name); 35 } 36 37 [InjectionMethod]//三. 方法注入(最不推荐的方法注入):最不好的,增加一个没有意义的方法,破坏封装 38 public void Init1234(IPower power) 39 { 40 this.iPower = power; 41 } 42 43 /// <summary> 44 /// 如果一个方法不添加 [InjectionMethod]特性, 那么它在初始化的时候, 则不会被容器构造 45 /// </summary> 46 /// <param name="tV"></param> 47 public void InitITV(ITV tV) 48 { 49 this.iTV = tV; 50 } 51 }
ii) 构造代码如下
1 //多层依赖, 依次注入 2 { 3 //在构造对象时,可以自动的去初始化,对象需要的对象 4 //构造函数注入 属性注入 方法注入(依赖注入的三种方式)//多层的依赖注入, 必须逐层都要注入 5 //不管是构造对象,还是注入对象,这里都是靠反射做到的 6 Console.WriteLine("***************多层依赖, 依次注入***************"); 7 IUnityContainer container = new UnityContainer(); 8 container.RegisterType<IPhone, ApplePhone>(); 9 container.RegisterType<IMicrophone, Microphone>(); 10 container.RegisterType<IPower, Power>(); 11 container.RegisterType<IHeadphone, Headphone>(); 12 container.RegisterType<IBaseDAL, BaseDAL>(); 13 IPhone phone = container.Resolve<IPhone>(); 14 phone.Call(); 15 }
d) 生命周期管理; 不通过容器创建的对象, 在脱离作用域之后,这个时候没有任何引用的情况下, 就会被标记为垃圾, 等待GC回收; 在Unity中, 由于容器成了对象创建的入口, 所以可以加入自己对创建出来对象的管理逻辑;
1 //使用Unity创建对象的时候, 默认每次创建都是一个全新的对象; 如果想要全局实现唯一(单例), 就要使用参数new ContainerControlledLifetimeManager()来让容器单例创建 2 IUnityContainer container = new UnityContainer(); 3 container.RegisterType<IPhone, AndroidPhone>();//使用容器创建对象时, 默认的是瞬时生命周期, 也就是每次创建都是一个全新的对象 4 container.RegisterType<IPhone, AndroidPhone>(new TransientLifetimeManager());//Unity中默认创建的就是瞬时对象, 在构造的时候有没有 new TransientLifetimeManager() 都是一样 5 var phone1 = container.Resolve<IPhone>(); 6 var phone2 = container.Resolve<IPhone>(); 7 Console.WriteLine(object.ReferenceEquals(phone1, phone2)); //false; 创建的是两个不同的对象 8 9 10 //容器实现的单例 11 container.RegisterType<IPhone, AndroidPhone>(new ContainerControlledLifetimeManager()); 12 var phone3 = container.Resolve<IPhone>(); 13 var phone4 = container.Resolve<IPhone>(); 14 Console.WriteLine(object.ReferenceEquals(phone3, phone4));//由于单例, 创建的是相同的对象 15 16 17 18 // 线程单例: 相同线程的实例相同, 不同线程的实例不同 应用场景:web请求 / 多线程操作 19 container.RegisterType<IPhone, AndroidPhone>(new PerThreadLifetimeManager()); 20 21 IPhone iphone1 = null; 22 Action act1 = new Action(() => 23 { //使用单独的线程初始化IPhone1 24 iphone1 = container.Resolve<IPhone>(); 25 Console.WriteLine($"iphone1由线程id={Thread.CurrentThread.ManagedThreadId}"); 26 }); 27 28 var result1 = act1.BeginInvoke(null, null); 29 30 IPhone iphone2 = null; 31 Action act2 = new Action(() => 32 { 33 //单独线程初始化iphone2 34 iphone2 = container.Resolve<IPhone>(); 35 Console.WriteLine($"iphone2由线程id={Thread.CurrentThread.ManagedThreadId}"); 36 }); 37 38 IPhone iphone3 = null; 39 var result2 = act2.BeginInvoke(t => //BeginInvoke是回调线程, 这将会和上面的Action act2的线程属于同一个线程, 注意BeginInvoke是当当前线程完成后, 再用当前的线程来执行现在的任务; 也就是act2完成会后, 再执行这里面的 40 { 41 iphone3 = container.Resolve<IPhone>(); 42 Console.WriteLine($"iphone3由线程id={Thread.CurrentThread.ManagedThreadId}"); 43 Console.WriteLine($"object.ReferenceEquals(iphone2, iphone3)={object.ReferenceEquals(iphone2, iphone3)}"); //结果为True 44 }, null); 45 46 act1.EndInvoke(result1); //调用EndInvoke获得执行解脱 47 act2.EndInvoke(result2); 48 49 Console.WriteLine($"object.ReferenceEquals(iphone1, iphone2)={object.ReferenceEquals(iphone1, iphone2)}");//结果为false 50 51 //了解知识: 52 //关于单例还可以使用分级容器来创建; 不同子容器创建的单例属于不同的实例, 相同子容器创建的单例对象属于同一个实例对象 53 container.RegisterType<IPhone, AndroidPhone>(new HierarchicalLifetimeManager());//分级容器单例 54 //获取子容器 55 IUnityContainer childContainer = container.CreateChildContainer(); 56 57 //外部可释放单例 单例是全局唯一;一旦释放大家都没了; 58 container.RegisterType<IPhone, AndroidPhone>(new ExternallyControlledLifetimeManager()); 59 60 //当真的发生了循环使用可以使用这个来创建 不推荐; 不要使用; 61 container.RegisterType<IPhone, AndroidPhone>(new PerResolveLifetimeManager());
四. 摆脱细节, 使用配置文件实现IOC:
a) Unity.Config配置文件代码:
1 <configuration> 2 <!--配置文件的路径到底应该放到哪个目录下: 配置文件最好是写到项目里, 而不是写到类库中; 始终复制--> 3 <!--当所有的引用都被移除之后, 注意一定要复制对应dll文件到当前程序运行目录下--> 4 <configSections> 5 <!--固定写法; --> 6 <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/> 7 </configSections> 8 <unity> 9 <!--AOP扩展--> 10 <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/> 11 <containers> <!--容器集合, 存放一个个container容器, 所有的容器名称不能重复--> 12 <container name="testContainer"> 13 <!--type 使用逗号分隔, 前面是完整的接口名称, 后面是接口所在的dll文件名 --> 14 <!--mapTo 表示映射名称, 想生成那个类, 就在这里配置一下; 当前这一行是想利用IPhone来生成ApplePhone这个类; name="Android" 表示别名--> 15 <register type="MyIOCTest.Interface.IPhone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.ApplePhone, MyIOCTest.Service"/> 16 <register type=" MyIOCTest.Interface.IPhone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.AndroidPhone, MyIOCTest.Service" name="Android"/> 17 <register type=" MyIOCTest.Interface.IMicrophone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Microphone, MyIOCTest.Service"/> 18 <register type=" MyIOCTest.Interface.IHeadphone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Headphone, MyIOCTest.Service"/> 19 <register type=" MyIOCTest.Interface.IPower, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Power, MyIOCTest.Service"/> 20 <register type=" MyIOCTest.IDAL.IBaseDAL, MyIOCTest.IDAL" mapTo=" MyIOCTest.DAL.BaseDAL, MyIOCTest.DAL"/> 21 </container> 22 </containers> 23 </unity> 24 </configuration>
b) 实现代码, 程序运行必须是依赖细节的, 但是编码过程又不想依赖细节, 所以只能通过配置文件:
1 // ExeConfigurationFileMap 配置文件操作对象 2 ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap(); 3 //配置文件的路径到底应该放到哪个目录下: 配置文件最好是写到项目里, exe所在的目录, 而不是写到类库中; 注意始终复制 4 fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\Unity.Config");//找配置文件的路径 5 Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); 6 UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName); 7 8 IUnityContainer container = new UnityContainer(); 9 section.Configure(container, "testContainer"); //testContainer 容器的名称 10 IPhone phone = container.Resolve<IPhone>(); 11 phone.Call(); 12 13 //使用别名构造 14 IPhone android = container.Resolve<IPhone>("Android"); 15 android.Call(); 16 17 //4.1 使用配置文件的时候, 不需要依赖细节类, 只需要引用接口的dll即可; 但是要注意的是, 还是必须要将细节类的 dll复制 到项目运行的根目录下的; 要不然就会报错
五. AOP和IOC的组合使用:
a) 配置文件代码:
1 <!--Unity 5.8.6 2 Unity.Interception 5.5.3 3 4 配置文件中的dll名称改了 5 <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/> 6 改为 7 <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/> 8 9 <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration"/> 10 改为 11 <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/> 12 --> 13 14 <configuration> 15 <!--配置文件的路径到底应该放到哪个目录下: 配置文件最好是写到项目里, 而不是写到类库中; 始终复制--> 16 <!--当所有的引用都被移除之后, 注意一定要复制对应dll文件到当前程序运行目录下--> 17 <configSections> 18 <!--固定写法; --> 19 <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/> 20 </configSections> 21 <unity> 22 <!--AOP扩展--> 23 <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/> 24 <containers> <!--容器集合, 里面放着一个个container容器--> 25 <!--这下面有一个个容器, 所有的容器名称不能重复--> 26 27 <!--name表示容器的名字, 前端使用的时候要传入--> 28 <container name="testContainerAOP"> 29 <extension type="Interception"/> 30 <register type=" MyIOCTest.Interface.IPhone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.AndroidPhone, MyIOCTest.Service.Extend"> 31 <interceptor type="InterfaceInterceptor"/> <!--加入AOP扩展, 访问AndroidPhone的时候, 执行AOP--> 32 <!--权限认证--> 33 <interceptionBehavior type=" MyIOCTest.Framework.AOP.AuthorizeBehavior, MyIOCTest.Framework"/> 34 <!--发送短信--> 35 <interceptionBehavior type=" MyIOCTest.Framework.AOP.SmsBehavior, MyIOCTest.Framework"/> 36 <!--异常捕获--> 37 <interceptionBehavior type=" MyIOCTest.Framework.AOP.ExceptionLoggingBehavior, MyIOCTest.Framework"/> 38 <!--缓存--> 39 <interceptionBehavior type=" MyIOCTest.Framework.AOP.CachingBehavior, MyIOCTest.Framework"/> 40 <!--调用前写日志--> 41 <interceptionBehavior type=" MyIOCTest.Framework.AOP.LogBeforeBehavior, MyIOCTest.Framework"/> 42 <!--参数检查--> 43 <interceptionBehavior type=" MyIOCTest.Framework.AOP.ParameterCheckBehavior, MyIOCTest.Framework"/> 44 <!--调用后写日志--> 45 <interceptionBehavior type=" MyIOCTest.Framework.AOP.LogAfterBehavior, MyIOCTest.Framework"/> 46 </register> 47 <register type=" MyIOCTest.Interface.IPhone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.AndroidPhone, MyIOCTest.Service.Extend" name="Android"/> 48 <register type=" MyIOCTest.Interface.IMicrophone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Microphone, MyIOCTest.Service.Extend"/> 49 <register type=" MyIOCTest.Interface.IHeadphone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Headphone, MyIOCTest.Service.Extend"/> 50 <register type=" MyIOCTest.Interface.IPower, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Power, MyIOCTest.Service.Extend"/> 51 <register type=" MyIOCTest.IDAL.IBaseDAL, MyIOCTest.IDAL" mapTo=" MyIOCTest.DAL.BaseDAL, MyIOCTest.DAL"> 52 </register> 53 </container> 54 </containers> 55 </unity> 56 </configuration>
b) 调用示例:
1 ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap(); 2 fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\Unity.Config");//找配置文件的路径 3 Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); 4 UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName); 5 6 IUnityContainer container = new UnityContainer(); 7 section.Configure(container, "testContainerAOP"); //AOP的容器扩展 8 IPhone phone = container.Resolve<IPhone>(); 9 phone.Call(); 10 11 IPhone android = container.Resolve<IPhone>("Android"); 12 android.Call();