●Castle Windsor
Windsor是Castle的IOC容器(built on top of a MicroKernel),包含几个概念:
组件(Component)
服务(Service)
扩张单元插件(Facilities)
我是这样理解他们之间的关系的:组件提供服务,也就是服务是一个个接口,而Facilities提供扩张容器管理组件的能力。我们可以直接使用组件,也可以把组件转换成相应的服务接口来使用。
也可以这么说,Component是普通的组件,Facilities是带有注入性质的组件。
●自动装配(Auto-wiring)
自动装配的意思是指由容器自动管理组件之间的依赖关系,而无需编写特定的xml config来配置依赖关系,spring和castle都支持自动装配,但是spring文档中是不推荐使用自动装配的,castle本身就是自动装配的,这是他们之间一个很大的区别。
spring不推荐自动装配的原因是:手动控制依赖关系让你自知道自己在做什么,有利于规范文档。
而castle作者是这样认为的:不使用自动装配,配置文档极之冗长,当组件配置到一定数目时候,管理起来就非常困难。
对比之下,如果我们要改变一个依赖关系或者增加一个组件依赖,使用Castle是比Spring容易得多的。
●扩张单元(Facilities)
如果你想扩张容器的功能,你可以通过创建扩张单元达到目的。
Castle提供有一系列的扩张单元,你也可以建立自己的扩展单元。这些扩张单元是可以重用的,你可以在扩张单元里面订阅容器事件,给组件附加属性,建立拦截器,控制组件生命周期等。
在spring里面,如果要拓展容器的功能,相信就要实现自己的容器,比如从XmlObjectFactory继承下来,相比之下,Castle是通过一种“插件”的形式达到目的,优势是明显的。
关于Castle提供的Facilities请参见:http://www.castleproject.org/index.php/Facilities
2)关于Castle的AOP
AspectSharp 是Castle提供的AOP轻量级框架,AspectSharp是建立在DynamicProxy的基础上,同时Castle提供了 AspectSharp 的Facilities使你可以更容易的在IOC容器里面应用Aspect#。
3)Castle Project ActiveRecord
如果你的项目中使用Nhnbernate作为数据库持久层,有了这个你就可以废弃掉hbm.xml文件了,activeRecord通过Attribute 来配置持久类,而且配合ActiveRecord Generater可以很方便的生成,管理持久类代码,而不用去找生成机的烦恼。
4)Castle Project DynamicProxy
实现动态代理,在Java里面有专门的库来实现代理,而。net的却没有动态代理的相关库,Castle DynamicProxy来弥补这个不足,他是基于Emit技术的。而且AOP,Nhibernate,IBatis都有用到DynamicProxy,了解他是非常重要的,网络上已经有很多介绍DynamicProxy的文章
5)Castle Project MonoRail
MonoRail 原名叫Castle on Rails,他涉及到“Action Pack”的概念,具体请参见:http://ap.rubyonrails.org/(MonoRail is an attempt to provide a port of Action Pack for .Net. The Action Pack way of development is extremelly productive, very intuitive and easily testable. )
Inversion of Control 反转控制介绍
什么是IOC?IOC是一个简单的原则,它规定一个外部实体应该发送信息给编程者的对象从而执行一些特定的操作或者允许编程者能够覆盖某些逻辑。这是API和框架最主要的区别。我们现在举两个例子,第一个例子,你的对象通过API(主动方式)进行对象调用;另一个是采用框架调用你的对象,你的对象处理被动方式。
下面的例子是反转控制解决松偶合问题其中一方面:怎样使你的类得到一个外部对象引用或配置。
using System; using System.Configuration; public class MyAwfulEmailClass { public MyAwfulEmailClass() { } public void SendEmail( String from, String to, String message, String templateName ) { String host = ConfigurationSettings.AppSettings["smtphost"]; int port = Convert.ToInt( ConfigurationSettings.AppSettings["smtpport"] ); NVelocityTemplateEngine engine = new NVelocityTemplateEngine(); String newMessage = engine.Process( message, templateName ); // Finally send message... } }
1、 它要从ConfigurationSettings得到配置信息。如果配置信息不在那里?或者你想采用其他格式来保存配置信息?
2、 在这个类里引用了一个模板引擎。事实上存在两个问题:一是这个类有多个职责。它是用来发邮件,这没错,但是它使用了模板引擎来处理信息内容;二是要对用到的模板引擎有很深的了解。如果以后要更改模板引擎,你必须得改代码。
因此,这个简单的类存在2个较强的依耐:取得配置信息和模板引擎。设想一下如果你要把这个类用到另外的项目,你必须记得配置文件的KEY,还必须引用模板引擎DLL及其用到的其他依赖文件。这只是一个很简单的邮件类,我敢肯定大家可能会有更多类似的情况。通常的最快的解决方案是复制代码再修改部分信息,但是使用IOC容器是更好的解决办法。
Component and Services 组件和服务
组件是一个可复用的代码单元。它应该实现并暴露为一个服务。实际的术语,组件是实现一个服务或接口的类。接口是服务的规范,它创建一个抽象层,,你可以轻松的替换服务的实现。
定义我们的邮件服务,我们要用到下面的接口:
// The contract public interface IEmailSender { void Send(String from, String to, String message) }
// The implementation public class SmtpEmailSender : IEmailSender { private String _host; private int _port; public SmtpEmailSender(String host, int port) { _host = host; _port = port; } public void Send(String from, String to, String message) { // Configures the Smtp class and sends the e-mail } }
是不是比以前做的更好?但是哪里有反转控制?在继续之前,允许我把事情复杂一点。你可能注意到现在的邮件发送器只负责发送邮件,没有对内容进行模板处理。所有这里在定义一个接口用作模板处理引擎。
public interface ITemplateEngine { String Process(String templateName) }
public interface INewsletterService { void Dispatch(String from, String[] targets, String messageTypeName) }
public class SimpleNewsletterService : INewsletterService { private IEmailSender _sender; private ITemplateEngine _templateEngine; public SimpleNewsletterService(IEmailSender sender, ITemplateEngine templateEngine) { _sender = sender; _templateEngine = templateEngine; } public void Dispatch(String from, String[] targets, String messageTypeName) { String message = _templateEngine.Process(messageTypeName); foreach(String target in targets) { _sender.Send(from, target, message); } } }
回顾一下上面的设计,我们把每个类的职责尽量细化了,能够轻松应付一般的软件开发。但是问题是我们必须把它们装配在一起,换句话说,我们需要连接所有类,实例化和配置IEmailSender和ITemplateEngine接口,并把它们作为参数传递给INewsLetterService, 天知道还有其他什么东西。你可以手工做这些事情,对于一个大系统将变得更复杂。
Castle Windsor
Castle Windsor是一个反转控制容器。它创建在一个微内核的基础之上,这个微内核能够扫描类并且试图找到这些类用到哪些对象引用、对象依赖,然后把这些依赖信息提供给类使用。
对于上面的邮件发送的例子,我们用下面的代码可以解决所有问题。
IWindsorContainer container = new WindsorContainer(); container.AddComponent( "newsletter", typeof(INewsletterService),typeof(SimpleNewsletterService) ); container.AddComponent( "smtpemailsender", typeof(IEmailSender),typeof(SmtpEmailSender) ); container.AddComponent( "templateengine", typeof(ITemplateEngine),typeof(NVelocityTemplateEngine) ); // Ok, start the show INewsletterService service = (INewsletterService) container["newsletter"]; service.Dispatch("hammett at gmail dot com", friendsList, "merryxmas");
在容器中注册服务INewsletterServie,并且说明SimpleNewsletterService是对接口的实现。第一个参数是你在以后用这个服务时的KEY,也可以通过类型来调用.
1. 容器检查你提供的实现并通知其他2个服务正常工作。如果它没检测到这样的服务被注册,就会报错!
2. 注册服务IEmailSender.容器通知它的实现,有一个构造函数,需要接受2个参数(Host和port).这个是不能满足要求的,因为我们没提供外部配置文件。这个在后面休正。
3. 注册服务ITemplateEngine.容器注册成功后呼叫其他组件为WaitingDependency状态表示成功注册。INewsletterService服务意识到它的一个依赖项已经OK,但是它任然要等待其他组件。
实际上对容器而言一个没有默认构造函数的类有一种特殊的方式。从构造函数可以知道:“看,我必须指定这些才能工作”。如果我们对IEmailSender的实施修改为属性的方式,容器也将采用不同的方式处理。
public class SmtpEmailSender : IEmailSender { private String _host; private int _port; public SmtpEmailSender() { _host = "mydefaulthost"; _port = 110; // default port } public String Host { get { return _host; } set { _host = value; } } public int Port { get { return _port; } set { _port = value; } } ... }
在这个案例里,你可以在外部指定也可以不指定Host和Port参数,容器能够自动识别。另外一个方式是你可以暴露多个构造函数,容器将会试着使用最佳的构造函数,也就是参数最多的那个构造函数。
配置文件怎么样呢?
默认情况下,容器将从APPDOMAIN关联的XML文件得到组件的配置信息。但是你可以自己实现配置文件的存放方式,要实现IConfigragonStore.这里有另一个实现IConfigragonStore,它从标准的XML文件读取配置信息,
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" /> </configSections> <castle> <components> <component id="smtpemailsender"> <parameters> <host>localhost</host> <port>110</port> </parameters> </component> </components> </castle> </configuration>
public class MyComponent { private IEmailSender _sender; public MyComponent() { } public IEmailSender EmailSender { get { return _sender; } set { _sender = value; } } } IWindsorContainer container = new WindsorContainer(); container.AddComponent( "mycomponent", typeof(MyComponent) );
请求组件的方式:
IEmailSender emailSender = container[ typeof(IEmailSender) ] as IEmailSender;
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" /> </configSections> <castle> <components> <component id="newsletter"> <parameters> <sender>#{smtpEmailSender}</sender> </parameters> </component> </components> </castle> </configuration>
public class SimpleNewsletterService : INewsletterService { private IEmailSender _sender; private ITemplateEngine _templateEngine; public SimpleNewsletterService(IEmailSender sender, ITemplateEngine templateEngine) { _sender = sender; _templateEngine = templateEngine; } ...
Lifecycle and Lifestyle 生命周期和生命样式
生命周期和生命样式是有用策略,Apache Avalon已经使用了较长时间(自19999至今)。Castle作者作为该组织以前的一员,所以在此项目坚决的支持。
组件的生命样式关联到它的实例。它可能是单例(这是默认样式),意味着在容器中只有一个实例将被创建,其他被支持的生命样式有:
* 瞬时:每次请求创建一个新实例
* 每线程:每线程中只存在一个实例
* 自定义:你可以重写实现ILifestyleManager,比如池对象
这里有2种途径指定组件的生命方式,采用属性(attributes)或者在组件上定义。下面的代码是一样的结果:
using Castle.Model; [PerThread] public class SimpleNewsletterService : INewsletterService { private IEmailSender _sender; private ITemplateEngine _templateEngine; ...
<castle> <components> <component id="newsletter" lifestyle="perthread" /> </components> </castle>
Windsor Container更多信息
深入分析组件的注册:
Windsor的核心是MicroKernel,组件是交由MicroKernel的ModelBuilder处理的。
找到DefaultComponentModelBuilder类,它实现了IComponentModelBuilder接口,提供默认的组件向容器注册的行为。IComponentModelBuilder有三个方法BuildModel,AddContributor, RemoveContributor,其中BuildModel按顺序调用Add进来的Contributor对组件进行处理。
当你添加一个组件到容器里时,将发生一系列操作。首先,容器创建一个ComponentModel组件模型,该组件模型包含该组件的“信息库”。ComponentModel委托contributors进行依赖检测,每个contributors只执行特定的任务。比如:有的contributors收集组件的构造函数依赖,有的contributors检测指定的生命样式,有的contributors处理配置,有的contributors处理属性依赖,有的contributors处理处理配置文件中的interceptors元素等。所有的 Contributor都实现IComponentModelBuilder接口,这个接口只有ProcessModel一个方法,从方法名字中可以很清楚的知道这个接口就是用于处理模块的。
容器的使用都是通过:IWindsorContainer 接口的,它提供一系列加载facilities和components的方法。IWindsorContainer把实际的行动交给 DefaultKernel处理,而DefaultKernel就调用ComponentModelBuilder来真正BuildModel并引发相应的Event。
同时,IWindsorContainer 也提供一系列释放facilities和components的方法,释放的同时也要检查依赖性,只有别的组件对要释放的组件没有依赖时候才能成功释放。同样真正的释放动作都是交给Kernel处理的。
扩展容器
我们来分析Facility,作为Castle的可扩展单元,他是可以带有注入性的,也就是对组件来说,他可能“侵犯”组件本身。下面我们从官方提供的一个Startable的Facility开始。
先明白这个Facility的作用,也就是要达到的目的:使一个实现了Castle.Model.IStartable接口的程序在满足依赖性的时候,能够自我执行。
public interface IStartable { void Start(); // 创建的时候执行 void Stop(); // 销毁的时候执行 } 首先我们来了解一下这个接口:ILifecycleConcern,这个接口带有一个方法: public interface ILifecycleConcern { void Apply( ComponentModel model, object component ); }
他用来在组件特定的生命周期提供处理行为。
比如,我们上一节提到的,实现了IInitializable接口的组件,容器会自动调用组件的初始化方法Initialize,只是执行这个接口的Facility是内置的(Kernel自带)。
接着,我们来用两个类实现这个接口:
第一个StartConcern,处理:如果是实现IStartable的组件则调用其Start()方法:
public void Apply(ComponentModel model, object component) { (component as IStartable).Start(); } 第二个StopConcern,处理:如果实现IStartable的组件则调用其Stop()方法: public void Apply(ComponentModel model, object component) { (component as IStartable).Stop(); }
好了,我们看下如何实现一个Facility,创建一个自定的Facility可以直接实现IFacility接口,或者从 AbstractFacility中继承下来,他们的不同支出只是AbstractFacility提供了IFacility接口的默认实现,你只需实现 Init()方法即可。但其实IFacility包含的东西并不多,只有两个:
public interface IFacility { // Facility被添加到容器中就立刻执行 void Init(IKernel kernel, IConfiguration facilityConfig); // 一般在容器的Dispose()中被调用 void Terminate(); }
protected override void Init() { // 在组件创建之后引发 Kernel.ComponentModelCreated += new ComponentModelDelegate(OnComponentModelCreated); // 在组件注册之后引发 Kernel.ComponentRegistered += new ComponentDataDelegate(OnComponentRegistered); }
所以,下面的事情就是这样发生的了:
先Add一个组件,ComponentRegistered事件发生了。
在OnComponentRegistered中检查依赖性,如果依赖性满足并组件实现了IStartable的话,则请求创建这个组件。
private void Start(String key) { object instance = Kernel[key]; }
把StartConcern注册为组件的LifecycleStepType.Commission(生命开始)周期处理行为。
把StopConcern注册为组件的LifecycleStepType.Decommission(生命结束)周期处理行为。
组件被创建之后,立刻进入LifecycleStepType.Commission周期,StartConcern被触发处理,StartConcern.Apply()调用组件的Start()来达到自启动的目的。
同样组件从容器移除的时候,组件就进入LifecycleStepType.Decommission,那么StopConcern.Apply()触发组件的Stop()来进行“最后的喘息”。
Startable的Facility设计原理已经说完,下步就实践咯。
/// <summary> /// 这是一个实现IStartable的类,主要用Application.Run()启动一个窗口 /// 注意看他的构造函数 /// </summary> public class ApplicationRunner : IStartable { private Form _form; private Thread _thread; // 这里表明:这个类是依赖于类型为Form1的组件的 public ApplicationRunner(Form1 form) { _form = form; } #region IStartable 成员 public void Start() { _thread = new Thread(new ThreadStart(StartApp)); _thread.Start(); } public void Stop() { MessageBox.Show("Stop is called, but it do nothing."); } #endregion private void StartApp() { Application.Run( _form ); } } 其中Form1是一个普通窗口: public class Form1 : System.Windows.Forms.Form { // …. } 最后是Main: [MTAThread] static void Main() { // 建立容器 IWindsorContainer container = new WindsorContainer("http://www.cnblogs.com/AppConfig.xml"); // 加入Facility container.AddFacility("startable", new StartableFacility()); // 加入一个ApplicationRunner,这时候容器检测到他对Form1有依赖,所以不启动 container.AddComponent("appRuner", typeof(ApplicationRunner)); // 加入Form1,此时ApplicationRunner满足依赖需求,Start就开始了 container.AddComponent("form1", typeof(Form1)); }
事务处理:
提供事务的封装和默认的事务管理服务器,其他几个Facility都是以他为基础,比如:AutomaticTransactionManagement、NHibernate等。 下面让我们从接口着手了解他对事务做了怎样的封装。
IResource:实现了此接口代表一个可以参与事务的资源
public interface IResource { // 在ITransaction.Begin()ITransaction.Enlist()时调用(准备动作) void Start(); // 在ITransaction.Commit()时调用(提交动作) void Commit(); // 在ITransaction.Rollback()时调用(回滚动作) void Rollback(); } ISynchronization:用于同步处理 public interface ISynchronization { // 在ITransaction.Commit()与ITransaction.Rollback()之前调用 void BeforeCompletion(); // 在ITransaction.Commit()与ITransaction.Rollback()之后调用 void AfterCompletion(); } 接口ITransaction:代表一个事务 public interface ITransaction { // 准备事务,初始化所属资源 void Begin(); // 提高事务 void Commit(); // 回滚事务 void Rollback(); // 获取当前事务状态 TransactionStatus Status { get; } // 加入一个共享资源 void Enlist(IResource resource); // 加入一个同步对象 void RegisterSynchronization(ISynchronization synchronization); // 资源上下文环境,可以附加额外的信息 IDictionary Context { get; } } 接口ITransactionManager:代表一个事务管理器 public interface ITransactionManager { // 创建一个事务 ITransaction CreateTransaction( TransactionMode transactionMode, IsolationMode isolationMode ); // 获取当前的事务 ITransaction CurrentTransaction { get; } // 销毁特定事务的资源 void Dispose(ITransaction transaction); }
AbstractTransaction实现了ITransaction接口:
他提供基本的事务资源与事务同步资源的管理和相应的处理逻辑。
StandardTransaction继承于AbstractTransaction类:
在AbstractTransaction的基础上提供子事务支持。这里为了区别事务的父子关系,就分别将他们称为:父事务和子事务吧。
一个父事务可以包含多个子事务,一个子事务还可以包含子子事务,在任何一个子事务发生回滚的时候,事务链上的所有事务都不能Commit只是Roolback。
事务模式: public enum TransactionMode { Unspecified, Requires, NotSupported, RequiresNew, Supported } 事务隔离级别: public enum IsolationMode { Unspecified, Chaos = 0, ReadCommitted, ReadUncommitted, RepeatableRead, Serializable }
他提供默认的事务管理器,主要负责创建事务和提供当前事务。创建不提供事务隔离级别支持。根据指定的“事务模式”,创建父事务和子事务。不过你完全可以定制自己的事务管理器。
下面来看看如何使用默认事务管理器:
1)创建一个事务管理器:
DefaultTransactionManager tm = new DefaultTransactionManager();
2)从事务管理器中创建一个事务:
ITransaction transaction = tm.CreateTransaction(TransactionMode.Unspecified, IsolationMode.Unspecified);
如果指定TransactionMode为Unspecified,则事务管理器使用默认的TransactionMode.Requires。这样就创建了一个父事务。
3)父事务创建之后,再以Unspecified或者Requires创建事务,则新创建的事务自动成为父事务的子事务。
4)如果不想创建子事务,则用RequiresNew来创建。
使用默事务管理器需要注意一下几点:
1)默认的事务管理器都一个堆栈管理,CurrentTransaction返回的始终是最后一个创建的事务。
2)子事务可以有子子事务。
3)创建事务的时候,父事务和子事务的顺序要一定。如果创建了一个父事务后,再创建就是一个子事务,再来就是一个子子事务。除非你用了RequiresNew后,就重新往返这个过程。