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

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

容器的构想

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

var container = new Container();

container.Register<MyLogger, ILogger>();

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

最基础的容器

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

public class Container
{
	// service => implementation
	private IDictionary<Type, Type> TypeMapping { get; set; }

	public Container() {
		TypeMapping = new Dictionary<Type, Type>();
	}

	public 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时使用索引找到实现类型并建立实例。
这个实现很简单,可是有不少问题,例如web

  • 一个服务类型不能对应多个实现类型
  • 没有对实例进行生命周期管理
  • 没有实现构造函数注入

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

要让一个服务类型对应多个实现类型,能够把TypeMapping改成c#

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

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

IDictionary<Type, IList<Func<object>>> Factories { get; set; }

有时候咱们会想让用户在配置文件中切换实现类型,这时若是把键类型改为服务类型+字符串,实现起来会简单不少。
Resolve能够这样用: Resolve<Service>(serviceKey: Configuration["ImplementationName"])markdown

IDictionary<Tuple<Type, string>, IList<Func<object>>> Factories { get; set; }

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

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

改进后的容器

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

public class Container
{
	private IDictionary<Tuple<Type, string>, IList<Func<object>>> Factories { get; set; }

	public Container() {
		Factories = new Dictionary<Tuple<Type, string>, IList<Func<object>>>();
	}

	public void Register<TImplementation, TService>(string serviceKey = null)
		where TImplementation : TService
	{
		var key = Tuple.Create(typeof(TService), serviceKey);
		IList<Func<object>> factories;
		if (!Factories.TryGetValue(key, 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>(string serviceKey = null)
	{
		var key = Tuple.Create(typeof(TService), serviceKey);
		var factory = Factories[key].Single();
		return (TService)factory();
	}

	public IEnumerable<TService> ResolveMany<TService>(string serviceKey = null)
	{
		var key = Tuple.Create(typeof(TService), serviceKey);
		IList<Func<object>> factories;
		if (!Factories.TryGetValue(key, 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, bool singleton) {
	if (!singleton)
		return originalFactory;
	object value = null;
	return () =>
	{
		if (value == null)
			value = originalFactory();
		return value;
	};
}

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

实现构造函数注入

如下面代码为例

public class MyLogWriter : ILogWriter
{
	public void Write(string str) {
		Console.WriteLine(str);
	}
}

public class MyLogger : ILogger
{
	ILogWriter _writer;
	
	public MyLogger(ILogWriter writer) {
		_writer = writer;
	}
	
	public void Log(string message) {
		_writer.Write("[ Log ] " + message);
	}
}

static void Main(string[] args) {
	var container = 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#中的反射机制。

把上面代码中的

var factory = Expression.Lambda<Func<object>>(Expression.New(typeof(TImplementation))).Compile();

换成

private Func<object> BuildFactory(Type type) {
	// 获取类型的构造函数
	var constructor = type.GetConstructors().FirstOrDefault();
	// 生成构造函数中的每一个参数的表达式
	var argumentExpressions = new List<Expression>();
	foreach (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, typeof(string))));
		}
		else
		{
			// 等于调用this.Resolve<TParameter>();
			argumentExpressions.Add(Expression.Call(
				Expression.Constant(this), "Resolve",
				new [] { parameterType },
				Expression.Constant(null, typeof(string))));
		}
	}
	// 构建new表达式并编译到委托
	var newExpression = Expression.New(constructor, argumentExpressions);
	return Expression.Lambda<Func<object>>(newExpression).Compile();
}

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

完整代码

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

public interface ILogWriter
{
	void Write(string text);
}

public class MyLogWriter : ILogWriter
{
	public void Write(string str) {
		Console.WriteLine(str);
	}
}

public interface ILogger
{
	void Log(string message);
}

public class MyLogger : ILogger
{
	ILogWriter _writer;

	public MyLogger(ILogWriter writer) {
		_writer = writer;
	}

	public void Log(string message) {
		_writer.Write("[ Log ] " + message);
	}
}

static void Main(string[] args) {
	var container = new Container();
	container.Register<MyLogWriter, ILogWriter>();
	container.Register<MyLogger, ILogger>();
	var logger = container.Resolve<ILogger>();
	logger.Log("asdasdas");
}

public class Container
{
	private IDictionary<Tuple<Type, string>, IList<Func<object>>> Factories { get; set; }

	public Container() {
		Factories = new Dictionary<Tuple<Type, string>, IList<Func<object>>>();
	}

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

	private Func<object> BuildFactory(Type type) {
		// 获取类型的构造函数
		var constructor = type.GetConstructors().FirstOrDefault();
		// 生成构造函数中的每一个参数的表达式
		var argumentExpressions = new List<Expression>();
		foreach (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, typeof(string))));
			}
			else
			{
				// 等于调用this.Resolve<TParameter>();
				argumentExpressions.Add(Expression.Call(
					Expression.Constant(this), "Resolve",
					new [] { parameterType },
					Expression.Constant(null, typeof(string))));
			}
		}
		// 构建new表达式并编译到委托
		var newExpression = Expression.New(constructor, argumentExpressions);
		return Expression.Lambda<Func<object>>(newExpression).Compile();
	}

	public void Register<TImplementation, TService>(string serviceKey = null, bool singleton = false)
		where TImplementation : TService
	{
		var key = Tuple.Create(typeof(TService), serviceKey);
		IList<Func<object>> factories;
		if (!Factories.TryGetValue(key, out factories))
		{
			factories = new List<Func<object>>();
			Factories[key] = factories;
		}
		var factory = BuildFactory(typeof(TImplementation));
		WrapFactory(factory, singleton);
		factories.Add(factory);
	}

	public TService Resolve<TService>(string serviceKey = null)
	{
		var key = Tuple.Create(typeof(TService), serviceKey);
		var factory = Factories[key].Single();
		return (TService)factory();
	}

	public IEnumerable<TService> ResolveMany<TService>(string serviceKey = null)
	{
		var key = Tuple.Create(typeof(TService), serviceKey);
		IList<Func<object>> factories;
		if (!Factories.TryGetValue(key, out factories))
		{
			yield break;
		}
		foreach (var factory in factories)
		{
			yield return (TService)factory();
		}
	}
}

写在最后

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

  • 不支持线程安全
  • 不支持非泛型的注册和解决
  • 不支持只用于指定范围内的单例
  • 不支持成员注入
  • 不支持动态代理实现AOP

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

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

 

 

出处:https://www.cnblogs.com/zkweb/p/5867820.html

相关文章
相关标签/搜索