IOC:有不少人把控制反轉和依賴注入混爲一談,雖然在某種意義上來看他們是一體的,但好像又有些不一樣。緩存
1. IOC(控制反轉)是一個控制容器,DI(依賴注入)就是這個容器的運行機制。安全
2. IOC就是一種軟件設計思想,DI是這種軟件設計思想的一個實現。框架
關於Ioc的框架有不少,好比astle Windsor、Unity、Spring.NET、StructureMap,咱們這邊使用微軟提供的Unity作示例,你可使用 Nuget 添加 Unity ,ide
也能夠引用Microsoft.Practices.Unity.dll和Microsoft.Practices.Unity.Configuration.dll。函數
a. 先介紹常規作法,代碼比較簡單,稍微看一下:測試
public interface IBaseRepository { void DoSomething(); } public interface IRespository : IBaseRepository { } public interface IUserRepository : IBaseRepository { } public interface IShopRepository : IBaseRepository { } public class Repository : IRespository { public void DoSomething() { Console.WriteLine("I am a Repository"); } } public class UserRepository : IUserRepository { public void DoSomething() { Console.WriteLine("I am a UserRepository"); } } public class ShopRepository : IShopRepository { public void DoSomething() { Console.WriteLine("I am a ShopRepository"); } } public interface ITestService { void DoSomething(); } public class TestService : ITestService { private IRespository respository; /// <summary> /// 構造注入 /// </summary> /// <param name="_respository"></param> public TestService(IRespository _respository) { this.respository = _respository; } /// <summary> /// 屬性注入 /// </summary> [Dependency] public IUserRepository UserRepository { get; set; } public IShopRepository ShopRepository { get; set; } /// <summary> /// 方法注入 /// </summary> /// <param name="_shopRepository"></param> [InjectionMethod] public void SetShopRepository(IShopRepository _shopRepository) { this.ShopRepository = _shopRepository; } public void DoSomething() { respository.DoSomething(); UserRepository.DoSomething(); ShopRepository.DoSomething(); } } class Program { static void Main(string[] args) { UnityContainer container = new UnityContainer(); container.RegisterType<IRespository, Repository>(); container.RegisterType<IUserRepository, UserRepository>(); container.RegisterType<IShopRepository, ShopRepository>(); container.RegisterType<ITestService, TestService>(); TestService testService = container.Resolve<ITestService>() as TestService; testService.DoSomething(); } }
b. 還有一種是配置文件作法this
這個是配置文件:spa
<?xml version="1.0" encoding="utf-8"?> <configuration> <!--要保證configSections爲configuration第一個節點--> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/> </configSections> <unity> <containers> <!--定義了一個名稱爲defaultContainer的Unity容器--> <container name="defaultContainer"> <register type="Demo.IRespository,Demo" mapTo="Demo.Respository, Demo"/> <register type="Demo.IUserRespository,Demo" mapTo="Demo.UserRespository, Demo"/> <register type="Demo.IShopRespository,Demo" mapTo="Demo.ShopRespository, Demo"/> <register type="Demo.ITestService,Demo" mapTo="Demo.TestService, Demo"/> </container> </containers> </unity> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> </startup> </configuration>
對應的代碼也得改改:線程
InitializeComponent(); //建立容器 UnityContainer container = new UnityContainer(); UnityConfigurationSection configuration = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName) as UnityConfigurationSection; configuration.Configure(container, "defaultContainer"); //經過Resolve<ITestService>方法返回的是一個類型爲TestService的對象,該對象的三個屬性被進行了有效的初始化。 //這個簡單的程序分別體現了接口注入(經過相應的接口根據配置解析出相應的實現類型)、構造器注入(屬性IRespository)、屬性注入(屬性IUserRepository)和方法注入(屬性IShopRepository) TestService t = container.Resolve<ITestService>() as TestService ; if (null != t) { t.DoSomething(); }
Unity有三種注入方式,構造,屬性和方法注入,Unity注入也有本身的生命週期(默認瞬時生命週期:每次都是構造一個新的),下面就介紹下生命週期。設計
IUnityContainer container = new UnityContainer(); //默認瞬時生命週期,每次都是構造一個新的 container.RegisterType<IA, A>(new TransientLifetimeManager()); //每線程生命週期管理器,就是保證每一個線程返回同一實例 container.RegisterType<IA, A>(new PerThreadLifetimeManager()); //容器控制生命週期管理,這個生命週期管理器是RegisterInstance默認使用的生命週期管理器,也就是單件實例,UnityContainer會維護一個對象實例的強引用,每次調用的時候都會返回同一對象 container.RegisterType<IA, A>(new ContainerControlledLifetimeManager()); //分層生命週期管理器,這個管理器相似於ContainerControlledLifetimeManager,也是由UnityContainer來管理,也就是單件實例,針對某個層單例 //不過與ContainerControlledLifetimeManager不一樣的是,這個生命週期管理器是分層的,由於Unity的容器時能夠嵌套的,因此這個生命週期管理器就是針對這種狀況,當使用了這種生命週期管理器,父容器和子容器所維護的對象的生命週期是由各自的容器來管理 container.RegisterType<IA, A>(new HierarchicalLifetimeManager()); //這個生命週期是爲了解決循環引用而重複引用的生命週期 container.RegisterType<IA, A>(new PerResolveLifetimeManager()); //外部控制生命週期管理器,這個生命週期管理容許你使用RegisterType和RegisterInstance來註冊對象之間的關係,可是其只會對對象保留一個弱引用,其生命週期交由外部控制,也就是意味着你能夠將這個對象緩存或者銷燬而不用在乎UnityContainer,而當其餘地方沒有強引用這個對象時,其會被GC給銷燬掉。 //在默認狀況下,使用這個生命週期管理器,每次調用Resolve都會返回同一對象(單件實例),若是被GC回收後再次調用Resolve方法將會從新建立新的對象 container.RegisterType<IA, A>(new ExternallyControlledLifetimeManager());
怎麼本身實現一個簡單的IOC容器呢?本次只實現一個簡單的版本,生命週期只實現了三種,分別是瞬時,單例和線程單例。
a.首先定義一個生命週期的枚舉 :
public enum LifeTimeType { /// <summary> /// 瞬時 /// </summary> Transient, /// <summary> /// 單例 /// </summary> Singleton, /// <summary> /// 線程單例 /// </summary> PerThread }
b. 定義一個保存註冊映射信息的對象:
public class RegisterInfo { /// <summary> /// 目標類型 /// </summary> public Type TargetType { get; set; } /// <summary> /// 生命週期 /// </summary> public LifeTimeType LifeTime { get; set; } }
c. 定義三個Attribute,直接寫一塊兒了:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class DependencyAttribute : Attribute { public LifeTimeType _lifeTimeType { get; set; } public DependencyAttribute(LifeTimeType lifeTimeType = LifeTimeType.Transient) { this._lifeTimeType = lifeTimeType; } } [AttributeUsage(AttributeTargets.Constructor)] public class InjectionConstructorAttribute : Attribute { public InjectionConstructorAttribute() { } } [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class InjectionMethodAttribute : Attribute { private LifeTimeType _lifeTimeType { get; set; } public InjectionMethodAttribute(LifeTimeType lifeTimeType = LifeTimeType.Transient) { this._lifeTimeType = lifeTimeType; } }
d. 定義一個容器的接口:
public interface IIOCContainer { void RegisterType<TFrom, TTo>(LifeTimeType lifeTimeType = LifeTimeType.Transient); T Resolve<T>(); }
e. 實現該接口的方法,這是整個容器的關鍵代碼,代碼比較長,寫的可能有錯誤,歡迎指正:
public class IOCContainer : IIOCContainer { /// <summary> /// 緩存注入的配置 /// </summary> private Dictionary<string, RegisterInfo> ContainerDictionary = new Dictionary<string, RegisterInfo>(); /// <summary> /// 緩存起來,類型的對象實例 /// </summary> private Dictionary<Type, object> TypeObjectDictionary = new Dictionary<Type, object>(); public void RegisterType<TFrom, TTo>(LifeTimeType lifeTimeType = LifeTimeType.Transient) { ContainerDictionary.Add(typeof(TFrom).FullName, new RegisterInfo() { TargetType = typeof(TTo), LifeTime = lifeTimeType }); } public T Resolve<T>() { RegisterInfo info = ContainerDictionary[typeof(T).FullName]; Type type = ContainerDictionary[typeof(T).FullName].TargetType; T result = default(T); result = (T)CreateTypeByRegisterType(info, type); return result; } private object CreateObject(Type type) { ConstructorInfo[] ctorArray = type.GetConstructors(); ConstructorInfo ctor = null; if (ctorArray.Count(c => c.IsDefined(typeof(InjectionConstructorAttribute), true)) > 0) { ctor = ctorArray.FirstOrDefault(c => c.IsDefined(typeof(InjectionConstructorAttribute), true)); } else { ctor = ctorArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault(); } List<object> paraList = new List<object>(); foreach (var parameter in ctor.GetParameters()) { Type paraType = parameter.ParameterType; RegisterInfo info = ContainerDictionary[paraType.FullName]; Type targetType = info.TargetType; object para = null; para = CreateTypeByRegisterType(info, targetType); //遞歸:隱形的跳出條件,就是GetParameters結果爲空,targetType擁有無參數構造函數 paraList.Add(para); } object objType = Activator.CreateInstance(type, paraList.ToArray()); //屬性注入 var properties = type.GetProperties() .Where(p => p.IsDefined(typeof(DependencyAttribute), false)).ToList(); foreach (var item in properties) { var customAttributes = item.GetCustomAttributes(typeof(DependencyAttribute), false) as DependencyAttribute[]; if (customAttributes != null) { Type t = item.PropertyType; RegisterInfo info = ContainerDictionary[t.FullName]; info.LifeTime = customAttributes.FirstOrDefault()._lifeTimeType; var value = CreateObject(info.TargetType); item.SetValue(objType, value); } } //字段注入 var filds = type.GetFields().Where(f => f.IsDefined(typeof(DependencyAttribute), false)).ToList(); foreach (var fild in filds) { var attribute = fild.GetCustomAttribute(typeof(DependencyAttribute)) as DependencyAttribute; if (attribute != null) { Type t = fild.DeclaringType; RegisterInfo info = ContainerDictionary[t.FullName]; info.LifeTime = attribute._lifeTimeType; var value = CreateObject(info.TargetType); fild.SetValue(objType, value); } } //方法注入 var methods = type.GetMethods().Where(m => m.IsDefined(typeof(InjectionMethodAttribute), false)).ToList(); List<object> paramrterList = new List<object>(); foreach (var item in methods) { var attribute = item.GetCustomAttribute(typeof(InjectionMethodAttribute)) as InjectionMethodAttribute; if (attribute != null) { var parameters = item.GetParameters(); foreach (var parameter in parameters) { var paraType = parameter.ParameterType; RegisterInfo info = ContainerDictionary[paraType.FullName]; Type targetType = info.TargetType; object para = CreateTypeByRegisterType(info, targetType); //遞歸:隱形的跳出條件,就是GetParameters結果爲空,targetType擁有無參數構造函數 paramrterList.Add(para); } item.Invoke(objType, paramrterList.ToArray()); } } return objType; } private static readonly object obj = new object(); /// <summary> /// 根據注入類別建立對象 /// </summary> /// <param name="info"></param> /// <param name="targetType"></param> /// <returns></returns> private object CreateTypeByRegisterType(RegisterInfo info, Type targetType) { object para = null; switch (info.LifeTime) { case LifeTimeType.Transient: para = this.CreateObject(targetType); break; case LifeTimeType.Singleton: //須要線程安全 雙if+lock if (para == null) { lock (obj) { if (this.TypeObjectDictionary.ContainsKey(targetType)) { para = this.TypeObjectDictionary[targetType]; } else { if (para == null) { para = this.CreateObject(targetType); this.TypeObjectDictionary[targetType] = para; } } } } break; case LifeTimeType.PerThread: //線程單例:線程槽,把數據存在這裏 { string key = targetType.FullName; object oValue = CallContext.GetData(key); if (oValue == null) { para = this.CreateObject(targetType); CallContext.SetData(key, para); } else { para = oValue; } } break; default: throw new Exception("wrong LifeTime"); } return para; } }
以上代碼通過測試,可使用。
public interface IImitateScene { void DoSomeThing(); } public class ImitateScene: IImitateScene { private IUserService userService; public ImitateScene(IUserService _userService) { this.userService = _userService; } [Dependency(LifeTimeType.Transient)] public IShopService ShopService { get; set; } public IStoreService StoreService { get; set; } [InjectionMethod(LifeTimeType.Transient)] public void SetStoreService(IStoreService _storeService) { this.StoreService = _storeService; } public void DoSomeThing() { this.userService.DoSomeThing(); this.StoreService.DoSomeThing(); this.ShopService.DoSomeThing(); } }
附上項目圖,項目結構只爲了測試,未作過設計,嘿嘿
測試結果:
基本代碼就在這裏了,還有不少功能未完善,後續有時間會努力研究完善它,造造輪子。