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

随着大规模的项目越来越多,许多项目都引入了依赖注入框架,其中最流行的有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>();
	}

	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时使用索引找到实现类型并创建实例。
这个实现很简单,但是有很多问题,例如

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

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

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

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

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

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

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

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

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

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

改进后的容器

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

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等,而是直接使用微软提供的标准接口。
虽然具体的实现方式离我们原来越远,但是了解一下它们的原理总是有好处的。

posted @ 2016-09-13 11:36  q303248153  阅读(2987)  评论(3编辑  收藏  举报