Castle IOC是Castle的核心和灵魂。有一句话是这么说的,如果要理解castle和spring这样的框架,必须首先理解其IOC。当然,如果停留在使用层次那就不需要了。本章帮你一起揭密Castle IOC本质。
首先来看个例子。假设有这么个需求,实现给一个通知功能,把一些消息通知一群朋友。需求分析如下:
参与者,接受者,通知内容,通知方式,消息服务协调者(manager,service)
1. 参于actor 操作员(登录者,管理员等) 和 朋友
简单期间,客户用string array实现
String[] friendsList = new String[] { "john", "steve", "david" };
2. 通知形式。为了扩充通知方式,抽出一接口先。
public interface IEmailSender
{
void Send(String from, String to, String message);
}
实现smtp方式,注意,fake方法。示意而已。
注意,这里实现了三个构造函数。
1: public class SmtpEmailSender : IEmailSender
2: {
3:
4: #region IEmailSender Members
5:
6: public virtual void Send(String from, String to, String message)
7: {
8: Console.WriteLine("Sending e-mail from {0} to {1} with message '{2}'", from, to, message);
9: }
10:
11: #endregion
12:
13: private String host = "my.default.host";
14: private int port = 25;
15:
16: public SmtpEmailSender()
17: {
18: }
19:
20: public SmtpEmailSender(String host, int port)
21: {
22: this.host = host;
23: this.port = port;
24: }
25: private String[] _hosts;
26:
27: public SmtpEmailSender(String[] hosts)
28: {
29: if (hosts == null) throw new ArgumentNullException("hosts");
30: if (hosts.Length == 0) throw new ArgumentException("hosts is empty");
31:
32: _hosts = hosts;
33: }
34:
35: }
3. 通知内容,为了通知很丰富格式多样的内容,抽出一模板接口,用不同方法实现各种内容。例如纯文本的,html的,特殊模板语法的等
public interface ITemplateEngine
{
String Process(String templateName);
}
实现一dummy NVelocity类,只返回一简单字符串而已
public class NVelocityTemplateEngine :ITemplateEngine
{
public NVelocityTemplateEngine()
{
}
#region ITemplateEngine Members
public string Process(string templateName)
{
return "some content";
}
#endregion
}
4. 抽象出通知内容和通知方式后,该主角出场。主角是一个通知service,通过使用IEmailSender发送ITemplateEngine 返回的内容给我的朋友,此功能封装在里INewsletterService,定义了Dispatch方法作为分发service。
public interface INewsletterService
{
void Dispatch(String from, String[] targets, String messageTypeName);
}
SimpleNewsLetterService实现了这一服务,定义了两个构造,第二个构造是为了测试对sometype的依赖用,没有他意。这里聚合依赖接口IEmailSender和ITemplateEngine,不是具体类型。这里通过构造器注入的方式实现解藕。
1: public class SimpleNewsLetterService :INewsletterService
2: {
3:
4:
5: private IEmailSender _sender;
6: private ITemplateEngine _templateEngine;
7:
8: public SimpleNewsLetterService(IEmailSender sender, ITemplateEngine templateEngine)
9: {
10: _sender = sender;
11: _templateEngine = templateEngine;
12: }
13:
14: public SimpleNewsLetterService(Type sometype, IEmailSender sender, ITemplateEngine templateEngine)
15: {
16: if (sometype == null) throw new ArgumentNullException("sometype");
17:
18: _sender = sender;
19: _templateEngine = templateEngine;
20: }
21: public void Dispatch(String from, String[] targets, String messageTypeName)
22: {
23: String message = _templateEngine.Process(messageTypeName);
24:
25: foreach(String target in targets)
26: {
27: _sender.Send(from, target, message);
28: }
29: }
30: }
5. 客户端调用如下。(请引入相应dll,我使用的是build 936,目前新的castle还没有release,较早的release是1.0 Release Candidate 3 - September 20, 2007 ,封装的是nhibernate1.2。
1: IWindsorContainer container = new WindsorContainer();
2: container.AddComponent("mailSender", typeof (IEmailSender), typeof (SmtpEmailSender));
3: container.AddComponent("NVelocityEngine", typeof (ITemplateEngine), typeof (NVelocityTemplateEngine));
4: container.AddComponent("newLetter", typeof (INewsletterService), typeof (SimpleNewsLetterService));
5:
6:
7:
8: String[] friendsList = new String[] { "john", "steve", "david" };
9:
10: INewsletterService newsLetterService = container["newLetter"] as INewsletterService ;
11: newsLetterService.Dispatch("peter", friendsList, "hello world");
解释
1:首先new一castle IOC容器WindsorContainer,放入三个component
2:调用的时候通过AddComponent方法,有很多重载。其中一个,AddComponent(string key, System.TypeserviceType , System.Type classType), serviceType是抽象服务type,定义了基本契约。可以是类,接口,抽象类等。classType是实现类 。key必须唯一,相同classType和serviceType不同key可以加入。
3:获取容器中的的组件可以通过Key或者service Type。
4:最后一个AddComponent调用完,容器自动确定IEmailSender,ITemplateEngine ,INewsletterService的依赖关系,并且通过依赖注入(这里是构造器注入)将ITemplateEngine和IEmailSender的实现注入给INewsletterService
5:这里SmtpEmailSender和NVelocityTemplateEngine是调用的默认构造函数,SimpleNewsLetterService是调用的public SimpleNewsLetterService(IEmailSender sender, ITemplateEngine templateEngine)。
问题来了:
1:容器怎么知道mailSender,NVelocityEngine和newLetter的依赖关系?
2:容器怎么会调用到SimpleNewsLetterService的构造函数将mailSender和templateEngine传入的?
3:容器如何管理组件生命周期的?
4:容器对其它注入方式如何支持?如Type3-setter
带着这些问题,让我们来解读大师们的源码,在这里我们领略下成功的open source框架架构设计所带来的乐趣。
未完,待续。。。
附:依赖注入可以通过代码设置,更多的是通过配置文件注入。Spring主要就是配置注入。关于xml配置的在开发中的优缺点,xml配置是否过渡使用,动态语言带来的革新对配置的冲击和影响,有兴趣可以另外探讨
配置文件示范:
Config.XML如下,细节不解释了。看看注释就明白了。另外不管是spring,castle还是microsoft enterprise library都对sectionHandler进行扩充,为了便于在.net开发中在web.config或者app.config进行配置注入。例如Castle.Windsor.Configuration.AppDomain.CastleSectionHandler:
<section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
<castle>
。。。
</castle>
注意下文的对array参数和list的配置,更多的还有泛型type的配置等,依赖的配置。如<sender>${smtpemailsender}</sender>,这里是揭示原理篇,更多使用请参考其它文章或者我的.net solution架构汇总。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <!-- Please note the usage of service and type attributes. Windsor will automatically installs the components and facilities with a type attribute (and optionally service for components)--> <!--This is a facility configuration example: <facilities> <facility id="nhibernate" type="Full Type Name, AssemblyName"> </facility> </facilities> --> <components> <component id="smtpemailsender" service="CastleIOC.IOC.Component.IEmailSender, CastleIOC" type="CastleIOC.IOC.Component.SmtpEmailSender, CastleIOC"> <parameters> <hosts> <!-- The following nodes will be converted to an array of String which is what the component expects. If the component expected an int[], the MicroKernel would try to convert each element as well.The MicroKernel also supports - Dictionaries <dict keyType="System.String, mscorlib" valueType="System.Int32, mscorlib"> <item key="key1">1</item> <item key="key2">2</item> </dict> - Lists <list type="typetobeconverted"> <item>item1</item> <item>item2</item> </list> --> <array> <elem>MyFirstHost</elem> <elem>OtherHost</elem> </array> </hosts> </parameters> </component> <component id="newLetter" service="CastleIOC.IOC.Component.INewsletterService, CastleIOC" type="CastleIOC.IOC.Component.SimpleNewsLetterService, CastleIOC"> <parameters> <!-- This argument will be converted to a System.Type --> <sometype>System.Collections.IList, mscorlib</sometype> <!-- Here we're overriding which IEmailSender implementation this component should use --> <sender>${smtpemailsender}</sender> <!-- Same thing for ITemplateEngine --> <templateEngine>${nvelocityengine}</templateEngine> </parameters> </component> <component id="nvelocityengine" service="CastleIOC.IOC.Component.ITemplateEngine, CastleIOC" type="CastleIOC.IOC.Component.NVelocityTemplateEngine, CastleIOC" /> </components> </configuration>