俄罗斯贵宾会-俄罗斯贵宾会官网
做最好的网站

如何编写一个简单的依赖注入容器俄罗斯贵宾会:

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

这段代码通过反射获取了构造函数中的所有参数,并对每个参数使用Resolve俄罗斯贵宾会,或ResolveMany解决。
值得注意的是参数的解决是延迟的,只有在构建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等,而是直接使用微软提供的标准接口。
虽然具体的实现方式离我们原来越远,但是了解一下它们的原理总是有好处的。

本文由俄罗斯贵宾会发布于编程,转载请注明出处:如何编写一个简单的依赖注入容器俄罗斯贵宾会:

您可能还会对下面的文章感兴趣: