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


     

     

    随着大规模的项目越来越多,许多项目都引入了依赖注入框架,其中最流行的有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等,而是直接使用微软提供的标准接口。
    虽然具体的实现方式离我们原来越远,但是了解一下它们的原理总是有好处的。

  • 相关阅读:
    子序列自动机学习笔记
    P4709 信息传递 解题报告
    斯坦纳树学习笔记
    NOIP2021 游记
    P5206 [WC2019]数树 解题报告
    CF1205D Almost All 解题报告
    设计模式原来如此策略模式(Strategy Pattern)
    再次站起,继续开博
    Java原来如此反射机制
    Java原来如此随机数
  • 原文地址:https://www.cnblogs.com/cheari/p/15336570.html
Copyright © 2020-2023  润新知