最近在研究ABP專案,有關ABP的介紹請看陽光銘睿 部落格,ABP的DI和AOP框架用的是Castle Windsor
下面就對Castle Windsor專案常用方法介紹和關於ABP的使用總結
1、下載Castle.Windsor所需要的dll,在程式包管理器控制檯 執行Install-Package Castle.Windsor
下面先看個簡單的例子
var container = new WindsorContainer(); container.Register( Component.For(typeof(IMyService) .ImplementedBy(typeof(MyServiceImpl) ); //控制反轉 得到例項 var myService= container.Resolve<IMyService>();
我們首先建立了WindsorContainer然後註冊了MyServiceImpl以及它的介面,然後我們用容器建立了一個MyServiceImpl的例項
2、註冊 Castle.Windsor有很多方法來註冊你的類,下面一一介紹幾種註冊方法
常規註冊
我們可以使用Castle.MicroKernel.Registration.Component這個靜態類,的For方法進行註冊,返回一個 ComponentRegistration,就可以用他來進一步註冊
註冊一個類到容器,預設的註冊型別是Singleton也就是單例
container.Register( Component.For<MyServiceImpl>() );
給介面註冊一個預設例項,這種做abp專案中應用很多
container.Register( Component.For(typeof(IMyService) .ImplementedBy(typeof(MyServiceImpl) );
當然我們也可以指定註冊的例項方式,主要有Transient和Singleton,Transient是每次請求都建立一個新例項,Singleton是單例,他們都是LifeStyle的屬性
container.Register( Component.For<IMyService>() .ImplementedBy<MyServiceImpl>() .LifeStyle.Transient );
當註冊一個介面有多個例項的時候,我們可以以命名的方式來註冊,下面這個是沒有重新命名的情況下,預設是註冊第一個MyServiceImpl的
container.Register( Component.For<IMyService>().ImplementedBy<MyServiceImpl>(), Component.For<IMyService>().ImplementedBy<OtherServiceImpl>() );
比如Nop專案中的快取,但是Nop專案是用Autofac,那麼你反轉的時候就可以根據名字進行反轉了
builder.RegisterType<MemoryCacheManager>().As<ICacheManager>().Named<ICacheManager>("nop_cache_static").SingleInstance(); builder.RegisterType<PerRequestCacheManager>().As<ICacheManager>().Named<ICacheManager>("nop_cache_per_request").InstancePerLifetimeScope();
在Castle Windsor我們可以
container.Register( Component.For<IMyService>().Named("OtherServiceImpl").ImplementedBy<OtherServiceImpl>() );
以上講到了windsor專案中常用的最簡單的註冊方式,那麼我們也可以按照程式集進行註冊,比如根據當前程式集註冊以IController為介面的例項
public WindsorControllerFactory(IWindsorContainer container) { this.container = container; var controllerTypes = from t in Assembly.GetExecutingAssembly().GetTypes() where typeof(IController).IsAssignableFrom(t) select t; foreach (var t in controllerTypes) container.Register(Component.For(t).LifeStyle.Transient); }
Assembly.GetExecutingAssembly()是獲取當前執行的程式集,或者你也可以這樣,下面是ABP程式碼
context.IocManager.IocContainer.Register( Classes.FromAssembly(context.Assembly) .IncludeNonPublicTypes() .BasedOn<ISingletonDependency>() .WithService.Self() .WithService.DefaultInterfaces() .LifestyleSingleton() );
自定義註冊
你也可以重寫IWindsorInstaller方法Install進行統一註冊
public class RepositoriesInstaller : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.Register(Classes.FromThisAssembly() .Where(Component.IsInSameNamespaceAs<King>()) .WithService.DefaultInterfaces() .LifestyleTransient()); } }
建構函式&屬性注入
建構函式和屬性注入是專案開發的最佳實踐,你可以使用去獲取你的類的依賴關係。
public class PersonAppService { public ILogger Logger { get; set; } private IPersonRepository _personRepository; public PersonAppService(IPersonRepository personRepository) { _personRepository = personRepository; Logger = NullLogger.Instance; } public void CreatePerson(string name, int age) { Logger.Debug("Inserting a new person to database with name = " + name); var person = new Person { Name = name, Age = age }; _personRepository.Insert(person); Logger.Debug("Successfully inserted!"); } }
IPersonRepository 從建構函式注入, ILogger 例項從公共屬性注入。這樣, 你的程式碼不會體現依賴注入系統。這是使用 DI 系統最適當的方式。
一般的控制器的話我們會統一註冊,下面是ABP註冊控制器的程式碼
public class WindsorControllerFactory : DefaultControllerFactory { private readonly IKernel kernel; public WindsorControllerFactory(IKernel kernel) { this.kernel = kernel; } public override void ReleaseController(IController controller) { kernel.ReleaseComponent(controller); } protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == null) { throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", requestContext.HttpContext.Request.Path)); } return (IController)kernel.Resolve(controllerType); } }
採用建構函式的注入模式是一個完美的提供類的依賴關係的方式。通過這種方式, 只有提供了依賴你才能建立類的例項。 同時這也是一個強大的方式顯式地宣告,類需要什麼樣的
依賴才能正確的工作。但是,在有些情況下,該類依賴於另一個類,但也可以沒有它。這通常是適用於橫切關注點(如日誌記錄)。一個類可以沒有工作日誌,但它可以寫日誌如果你提供一個日誌物件。
在這種情況下, 你可以定義依賴為公共屬性,而不是讓他們放在建構函式。---摘自abp中文文件
好了,到了終於把Castle Windsor一些常用的註冊寫完了,上面主要還是講依賴注入,下面開始ABP的相關介紹
ABP定義了一個統一的註冊類IocManager,主要提供註冊、反轉、登出等操作
public class IocManager : IIocManager { public static IocManager Instance { get; private set; } public IWindsorContainer IocContainer { get; private set; } private readonly List<IConventionalDependencyRegistrar> _conventionalRegistrars; static IocManager() { Instance = new IocManager(); } public IocManager() { IocContainer = new WindsorContainer(); _conventionalRegistrars = new List<IConventionalDependencyRegistrar>(); //Register self! IocContainer.Register( Component.For<IocManager, IIocManager, IIocRegistrar, IIocResolver>().UsingFactoryMethod(() => this) ); } /// <summary> /// Registers types of given assembly by all conventional registrars. See <see cref="AddConventionalRegistrar"/> method. /// </summary> /// <param name="assembly">Assembly to register</param> /// <param name="config">Additional configuration</param> public void RegisterAssemblyByConvention(Assembly assembly, ConventionalRegistrationConfig config) { var context = new ConventionalRegistrationContext(assembly, this, config); //這個迴圈還是要進行四個註冊, foreach (var registerer in _conventionalRegistrars) { registerer.RegisterAssembly(context); } if (config.InstallInstallers) { IocContainer.Install(FromAssembly.Instance(assembly)); } }
有個重要的方法是RegisterAssemblyByConvention會迴圈遍歷繼承IConventionalDependencyRegistrar介面的所有類,並進行註冊
檢視ABP的程式碼發現,實現該介面的主要有四個類,分別註冊DbContex型別,控制器和ApiController和註冊基於介面ITransientDependency和ISingletonDependency和IInterceptor 實現的類
由於篇幅關係 我就只展示註冊控制器
/// <summary> /// Registers all MVC Controllers derived from <see cref="Controller"/>. /// </summary> public class ControllerConventionalRegistrar : IConventionalDependencyRegistrar { /// <inheritdoc/> public void RegisterAssembly(IConventionalRegistrationContext context) { context.IocManager.IocContainer.Register( Classes.FromAssembly(context.Assembly) .BasedOn<Controller>() .LifestyleTransient() ); } }
關於Castle.Windsor註冊有一個實體,一般我們常用的有Singleton(單例)和Transient(每次建立新物件)那麼我們看下ABP是怎麼做的吧
public enum DependencyLifeStyle { /// <summary> /// Singleton object. Created a single object on first resolving /// and same instance is used for subsequent resolves. /// </summary> Singleton, /// <summary> /// Transient object. Created one object for every resolving. /// </summary> Transient }
定義了一個關於LifeStyle的列舉進行相關注冊
public void Register(Type type, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton) { IocContainer.Register(ApplyLifestyle(Component.For(type), lifeStyle)); }
public class LoggerInstaller : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.AddFacility<LoggingFacility>(f => f.UseLog4Net()); } }
第三配置下log4net.config檔案,下面是我的簡單配置
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <!--日誌配置部分--> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" /> </configSections> <log4net> <root> <priority value="All" /> <appender-ref ref="FileAppender" /> <appender-ref ref="InfoLoging" /> </root> <appender name="FileAppender" type="log4net.Appender.RollingFileAppender"> <file value="log\\log.txt" /> <appendToFile value="true" /> <maxSizeRollBackups value="10" /> <maximumFileSize value="10000KB" /> <rollingStyle value="Size" /> <staticLogFileName value="true" /> <filter type="log4net.Filter.LevelRangeFilter"> <levelMin value="ERROR" /> <levelMax value="ERROR" /> </filter> <lockingModel type="log4net.Appender.FileAppender+MinimalLock" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> </layout> </appender> <appender name="InfoLoging" type="log4net.Appender.RollingFileAppender"> <file value="log\\logData.txt" /> <appendToFile value="true" /> <maxSizeRollBackups value="10" /> <maximumFileSize value="10000KB" /> <rollingStyle value="Size" /> <staticLogFileName value="true" /> <filter type="log4net.Filter.LevelRangeFilter"> <levelMin value="INFO" /> <levelMax value="INFO" /> </filter> <lockingModel type="log4net.Appender.FileAppender+MinimalLock" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> </layout> </appender> </log4net> </configuration>
文章最後會附原始碼,包含配置等
最後我們就可以使用了,下面我用的是屬性注入方式
public class AccountController : Controller { public ILogger Logger { get; set; } public AccountController() { Logger = NullLogger.Instance; } public ActionResult LogOn() { Logger.Error("test"); return View(); } }
當然ABP也是提供這種方式用log4net日誌的,但是它驅動的時候是在Global中配置
上面是Castle Windsor的IOC的應用,當然在ABP中也有用到其AOP方法,我們可以繼承IInterceptor的Intercept方法來進行攔截
namespace Castle.DynamicProxy { using System; public interface IInterceptor { void Intercept(IInvocation invocation); } }
下面看下ABP最重要的一個攔截方法
internal class UnitOfWorkInterceptor : IInterceptor { private readonly IUnitOfWorkManager _unitOfWorkManager; public UnitOfWorkInterceptor(IUnitOfWorkManager unitOfWorkManager) { _unitOfWorkManager = unitOfWorkManager; } /// <summary> /// Intercepts a method. /// </summary> /// <param name="invocation">Method invocation arguments</param> public void Intercept(IInvocation invocation) { if (_unitOfWorkManager.Current != null) { //Continue with current uow invocation.Proceed(); return; } var unitOfWorkAttr = UnitOfWorkAttribute.GetUnitOfWorkAttributeOrNull(invocation.MethodInvocationTarget); if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled) { //No need to a uow invocation.Proceed(); return; } //No current uow, run a new one PerformUow(invocation, unitOfWorkAttr.CreateOptions()); }
它的註冊是在模組中進行註冊的,註冊主要包含IRepository和IApplicationService和UnitOfWorkAttribute,所以包含[UnitOfWork]的方法和繼承IRepository和IApplicationService的方法都會被攔截
/// <summary> /// 攔截註冊事件 /// </summary> /// <param name="key"></param> /// <param name="handler"></param> private static void ComponentRegistered(string key, IHandler handler) { if (UnitOfWorkHelper.IsConventionalUowClass(handler.ComponentModel.Implementation)) { //Intercept all methods of all repositories. handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor))); } else if (handler.ComponentModel.Implementation.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any(UnitOfWorkHelper.HasUnitOfWorkAttribute)) { //Intercept all methods of classes those have at least one method that has UnitOfWork attribute. //TODO: Intecept only UnitOfWork methods, not other methods! handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor))); } }
以上就把Castle Windsor的常用功能和ABP專案中的使用簡單的講完了。主要參考的幾個專案ABP、NOP、Prodinner
簡單的原始碼地址:http://pan.baidu.com/s/1kTCNpQZ
參考文章:
https://github.com/ABPFrameWorkGroup/AbpDocument2Chinese
https://github.com/castleproject/Windsor/blob/master/docs/README.md
http://www.cnblogs.com/wucg/archive/2012/03/09/2387946.html