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