加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

如何编写一个简单的依赖注入容器

发布时间:2020-12-14 05:12:17 所属栏目:百科 来源:网络整理
导读:随着大规模的项目越来越多,许多项目都引入了依赖注入框架,其中最流行的有Castle Windsor,Autofac和Unity Container。 微软在最新版的Asp.Net Core中自带了依赖注入的功能,有兴趣可以查看这里。 关于什么是依赖注入容器网上已经有很多的文章介绍,这里我将

随着大规模的项目越来越多,许多项目都引入了依赖注入框架,其中最流行的有Castle Windsor,Autofac和Unity Container。
微软在最新版的Asp.Net Core中自带了依赖注入的功能,有兴趣可以查看这里。
关于什么是依赖注入容器网上已经有很多的文章介绍,这里我将重点讲述如何实现一个自己的容器,可以帮助你理解依赖注入的原理。

容器的构想

在编写容器之前,应该先想好这个容器如何使用。
容器允许注册服务和实现类型,允许从服务类型得出服务的实例,它的使用代码应该像

var container = new Container();

container.Register<MyLogger,ILogger>();var logger = container.Resolve<ILogger>();

最基础的容器

在上面的构想中,Container类有两个函数,一个是Register,一个是Resolve
容器需要在Register时关联ILogger接口到MyLogger实现,并且需要在Resolve时知道应该为ILogger生成MyLogger的实例。
以下是实现这两个函数最基础的代码

public class Container{ ? ?// service => implementation
 ? ?private IDictionary<Type,Type> TypeMapping { get; set; } ? ?public Container() ? ?{
 ? ? ? ?TypeMapping = new Dictionary<Type,Type>();
 ? ?} ? ?void Register<TImplementation,TService>() ? ? ? ?where TImplementation : TService
 ? ?{
 ? ? ? ?TypeMapping[typeof(TService)] = typeof(TImplementation);
 ? ?} ? ?public TService Resolve<TService>()
 ? ?{ ? ? ? ?var implementationType = TypeMapping[typeof(TService)]; ? ? ? ?return (TService)Activator.CreateInstance(implementationType);
 ? ?}
}

Container在内部创建了一个服务类型(接口类型)到实现类型的索引,Resolve时使用索引找到实现类型并创建实例。
这个实现很简单,但是有很多问题,例如

  • 一个服务类型不能对应多个实现类型

  • 没有对实例进行生命周期管理

  • 没有实现构造函数注入

改进容器的构想 - 类型索引类型

要让一个服务类型对应多个实现类型,可以把TypeMapping改为

IDictionary<Type,IList<Type>> TypeMapping { set; }

如果另外提供一个保存实例的变量,也能实现生命周期管理,但显得稍微复杂了。
这里可以转换一下思路,把{服务类型=>实现类型}改为{服务类型=>工厂函数},让生命周期的管理在工厂函数中实现。

object>>> Factories { 有时候我们会想让用户在配置文件中切换实现类型,这时如果把键类型改成服务类型+字符串,实现起来会简单很多。
Resolve可以这样用:?Resolve<Service>(serviceKey: Configuration["ImplementationName"])

IDictionary<Tuple<Type,string>,255);">set; }

改进容器的构想 - Register和Resolve的处理

在确定了索引类型后,RegisterResolve的处理都应该随之改变。
Register注册时应该首先根据实现类型生成工厂函数,再把工厂函数加到服务类型对应的列表中。
Resolve解决时应该根据服务类型找到工厂函数,然后执行工厂函数返回实例。

改进后的容器

这个容器新增了一个ResolveMany函数,用于解决多个实例。
另外还用了Expression.Lambda编译工厂函数,生成效率会比Activator.CreateInstance快数十倍。

