Ninject.Web.Common,Ninject.MVC3源碼分析
Ninject是一款.Net平臺下的開源依賴注入框架.按照官方說法,它快如閃電、超級輕量,且充分利用了.Net的最新語法,使用Lambda表達式代替Xml文件完成類型綁定.Ninject結構精巧,功能強大,越來越受到各類開發者的關注,其開源社區非常活躍,眾多開發者為它開發了各種各樣的擴展應用.其中有一款名叫Ninject.Web.Common,是所有將Ninject應用于Web項目的基框架,而Ninject.MVC3則是將Ninject應用于Asp.Net Mvc中的框架.這兩者是本文分析的主角.
書寫本文時,Ninject的版本號為3.0.1,Ninject.Web.Common的版本號為3.0.0.3.,Ninject.MVC3的版本號為3.0.0.6.
OnePerRequestHttpModule是與對象生命周期有關的類,表示在同一次請求內同一類型會解析出相同的實例.這個是Web程序特有的生命周期,能在部分應用中節約資源,提高性能.此類實現了IHttpModule接口,如下:
public void Init(HttpApplication application) { application.EndRequest += (o, e) => this.DeactivateInstancesForCurrentHttpRequest(); } public void DeactivateInstancesForCurrentHttpRequest() { if (this.ReleaseScopeAtRequestEnd) { var context = HttpContext.Current; this.MapKernels(kernel => kernel.Components.Get<ICache>().Clear(context)); } }
可以看到,在WebApplication生命周期的最后,其會將所有在請求過程中生成的且緩存于HttpContext.Current的對象手動清空.還可以看到,其實這個類只涉及到OnePerRequest生命周期的部分實現,即清空部分.對象的分配部分則內置于Ninject框架中.
IBootstrapper接口的實現類為Bootstrapper,如下所示:
private static IKernel kernelInstance; public void Initialize(Func<IKernel> createKernelCallback) { kernelInstance = createKernelCallback(); kernelInstance.Components.GetAll<INinjectHttpApplicationPlugin>().Map(c => c.Start()); kernelInstance.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>(); kernelInstance.Inject(this); } public void ShutDown() { if (kernelInstance != null) { kernelInstance.Components.GetAll<INinjectHttpApplicationPlugin>().Map(c => c.Stop()); kernelInstance.Dispose(); kernelInstance = null; } }
public void InitializeHttpApplication(HttpApplication httpApplication)
{
kernelInstance.Inject(httpApplication);
}
Bootstrapper的中文意思是啟動加載器,即管理程序啟動時所需資源.IOC核心最終是保存在此對象的靜態字段kernelInstance中.Initialize方法接受一個Func<IKernel>類型的委托,即通過它獲取IOC核心.然后從中獲取所有實現了INinjectHttpApplicationPlugin接口的類并立刻調用此接口Start方法.可以從名字猜出,這是一個簡易的插件系統,在系統啟動前首先啟動所有插件.第二句先放一下.第三句則是將自己重新注入.只有由從IOC中獲取的對象才能被注入,而Inject方法專門用于不是由IOC生成的對象,可以讓其標記了[Inject]的且為空的屬性重新獲得對象注入.可以看到在默認情況下這一句不會產生實際效果.
InitializeHttpApplication則是將HttpApplication對象重注入.HttpApplication對象是由Asp.Net自動生成的.如果在實際使用的子類中進行了屬性注入,則需要通過重注入的方式為屬性獲取合適的值.
ShutDown方法在系統關閉時被調用,用于插件的逐個卸載.
分析完這兩個類我們再來分析Ninject.Web.Common的核心類:NinjectHttpApplication
private readonly OnePerRequestHttpModule onePerRequestHttpModule; private readonly IBootstrapper bootstrapper; protected NinjectHttpApplication() { this.onePerRequestHttpModule = new OnePerRequestHttpModule(); this.onePerRequestHttpModule.Init(this); this.bootstrapper = new Bootstrapper(); } public void Application_Start() { lock (this) { this.bootstrapper.Initialize(this.CreateKernel); this.onePerRequestHttpModule.ReleaseScopeAtRequestEnd = this.bootstrapper.Kernel.Settings.Get("ReleaseScopeAtRequestEnd", true); this.OnApplicationStarted(); } } public void Application_End() { this.bootstrapper.ShutDown(); this.OnApplicationStopped(); } protected abstract IKernel CreateKernel(); protected virtual void OnApplicationStarted() { } protected virtual void OnApplicationStopped() { }
public override void Init()
{
base.Init();
this.bootstrapper.InitializeHttpApplication(this);
}
在此對象構造函數中,構造了上面所述的兩個對象,并手動調用了OnePerRequestHttpModule對象的Init方法完成了此IHttpModule的掛載.然后在Application_Start()方法中調用了IBootstrapper對象的Initialize方法完成了插件的掛載.并設置了OnePerRequestHttpModule對象是否開啟資源清理.最后調用OnApplicationStarted方法.Application_End()則完成插件的卸載和OnApplicationStopped()方法.
可以看到,此對象改變了傳統HttpApplication對象的一般處理過程.按照其設計意圖,應用程序開啟時執行的方法由默認的Application_Start方法轉移動OnApplicationStarted(),而應用程序終止時執行的方法則由Application_End方法轉移到OnApplicationStopped方法中.
此對象實際是一個抽象對象,包含唯一一個抽象方法CreateKernel,方法最終將被用戶重寫,用于提供IOC核心.
在Init方法中調用Bootstrapper對象的InitializeHttpApplication方法完成對自身的重注入.Init方法與Application_Start方法不同.Init方法將在每個HttpApplication對象構造完成并加載了所有IHttpModule之后被調用,而Application_Start方法則是在應用程序啟動時被調用,在整個應用程序中Init方法會被調用多次,而Application_Start方法只會被調用一次.如果HttpApplication對象池有剩余對象,則會取出一個來處理請求,這時不會觸發Init方法,否則則會建立新的HttpApplication對象來處理新的請求,這時則會調用Init方法.
至此,Ninject.Web.Common的主體工程已分析完畢.但其仍提供了兩個額外的HttoModule.這也是最容易讓人引起困惑的地方.
NinjectHttpModule類提供了一種功能,即通過類型綁定而不是Web.Config來配置IHttpModule,如下:
private IList<IHttpModule> httpModules; public void Init(HttpApplication context) { this.httpModules = new Bootstrapper().Kernel.GetAll<IHttpModule>().ToList(); foreach (var httpModule in this.httpModules) { httpModule.Init(context); } } public void Dispose() { foreach (var httpModule in this.httpModules) { httpModule.Dispose(); } this.httpModules.Clear(); }
代碼比較簡單,即在Init處獲取所有已綁定的實現了IHttpModule接口的對象并循環調用它們的Init方法,在Dispose方法處循環調用它們各自的Dispose方法.
HttpApplicationInitializationHttpModule對象如下:
private readonly Func<IKernel> lazyKernel; public HttpApplicationInitializationHttpModule(Func<IKernel> lazyKernel) { this.lazyKernel = lazyKernel; } public void Init(HttpApplication context) { this.lazyKernel().Inject(context); }
這個更簡單,就是將HttpApplication對象重新注入,只不過實現為了IHttpModule接口.還記得Bootstrapper對象的Initialize方法嗎?在那里將IHttpModule與HttpApplicationInitializationHttpModule進行了綁定.
在Ninject.Web.Common官網上展示了使用這個框架的兩種方式,一種是繼承,一種是動態注入.現在問題來了:
1.如何使用HttpApplicationInitializationHttpModule對象?可以看到,其沒有無參構造函數,如果直接配置于Web.Config,Asp.Net則會因為無法構造此對象而報錯.此對象需配合NinjectHttpModule對象使用.
2.如果使用繼承方式使用,同時在Web.Config中配置了NinjectHttpModule,則HttpApplication對象將被注入兩次.一次在Bootstrapper對象的InitializeHttpApplication方法中,一次在HttpApplicationInitializationHttpModule對象的Init方法中.一開始我百思不得其解,后來看到一篇官方的反饋我才恍然大悟.原來HttpApplicationInitializationHttpModule并不應該用于使用繼承方式中,而是用于動態注入方式中的.
3.同上,如果使用繼承方式使用,同時在Web.Config中配置了NinjectHttpModule,則程序會報錯,因為是HttpApplicationInitializationHttpModule對象是由IOC核心構造的,其需要知道Func<IKernel>類型的實際類型.
最后,我們來看一看官方推薦的動態注入使用方式
[assembly: WebActivator.PreApplicationStartMethod(typeof(WebApplication1.App_Start.NinjectWebCommon), "Start")] [assembly: WebActivator.ApplicationShutdownMethodAttribute(typeof(WebApplication1.App_Start.NinjectWebCommon), "Stop")] public static class NinjectWebCommon { private static readonly Bootstrapper bootstrapper = new Bootstrapper(); /// <summary> /// Starts the application /// </summary> public static void Start() { DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule)); DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule)); bootstrapper.Initialize(CreateKernel); } public static void Stop() { bootstrapper.ShutDown(); } private static IKernel CreateKernel() { var kernel = new StandardKernel(); kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel); kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>(); RegisterServices(kernel); return kernel; } private static void RegisterServices(IKernel kernel) { } }
它充分使用Asp.Net 4.0的新技術,并使用了WebActivator框架,在Web程序啟動前動態調用Start函數,在關閉前調用Stop函數.在Start函數中,動態注冊了OnePerRequestHttpModule和NinjectHttpModule.而對于CreateKernel函數,首先新建了IOC核心,然后綁定了Func<IKernel>類型與IHttpModule類型.可以看到,由于在Bootstrapper對象的Initialize方法里已綁定過一次,這里重復綁定了.然后調用RegisterServices方法獲取應用程序的各類具體類型綁定,最后將IOC核心返回以保存至Bootstrapper的靜態變量kernelInstance中.
Ninject.MVC3的代碼則比較簡單
MvcModule類繼承自GlobalKernelRegistrationModule<OnePerRequestHttpModule>類.GlobalKernelRegistrationModule<T>類繼承自NinjectModule類.完成了各種類型綁定.GlobalKernelRegistrationModule<T>類與GlobalKernelRegistration類配合使用的,實現了一種反向綁定.一般的正向綁定,即某一個IOC核心綁定了多個接口到類的映射,而反向綁定則是記錄某個接口在哪些IOC核心里進行了綁定.這個功能我暫時沒有看到在哪里進行了使用,不過應該是有所目的的.
MvcModule類定義如下:
public override void Load() { base.Load(); this.Kernel.Components.Add<INinjectHttpApplicationPlugin, NinjectMvcHttpApplicationPlugin>(); this.Kernel.Bind<IDependencyResolver>().To<NinjectDependencyResolver>(); this.Kernel.Bind<IFilterProvider>().To<NinjectFilterAttributeFilterProvider>(); this.Kernel.Bind<IFilterProvider>().To<NinjectFilterProvider>(); this.Kernel.Bind<RouteCollection>().ToConstant(RouteTable.Routes); this.Kernel.Bind<HttpContext>().ToMethod(ctx => HttpContext.Current).InTransientScope(); this.Kernel.Bind<HttpContextBase>().ToMethod(ctx => new HttpContextWrapper(HttpContext.Current)).InTransientScope(); this.Kernel.Bind<ModelValidatorProvider>().To<NinjectDataAnnotationsModelValidatorProvider>(); //this.Kernel.Bind<IModelBinderProvider>().To<NinjectModelBinderProvider>(); //this.Kernel.Bind<IModelBinder>().To<NinjectModelBinder>(); }
那這個Load函數是何時執行的呢?這就要談到Ninject的加載策略了.核心對象StandardKernel與其基類KernelBase的構造函數如下
public class StandardKernel : KernelBase { public StandardKernel(params INinjectModule[] modules) : base(modules) { } } public abstract class KernelBase : BindingRoot, IKernel { protected KernelBase() : this(new ComponentContainer(), new NinjectSettings(), new INinjectModule[0]) {} protected KernelBase(IComponentContainer components, INinjectSettings settings, params INinjectModule[] modules) { if (this.Settings.LoadExtensions) { this.Load(this.Settings.ExtensionSearchPatterns); } } }
可以看到,在KernelBase的構造函數中,有一個LoadExtensions的判斷,默認在NinjectSettings類中,意思是加載本地擴展
public class NinjectSettings : INinjectSettings { public bool LoadExtensions { get { return Get("LoadExtensions", true); } set { Set("LoadExtensions", value); } } public string[] ExtensionSearchPatterns { get { return Get("ExtensionSearchPatterns", new [] { "Ninject.Extensions.*.dll", "Ninject.Web*.dll" }); } set { Set("ExtensionSearchPatterns", value); } } }
可以猜測,默認會去Bin目錄下搜索所有以Ninject.Extensions或Ninject.Web開頭的程序集.
具體的加載過程則比較簡單
public void Load(IEnumerable<string> filePatterns) { var moduleLoader = this.Components.Get<IModuleLoader>(); moduleLoader.LoadModules(filePatterns); }
加載管理器接口IModuleLoader的實現類為ModuleLoader
public void LoadModules(IEnumerable<string> patterns) { var plugins = Kernel.Components.GetAll<IModuleLoaderPlugin>(); var fileGroups = patterns .SelectMany(pattern => GetFilesMatchingPattern(pattern)) .GroupBy(filename => Path.GetExtension(filename).ToLowerInvariant()); foreach (var fileGroup in fileGroups) { string extension = fileGroup.Key; IModuleLoaderPlugin plugin = plugins.Where(p => p.SupportedExtensions.Contains(extension)).FirstOrDefault(); if (plugin != null) plugin.LoadModules(fileGroup); } }
第一句獲取所有的加載分析器.程序自帶了加載以dll為擴展名的文件型加載分析器,實現類為CompiledModuleLoaderPlugin.第二句獲取傳入的程序集全路徑.第三句則是循環文件,為每一個文件獲取一個加載分析器并進行操作.
public class CompiledModuleLoaderPlugin : NinjectComponent, IModuleLoaderPlugin { public void LoadModules(IEnumerable<string> filenames) { var assembliesWithModules = this.assemblyNameRetriever.GetAssemblyNames(filenames, asm => asm.HasNinjectModules()); this.Kernel.Load(assembliesWithModules.Select(asm => Assembly.Load(asm))); } }
這里將實際功能委托給AssemblyNameRetriever類完成.
public class AssemblyNameRetriever : NinjectComponent, IAssemblyNameRetriever { public IEnumerable<AssemblyName> GetAssemblyNames(IEnumerable<string> filenames, Predicate<Assembly> filter) { var assemblyCheckerType = typeof(AssemblyChecker); var temporaryDomain = CreateTemporaryAppDomain(); try { var checker = (AssemblyChecker)temporaryDomain.CreateInstanceAndUnwrap( assemblyCheckerType.Assembly.FullName, assemblyCheckerType.FullName ?? string.Empty); return checker.GetAssemblyNames(filenames.ToArray(), filter); } } }
這里創建了一個臨時應用程序域,然后在其中創建AssemblyChecker對象,最終通過它來完成最后的任務
private class AssemblyChecker : MarshalByRefObject { public IEnumerable<AssemblyName> GetAssemblyNames(IEnumerable<string> filenames, Predicate<Assembly> filter) { var result = new List<AssemblyName>(); foreach (var filename in filenames) { Assembly assembly = Assembly.Load(filename); if (filter(assembly)) { result.Add(assembly.GetName(false)); } } return result; } }
這里在臨時應用程序域中創建程序集后,使用了一個判斷來決定是否在此程序集中搜索,是由之前傳入的,定義在ExtensionsForAssembly類中.
internal static class ExtensionsForAssembly { public static bool HasNinjectModules(this Assembly assembly) { return assembly.GetExportedTypes().Any(IsLoadableModule); } public static IEnumerable<INinjectModule> GetNinjectModules(this Assembly assembly) { return assembly.GetExportedTypes() .Where(IsLoadableModule) .Select(type => Activator.CreateInstance(type) as INinjectModule); } private static bool IsLoadableModule(Type type) { return typeof(INinjectModule).IsAssignableFrom(type) && !type.IsAbstract && !type.IsInterface && type.GetConstructor(Type.EmptyTypes) != null; } }
可以看到,這里就是說,在某一程序集中是否有實現了INinjectModule接口的類,且不是接口,不是抽象類,且還擁有無參構建函數.
獲取到程序集名后,由KernelBase完全加載
public void Load(IEnumerable<Assembly> assemblies) { this.Load(assemblies.SelectMany(asm => asm.GetNinjectModules())); } public void Load(IEnumerable<INinjectModule> m) { Ensure.ArgumentNotNull(m, "modules"); m = m.ToList(); foreach (INinjectModule module in m) { module.OnLoad(this); this.modules.Add(module.Name, module); } }
NinjectDependencyResolver類則實現了接口IDependencyResolver,其本質是一個配置器模式,即讓Asp.Net MVC從IOC核心的獲取資源.定義如下:
private readonly IResolutionRoot resolutionRoot; public object GetService(Type serviceType) { var request = this.resolutionRoot.CreateRequest(serviceType, null, new Parameter[0], true, true); return this.resolutionRoot.Resolve(request).SingleOrDefault(); } public IEnumerable<object> GetServices(Type serviceType) { return this.resolutionRoot.GetAll(serviceType).ToList(); }
NinjectMvcHttpApplicationPlugin類則是利用Ninject.Web.Common的插件機制,完成IOC與Asp.Net MVC的對接,其關鍵代碼如下:
public void Start() { ModelValidatorProviders.Providers.Remove(ModelValidatorProviders.Providers.OfType<DataAnnotationsModelValidatorProvider>().Single()); DependencyResolver.SetResolver(this.CreateDependencyResolver()); RemoveDefaultAttributeFilterProvider(); } protected IDependencyResolver CreateDependencyResolver() { return this.kernel.Get<IDependencyResolver>(); }
在CreateDependencyResolver方法中從IOC核心中獲取IOC實例,在KernelBase對象的構造函數中完成了IKernel接口與IKernel實例的隱式綁定.如下
this.Bind<IKernel>().ToConstant(this).InTransientScope(); this.Bind<IResolutionRoot>().ToConstant(this).InTransientScope();
回到NinjectMvcHttpApplicationPlugin類,最后在其Start方法的第二行將獲取到的IOC實例作為參數傳入DependencyResolver對象的SetResolver方法,完成與Asp.Net MVC的對接.
至此,Ninject.MVC3的工作已全部完成!
參考的文章
How to inject dependencies in an HttpModule with a NinjectHttpApplication (no nuget)?
RC2, issue with HttpApplicationInitializationHttpModule Injection
ASP.NET MVC3 讓依賴注入來的更簡單(新補充了Ninject示例)
ASP.NET MVC3 + Ninject.Mvc3 依賴注入原來可以這么簡單
ASP.NET MVC 3 Service Location, Part

浙公網安備 33010602011771號