如何編寫一個簡單的依賴注入容器

隨着大規模的項目愈來愈多,許多項目都引入了依賴注入框架,其中最流行的有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

相關文章
相關標籤/搜索