Container{ ? ?private IDictionary<Tuple<Type,21);">Container() ? ?{
 ? ? ? ?Factories = new Dictionary<Tuple<Type,255);">object>>>();
 ? ?} ? ?string serviceKey = null) ? ? ? ?where TImplementation : TService
 ? ?{ ? ? ? ?var key = Tuple.Create(typeof(TService),serviceKey);
 ? ? ? ?IList<Func<object>> factories; ? ? ? ?if (!Factories.TryGetValue(key,255);">out factories))
 ? ? ? ?{
 ? ? ? ? ? ?factories = new List<Func<object>>();
 ? ? ? ? ? ?Factories[key] = factories;
 ? ? ? ?} ? ? ? ?var factory = Expression.Lambda<Func<object>>(Expression.New(typeof(TImplementation))).Compile();
 ? ? ? ?factories.Add(factory);
 ? ?} ? ?public TService Resolve<TService>(null)
 ? ?{ ? ? ? ?var factory = Factories[key].Single(); ? ? ? ?return (TService)factory();
 ? ?} ? ?public IEnumerable<TService> ResolveMany<TService>(out factories))
 ? ? ? ?{ ? ? ? ? ? ?yield break;
 ? ? ? ?} ? ? ? ?foreach (var factory in factories)
 ? ? ? ?{ ? ? ? ? ? ?yield return (TService)factory();
 ? ? ? ?}
 ? ?}
}

改进后的容器仍然有以下的问题

  • 没有对实例进行生命周期管理

  • 没有实现构造函数注入

实现实例的单例

以下面代码为例

var logger_a = container.Resolve<ILogger>();var logger_b = container.Resolve<ILogger>();

使用上面的容器执行这段代码时,logger_alogger_b是两个不同的对象,如果想要每次Resolve都返回同样的对象呢?
我们可以对工厂函数进行包装,借助闭包(Closure)的力量可以非常简单的实现。

private Func<object> WrapFactory(Func<object> originalFactory,255);">bool singleton){ ? ?if (!singleton) ? ? ? ?return originalFactory; ? ?object value = null; ? ?return () =>
 ? ?{ ? ? ? ?if (value == null) ? ? ? ? ? ?value = originalFactory(); ? ? ? ?return value;
 ? ?};
}

添加这个函数后在Register中调用factory = WrapFactory(factory,singleton);即可。
完整代码将在后面放出,接下来再看如何实现构造函数注入。

实现构造函数注入

MyLogWriter : ILogWriter{ ? ?void Write(string str) ? ?{ ? ? ? ?Console.WriteLine(str); ? ?} }MyLogger : ILogger{ ? ?ILogWriter _writer; ? ? ? ?MyLogger(ILogWriter writer) ? ?{ ? ? ? ?_writer = writer; ? ?} ? ? ? ?Log(string message) ? ?{ ? ? ? ?_writer.Write("[ Log ] " + message); ? ?} }static Main(string[] args){ ? ?new Container(); ? ?container.Register<MyLogWriter,ILogWriter>(); ? ?container.Register<MyLogger,ILogger>(); ? ? ? ?var logger = container.Resolve<ILogger>(); ? ?logger.Log("Example Message"); }

在这段代码中,MyLogger构造时需要一个ILogWriter的实例,但是这个实例我们不能直接传给它。
这样就要求容器可以自动生成ILogWriter的实例,再传给MyLogger以生成MyLogger的实例。
要实现这个功能需要使用c#中的反射机制。

把上面代码中的

typeof(TImplementation))).Compile();

换成

