找过一些Windsor教程的文章,博客园上TerryLee有写了不少,以及codeproject等也有一些例子,但都讲的不太明了。今天看到Alex Henderson写的一个系列,非常简单明了。下面是主要的内容
http://www.cnblogs.com/RicCC/archive/2010/03/30/castle-windsor-ioc-di.html
Part 1 - Simple configuration
Part 2 - Array Configuration
Part 3 - Dictionary configuration
Part 4 - Switching configurations
Part 5 - Configuration parameters
Part 6 - Switching between lifestyles
Part 7 - Switching implementations
Part 8 - Referencing implementations by key
Part 9 - Constructor Injection
Part 10 - Setter Injection
Part 11 - Factories
Part 12 - Decorators
Part 13 - Injecting Service Arrays
Part 14 - Startable Facility
基本示例
项目要引用Castle.Core.dll、Castle.MicroKernel.dll、Castle.Windsor.dll,引用namespace:
using Castle.Windsor;
using Castle.Windsor.Configuration.Interpreters;
假设有一个服务类TaxCalculator,用来计算税额:
01 |
public class TaxCalculator |
02 |
{ |
03 |
private decimal _rate = 0.125M; |
04 |
public decimal Rate |
05 |
{ |
06 |
set { _rate = value; } |
07 |
get { return _rate; } |
08 |
} |
09 |
public decimal CalculateTax( decimal gross) |
10 |
{ |
11 |
return Math.Round(_rate * gross, 2); |
12 |
} |
13 |
} |
计算税额时的代码如下:
1 |
WindsorContainer container = new WindsorContainer( new XmlInterpreter()); |
2 |
TaxCalculator calculator = container.Resolve<TaxCalculator>(); |
3 |
decimal gross = 100; |
4 |
decimal tax = calculator.CalculateTax(gross); |
5 |
Console.WriteLine( "Gross: {0}, Tax: {1}" , gross, tax); |
6 |
Console.ReadKey(); |
app.config中的配置如下:
01 |
< configuration > |
02 |
< configSections > |
03 |
< section name = "castle" |
04 |
type = "Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" /> |
05 |
</ configSections > |
06 |
< BR > |
07 |
< castle > |
08 |
< components > |
09 |
< component id = "taxcalc.service" type = "Windsor.Test.TaxCalculator, Windsor.Test" > |
10 |
</ component > |
11 |
</ components > |
12 |
</ castle > |
13 |
</ configuration > |
运行程序,计算结果为12.5。可以在配置文件中为Rate属性指定其他值,例如:
1 |
< component id = "taxcalc.service" type = "Windsor.Test.TaxCalculator, Windsor.Test" > |
2 |
< parameters > |
3 |
< Rate >0.25</ Rate > |
4 |
</ parameters > |
5 |
</ component > |
上面的配置指定税率为0.25,因此计算结果为25
配置
如果在Windsor容器创建对象实例时,需要注入的属性为数组,而不是上面Rate这样的单个值,怎么配置?
假如要注入的属性为:
1 |
public DateTime[] Holidays |
2 |
{ |
3 |
get { return _holidays; } |
4 |
set { _holidays = value; } |
5 |
} |
则可以如下配置:
01 |
< component id = "holidays.service" type = "Windsor.Test.HolidayService, Windsor.Test" > |
02 |
< parameters > |
03 |
< Holidays > |
04 |
< array > |
05 |
< item >2007-12-24</ item > |
06 |
< item >2007-12-25</ item > |
07 |
< item >2008-1-1</ item > |
08 |
</ array > |
09 |
</ Holidays > |
10 |
</ parameters > |
11 |
</ component > |
如果要注入的属性为Dictionary类型,例如:
1 |
public Dictionary< string , string > Aliases |
2 |
{ |
3 |
get { return _aliases; } |
4 |
set { _aliases = value; } |
5 |
} |
配置如下:
01 |
< component id = "aliases.service" type = "Windsor.Test.HolidayService, Windsor.Test" > |
02 |
< parameters > |
03 |
< Aliases > |
04 |
< dictionary > |
05 |
< entry key = "dog" >duck</ entry > |
06 |
< entry key = "ate" >broke</ entry > |
07 |
< entry key = "homework" >code</ entry > |
08 |
</ dictionary > |
09 |
</ Aliases > |
10 |
</ parameters > |
11 |
</ component > |
注入的数组、Dictionary属性,我们都不需要初始化创建这个对象,Windsor在注入的时候会新建数组或者Dictionary对象设置给相应属性
假如,现在通过Windsor配置的服务类比较多,我建立了2份配置,一份用于测试,一份用于生产环境,如何方便的在这2份配置之间切换呢?可以在配置文件中使用include实现,示例如下:
1 |
< castle > |
2 |
<!--<include uri="file://container-debug.config" />--> |
3 |
< include uri = "file://container-live.config" /> |
4 |
</ castle > |
include甚至可以包含assembly中的resource(嵌入assembly中的文件)
另外可以在配置文件中定义属性,然后在其他地方引用这些属性,例如定义属性:
1 |
< configuration > |
2 |
< properties > |
3 |
< myProperty >Live</ myProperty > |
4 |
</ properties > |
5 |
</ configuration > |
使用属性:
1 |
< component id = "whatConfig.service" type = "Windsor.Test.HolidayService, Windsor.Test" > |
2 |
< parameters > |
3 |
< Configuration >#{myProperty}</ Configuration > |
4 |
</ parameters > |
5 |
</ component > |
我们可以针对同一个服务配置多个实现方式,使用id获取各个实现方式的对象实例:
01 |
< component id = "reader.file1" type = "IoC.Tutorials.Part8.FileReader, IoC.Tutorials.Part8" > |
02 |
< parameters > |
03 |
< FileName >file1.txt</ FileName > |
04 |
</ parameters > |
05 |
</ component > |
06 |
< component id = "reader.file2" type = "IoC.Tutorials.Part8.FileReader, IoC.Tutorials.Part8" > |
07 |
< parameters > |
08 |
< FileName >file2.txt</ FileName > |
09 |
</ parameters > |
10 |
</ component > |
然后使用配置中的id来获取实例对象:
1 |
WindsorContainer container = new WindsorContainer( new XmlInterpreter()); |
2 |
FileReader defaultReader = container.Resolve<FileReader>(); |
3 |
FileReader file1Reader = container.Resolve<FileReader>( "reader.file1" ); |
4 |
FileReader file2Reader = container.Resolve<FileReader>( "reader.file2" ); |
我们可以使用container.Kernel.HasComponent(string key)方法在代码中判断特定的key是否有注册了服务
生命周期 Lifestyle, Lifecycle
Windsor容器中的对象其生命周期有以下几种方式:
Singleton: 单例模式
Transient: 临时对象模式,每次都创建一个新对象返回给请求者
PerThread: 在当前执行线程上为单例模式
Pooled: 用一个对象池管理请求对象,从对象池中返回对象实例
Custom: 实现Castle.MicroKernel.Lifestyle.ILifestyleManager或从Castle.MicroKernel.Lifestyle.AbstractLifestyleManager继承,实现自定义的对象生命周期管理
默认情况下,组件的生命周期为单例模式,可以通过配置文件进行设置:
1 |
<component id= "taxcalc.service" type= "Windsor.Test.TaxCalculator, Windsor.Test" lifestyle= "transient" /> |
也可以给class添加上[Castle.Core.Transient]、[Castle.Core.PerThread]等属性来设置组件的生命周期,从而忽略配置文件中的设置
Windsor支持Castle.Core.IInitializable和System.IDisposable接口,如果类实现了IInitializable接口,容器在创建对象实例之后会执行接口的Initialize方法;如果类实现了IDisposable接口,则在销毁对象的时候会执行Dispose方法
构造器注入
前面示例我们用的都是setter注入,下面示例使用构造器注入
有一个用于字符串编码的接口,该接口有2个实现:
01 |
public interface IEncoder |
02 |
{ |
03 |
string Encode( string source); |
04 |
} |
05 |
public class NullEncoder : IEncoder |
06 |
{ |
07 |
public string Encode( string source) |
08 |
{ |
09 |
return source; |
10 |
} |
11 |
} |
12 |
public class SillyEncoder : IEncoder |
13 |
{ |
14 |
private char [] _mixedUp = "YACBDFEGIHJLKMONPRSQTUWVXZ" .ToCharArray(); |
15 |
<BR> |
16 |
public string Encode( string source) |
17 |
{ |
18 |
string upperSource = source.ToUpper(); |
19 |
char [] encoded = new char [source.Length]; |
20 |
for ( int i = 0; i < encoded.Length; i++) |
21 |
{ |
22 |
encoded[i] = MapCharacter(upperSource[i]); |
23 |
} |
24 |
return new string (encoded); |
25 |
} |
26 |
<BR> |
27 |
private char MapCharacter( char ch) |
28 |
{ |
29 |
if ((ch >= 'A' ) && (ch <= 'Z' )) |
30 |
{ |
31 |
return _mixedUp[ch - 'A' ]; |
32 |
} |
33 |
return ch; |
34 |
} |
35 |
} |
然后有一个发送消息的类,其构造函数要求一个IEncode对象:
01 |
public class MessageSender |
02 |
{ |
03 |
private readonly IEncoder _encoder; |
04 |
private readonly string _from; |
05 |
public MessageSender( string from, IEncoder encoder) |
06 |
{ |
07 |
_from = from; |
08 |
_encoder = encoder; |
09 |
} |
10 |
public void SendMessage( string to, string body) |
11 |
{ |
12 |
Console.WriteLine( "to: {0}\r\nfrom: {1}\r\n\r\n{2}" , to, _from, _encoder.Encode(body)); |
13 |
} |
14 |
} |
使用Windsor可以实现:Windsor自动创建一个IEncoder对象提供给MessageSender的构造函数;在配置文件中需要指定from参数的值,否则Windsor将抛出异常无法创建MessageSender对象
使用的代码如下:
1 |
WindsorContainer container = new WindsorContainer( new XmlInterpreter()); |
2 |
MessageSender sender = container.Resolve<MessageSender>(); |
3 |
sender.SendMessage( "hammet" , "castle is great!" ); |
4 |
Console.ReadKey(); |
配置如下:
01 |
< component id = "encoder.silly" |
02 |
service = "Windsor.Test.IEncoder, Windsor.Test" |
03 |
type = "Windsor.Test.SillyEncoder, Windsor.Test" /> |
04 |
< component id = "encoder.null" |
05 |
service = "Windsor.Test.IEncoder, Windsor.Test" |
06 |
type = "Windsor.Test.NullEncoder, Windsor.Test" /> |
07 |
< component id = "messageSender" |
08 |
type = "Windsor.Test.MessageSender, Windsor.Test" > |
09 |
< parameters >MessageSender |
10 |
< from >[email protected]</ from > |
11 |
</ parameters > |
12 |
</ component > |
上面我们有2个IEncoder的实现,我们可以在配置文件中为MessageSender的构造函数指定使用哪一个实现类:
1 |
< component id = "messageSender" |
2 |
type = "Windsor.Test.MessageSender, Windsor.Test" > |
3 |
< parameters >MessageSender |
4 |
< from >[email protected]</ from > |
5 |
< encoder >${encoder.null}</ encoder > |
6 |
</ parameters > |
7 |
</ component > |
Factory Facilities
我们自己写的类完全由我们自己控制,因此我们可以让他们能够通过Windsor容器管理,但对于某些第三方提供的服务程序,可能构造函数存在额外的依赖性,使得我们无法通过配置直接使用Windsor容器来管理,这种情况下可以使用Windsor的Factory Facilities实现一个工厂,告诉Windsor使用我们的工厂来创建特定的服务对象实例
比如我们实现了下面这样一个工厂类,用来创建一个ISmsService服务对象:
01 |
public class SmsServiceFactory |
02 |
{ |
03 |
private string _userName; |
04 |
private string _password; |
05 |
private int _retryAttempts = 3; |
06 |
<BR> |
07 |
public SmsServiceFactory( string userName, string password) |
08 |
{ |
09 |
_userName = userName; |
10 |
_password = password; |
11 |
} |
12 |
public int RetryAttempts |
13 |
{ |
14 |
get { return _retryAttempts; } |
15 |
set { _retryAttempts = value; } |
16 |
} |
17 |
<BR> |
18 |
public ISmsService CreateService() |
19 |
{ |
20 |
SmsService service = new SmsService(); |
21 |
SmsService.SmsConfig config = new SmsService.SmsConfig(); |
22 |
config.SetCredentials(_userName, _password); |
23 |
config.RetryAttempts = _retryAttempts; |
24 |
service.SetConfig(config); |
25 |
return service; |
26 |
} |
27 |
} |
然后我们使用下面的配置,通过Windsor Factory Facilities指定我们所使用的工厂类:
01 |
< castle > |
02 |
< facilities > |
03 |
< facility |
04 |
id = "factorysupport" |
05 |
type = "Castle.Facilities.FactorySupport.FactorySupportFacility, Castle.MicroKernel" /> |
06 |
</ facilities > |
07 |
< components > |
08 |
< component id = "smsService.Factory" |
09 |
type = "Windsor.Test.SmsServiceFactory, Windsor.Test" > |
10 |
< parameters > |
11 |
< userName >joe</ userName > |
12 |
< password >secret</ password > |
13 |
</ parameters > |
14 |
</ component > |
15 |
< component id = "smsService.default" |
16 |
type = "Windsor.Test.ISmsService, Windsor.Test" |
17 |
factoryId = "smsService.Factory" |
18 |
factoryCreate = "CreateService" /> |
19 |
</ components > |
20 |
</ castle > |
使用的代码跟其他示例一样:
1 |
WindsorContainer container = new WindsorContainer( new XmlInterpreter()); |
2 |
ISmsService smsService = container.Resolve<ISmsService>(); |
3 |
smsService.SendMessage( "+465556555" , "testing testing...1.2.3" ); |