NopCommerce使用Autofac实现依赖注入
文章内容:
- 什么是依赖注入
- NopCommerce网站中使用Autofac
什么是依赖注入
依赖注入也叫"控制反转(IOC)",目的在于将组件的配置与使用分离开来。
举个简单的例子:我饿了,要叫外卖吃,我只要打开APP,选好吃的,然后付钱,等着外卖到了就可以吃了。至于外卖员怎么送外卖,是步行送还是骑车送, 是走什么路线,这些我不需要知道,也无法知道。
我们从代码来看,理解起来会更清晰一点
首先,我给送餐定义了一个接口ITakeout:
//送餐接口,SendFood送餐的方法定义
public interface ITakeout
{
string SendFood()
}
然后,订餐的实现类:
public class OnlineOrdering
{
private ITakeout _takeoutService;
public string Ordering()
{
APP订餐付款();
//调用_takeoutService的SendFood方法获得食物food, _takeoutService的具体实现类我是不知道的。
string food = _takeoutService.SendFood();
return food;
}
}
在上面的例子中,虽然我知道有送餐这个过程,但是我并不知道它的具体实现方式,所以我不能new一个实例给_takeoutService属性。退一步讲,就算我知道一个类TakeoutByFoot实现了这个接口,于是我将它赋值给 _takeoutService, 将构造函数改为如下代码:
public OnlineOrdering()
{
this._takeoutService=new TakeoutByFoot();
}
这样做的问题是,如果明天换了个外卖员,他是骑车送餐的,后天又换了一个,他是开车送的。那我不是要频繁的更改OnlineOrdering类。这就造成了耦合。
依赖注入的好处在于我只要定义一个送餐的接口,然后就可以使用它,至于接口的实现类是什么我不需要知道,因为IOC框架会自动为接口赋值。将OnlineOrdering构造方法改为:
public OnlineOrdering(ITakeout takeoutService)
{
this._takeoutService = takeoutService;
}
这样IOC框架就可以用构造子注入的方式,为 _takeoutService 添加具体实现类。至于是实现那个类,这个可以交给配置来做。如何配置这个后面会讲。这就是”使用”与 “配置”分离。
有人会说为什么不直接在外部给 _takeoutService 属性赋值,那样的话 _takeoutService 属性就必须是public ,可我并不想将这个属性暴露给外部。另外,如果ITakeout实现类中还定义了其他引用型属性,我就必须一层层赋值,这不是理想的方式。
NopCommerce网站中使用Autofac
官方网站地址
NopCommerce的官方网站,可以下载源码,详见[http://www.nopcommerce.com/][1].
Autofac的官方网站,详见[http://autofac.org/][1].
NopCommerce是国外的一个高质量的开源b2c网站系统。该网站使用了名为Autofac的依赖注入框架。Autofac的使用方法这里就不做介绍了,可以去官网上看,我只是简单分析一下NopCommerce是如何使用Autofac框架的。(本人水平有限,这只是我自己的理解,如果有不对的地方,欢迎指正)
代码在Nop.Core.Infrastructure和Nop.Core.Infrastructure.DependencyManagement命名空间。主要分为两个模块:
ContainerManager 的功能有:管理依赖注入的容器,实例化服务。
Engine 的功能有:管理ContainerManager, 实例化服务,在程序启动时注册各种服务。
另外还有一些工具模块,这里只简单说明。具体实现可以看代码:
模块 | 作用 |
---|---|
TypeFinder | 通过反射,找到某个Type的类,子类或实现类 |
Singleton | 用于存储单例 |
Config | 系统设置 |
首先,来看ContainerManager 类,部分代码:
public class ContainerManager
{
private readonly IContainer _container;
public ContainerManager(IContainer container)
{
this._container = container;
}
public virtual IContainer Container
{
get
{
return this._container;
}
}
//........各种Resolve方法,略
}
ContainerManager 中有个只读属性IContainer _container这是依赖注入容器,我们注册的服务都有保存到 _container中,同时实例化服务也是从 _container中获取。它相当于一个仓库。ContainerManager 通过在构造函数中传入IContainer 的实例,为 _container赋值。同时,ContainerManager 也提供了Resolve方法,为在 _container中注册的服务提供实例。
再来看下Engion模块,Engion模块定义了一个接口IEngine,该接口的实现类NopEngine,以及用来保存NopEngine单例的EngineContext。
IEngine定义如下:
public interface IEngine
{
ContainerManager ContainerManager { get; }
void Initialize(NopConfig config);
T Resolve<T>() where T : class;
object Resolve(Type type);
T[] ResolveAll<T>();
}
很清楚看到,IEngion有个ContainerManager 类型的属性,通过这个属性我们可以获得IContainer , 看一下NopEngine的代码就会发现ContainerManager 是在执行Initialize方法的时候赋值的。IEngion还提供了Resolve方法,其实就是调用ContainerManager 的Resolve方法,向外界提供服务。
IEngion的实现类NopEngine:
public class NopEngine : IEngine
{
private ContainerManager _containerManager;
protected virtual void RegisterDependencies(NopConfig config)
{
// ①
var builder = new ContainerBuilder();
var container = builder.Build();
this._containerManager = new ContainerManager(container);
//dependencies WebAppTypeFinder是ITypeFinder的实现类,属于TypeFinder 模块。暂时知道它是干什么的就行了。
var typeFinder = new WebAppTypeFinder();
//因为ContainerBuilder只能Build()或Update()一次,所以要新建一个
builder = new ContainerBuilder();
// ②
builder.RegisterInstance(this).As<IEngine>().SingleInstance();
builder.RegisterInstance(typeFinder).As<ITypeFinder>().SingleInstance();
builder.Update(container);
//register dependencies provided by other assemblies ③
builder = new ContainerBuilder();
var drTypes = typeFinder.FindClassesOfType<IDependencyRegistrar>();
var drInstances = new List<IDependencyRegistrar>();
foreach (var drType in drTypes)
drInstances.Add((IDependencyRegistrar) Activator.CreateInstance(drType));
//sort
drInstances = drInstances.AsQueryable().OrderBy(t => t.Order).ToList();
foreach (var dependencyRegistrar in drInstances)
dependencyRegistrar.Register(builder, typeFinder, config);
builder.Update(container);
//set dependency resolver ④
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
public void Initialize(NopConfig config)
{
//register dependencies
RegisterDependencies(config);
//.......执行启动任务,与依赖注入无关,略
}
//.............Resolve方法,略
public ContainerManager ContainerManager
{
get { return _containerManager; }
}
}
以上代码我只截取了比较重要的部分,NopEngine类的重点在Initialize方法,该方法调用了RegisterDependencies方法,从方法名可以知道用来注册依赖项(服务)。
详细过程如下:
① build 了一个IContainer 容器,然后用该容器初始化ContainerManager ,以后注册的服务都将保存在这个容器中。由于NopEngine是单例,所以IContainer 容器也只有一个。
②将自己注册为一个单例,TypeFinder 模块也注册一个单例。注意builder.Update(container)这句一定要加,它用来将注册的服务保存到容器中去。
③首先来看这句代码var drTypes = typeFinder.FindClassesOfType<IDependencyRegistrar>();
这句的意思是找出所有IDependencyRegistrar接口的实现类。IDependencyRegistrar的定义如下:
public interface IDependencyRegistrar
{
void Register(ContainerBuilder builder, ITypeFinder typeFinder, NopConfig config);
int Order { get; }
}
然后再看这两句代码:
1.foreach (var drType in drTypes)
drInstances.Add((IDependencyRegistrar) Activator.CreateInstance(drType));
2.foreach (var dependencyRegistrar in drInstances)
dependencyRegistrar.Register(builder, typeFinder, config);
builder.Update(container);
1表示实例化所有IDependencyRegistrar实现类,2表示执行IDependencyRegistrar实现类的Register方法,并且将builder作为参数传进去。然后将builder更新到container容器中。
这步操作有什么用呢? 举例一个IDependencyRegistrar的实现类:
public class DependencyRegistrar : IDependencyRegistrar
{
public void Register(ContainerBuilder builder, ITypeFinder finder ,FLOWConfig config)
{
builder.RegisterType<TakeoutByFoot>().As<ITakeout>().InstancePerLifetimeScope();
}
public int Order
{
get { return 1; }
}
}
DependencyRegistrar 可以通过Register方法注册自己需要的服务。这样做的好处是,不同的服务使用者可以定义自己的IDependencyRegistrar实现类,然后注册自己需要的服务,这样服务使用起来非常方便,更换服务也很快捷。而服务提供者,我只要提供一个接口给使用者,至于使用者如何配置,我可以不用考虑。拿开始的例子说就是,外卖员如何送餐由他自己选择,而不是我来选择一个骑摩托的外卖员。
④set dependency resolver
NopEngine理解了以后,剩下就很好理解了。EngionContext提供了NopEngine的单例,代码如下:
public class EngineContext
{
#region Methods
[MethodImpl(MethodImplOptions.Synchronized)]
public static IEngine Initialize(bool forceRecreate)
{
if (Singleton<IEngine>.Instance == null || forceRecreate)
{
Singleton<IEngine>.Instance = new NopEngine();
var config = ConfigurationManager.GetSection("NopConfig") as NopConfig;
Singleton<IEngine>.Instance.Initialize(config);
}
return Singleton<IEngine>.Instance;
}
public static void Replace(IEngine engine)
{
Singleton<IEngine>.Instance = engine;
}
#endregion
#region Properties
public static IEngine Current
{
get
{
if (Singleton<IEngine>.Instance == null)
{
Initialize(false);
}
return Singleton<IEngine>.Instance;
}
}
#endregion
}
可以看出,Singleton.Instance保存了IEngine的单例,Current用来获取IEngine单例。Initialize方法在IEngine单例为空或强制Recreate的情况下,初始化IEngine引擎。Singleton<IEngine>.Instance.Initialize(config);
这句代码表示IEngine引擎初始化,在初始化过程中,会注册服务。
最后,在程序启动时初始化IEngine引擎就会自动注册服务了:
protected void Application_Start()
{
........
//initialize engine context
EngineContext.Initialize(false);
.......
}