zoukankan      html  css  js  c++  java
  • 如何编写一个简单的依赖注入容器

     

     

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

  • 相关阅读:
    VIJOS-P1340 拯救ice-cream(广搜+优先级队列)
    uva 11754 Code Feat
    uva11426 GCD Extreme(II)
    uvalive 4119 Always an Interger
    POJ 1442 Black Box 优先队列
    2014上海网络赛 HDU 5053 the Sum of Cube
    uvalive 4795 Paperweight
    uvalive 4589 Asteroids
    uvalive 4973 Ardenia
    DP——数字游戏
  • 原文地址:https://www.cnblogs.com/cheari/p/15336570.html
Copyright © 2011-2022 走看看