BuildFactory(Type type){ ? ?// 获取类型的构造函数
 ? ?var constructor = type.GetConstructors().FirstOrDefault(); ? ?// 生成构造函数中的每个参数的表达式
 ? ?var argumentExpressions = new List<Expression>(); ? ?var parameter in constructor.GetParameters())
 ? ?{ ? ? ? ?var parameterType = parameter.ParameterType; ? ? ? ?if (parameterType.IsGenericType &&
 ? ? ? ? ? ?parameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
 ? ? ? ?{ ? ? ? ? ? ?// 等于调用this.ResolveMany<TParameter>();
 ? ? ? ? ? ?argumentExpressions.Add(Expression.Call(
 ? ? ? ? ? ? ? ?Expression.Constant(this),"ResolveMany",? ? ? ? ? ? ? ?parameterType.GetGenericArguments(),? ? ? ? ? ? ? ?Expression.Constant(null,255);">typeof(string))));
 ? ? ? ?} ? ? ? ?else
 ? ? ? ?{ ? ? ? ? ? ?// 等于调用this.Resolve<TParameter>();
 ? ? ? ? ? ?argumentExpressions.Add(Expression.Call(
 ? ? ? ? ? ? ? ?Expression.Constant("Resolve",? ? ? ? ? ? ? ?new [] { parameterType },255);">string))));
 ? ? ? ?}
 ? ?} ? ?// 构建new表达式并编译到委托
 ? ?var newExpression = Expression.New(constructor,argumentExpressions); ? ?return Expression.Lambda<Func<object>>(newExpression).Compile();
}

这段代码通过反射获取了构造函数中的所有参数,并对每个参数使用ResolveResolveMany解决。
值得注意的是参数的解决是延迟的,只有在构建MyLogger的时候才会构建MyLogWriter,这样做的好处是注入的实例不一定需要是单例。
用表达式构建的工厂函数解决的时候的性能会很高。

完整代码

容器和示例的完整代码如下

interface string text);
}ILogger{ ? ?string message);
}ILogger{
 ? ?ILogWriter _writer; ? ?MyLogger(ILogWriter writer) ? ?{
 ? ? ? ?_writer = writer;
 ? ?} ? ?"asdasdas");
}object>>>();
 ? ?} ? ?bool singleton) ? ?{ ? ? ? ?if (!singleton) ? ? ? ? ? ?return originalFactory; ? ? ? ?null; ? ? ? ?return () =>
 ? ? ? ?{ ? ? ? ? ? ?null) ? ? ? ? ? ? ? ?value = originalFactory(); ? ? ? ? ? ?value;
 ? ? ? ?};
 ? ?} ? ?BuildFactory(Type type) ? ?{ ? ? ? ?// 获取类型的构造函数
 ? ? ? ?var constructor = type.GetConstructors().FirstOrDefault(); ? ? ? ?// 生成构造函数中的每个参数的表达式
 ? ? ? ?new List<Expression>(); ? ? ? ?in constructor.GetParameters())
 ? ? ? ?{ ? ? ? ? ? ?var parameterType = parameter.ParameterType; ? ? ? ? ? ?if (parameterType.IsGenericType &&
 ? ? ? ? ? ? ? ?parameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
 ? ? ? ? ? ?{ ? ? ? ? ? ? ? ?// 等于调用this.ResolveMany<TParameter>();
 ? ? ? ? ? ? ? ?argumentExpressions.Add(Expression.Call(
 ? ? ? ? ? ? ? ? ? ?Expression.Constant(string))));
 ? ? ? ? ? ?} ? ? ? ? ? ?else
 ? ? ? ? ? ?{ ? ? ? ? ? ? ? ?// 等于调用this.Resolve<TParameter>();
 ? ? ? ? ? ? ? ?argumentExpressions.Add(Expression.Call(
 ? ? ? ? ? ? ? ? ? ?Expression.Constant(string))));
 ? ? ? ? ? ?}
 ? ? ? ?} ? ? ? ?// 构建new表达式并编译到委托
 ? ? ? ?object>>(newExpression).Compile();
 ? ?} ? ?bool singleton = false) ? ? ? ?var factory = BuildFactory(typeof(TImplementation));
 ? ? ? ?WrapFactory(factory,singleton);
 ? ? ? ?factories.Add(factory);
 ? ?} ? ?factory();
 ? ? ? ?}
 ? ?}
}

写在最后

这个容器实现了一个依赖注入容器应该有的主要功能,但是还是有很多不足的地方,例如

  • 不支持线程安全

  • 不支持非泛型的注册和解决

  • 不支持只用于指定范围内的单例

  • 不支持成员注入

  • 不支持动态代理实现AOP

我在ZKWeb网页框架中也使用了自己编写的容器,只有300多行但是可以满足实际项目的使用。
完整的源代码可以查看这里和这里。

微软从.Net Core开始提供了DependencyInjection的抽象接口,这为依赖注入提供了一个标准。
在将来可能不会再需要学习Castle Windsor,Autofac等,而是直接使用微软提供的标准接口。
虽然具体的实现方式离我们原来越远,但是了解一下它们的原理总是有好处的

来源:http://www.cnblogs.com/zkweb/archive/2016/09/13/5867820.html

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读