Castle Windsor常用介紹以及其在ABP專案的應用介紹

淮左發表於2015-09-23

最近在研究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));
    }

  

另外Castle Windsor還支援日誌,這點在ABP也有應用到,支援log4net等 
首先需要下載相應的dll ,Install-Package Castle.Windsor-log4net
其次自動註冊一個Log例項
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 

 

相關文章