[Asp.net 5] DependencyInjection项目代码分析4-微软的实现(中)

在 DependencyInjection项目代码分析4-微软的实现(上)中介绍了“ServiceTable”、“ServiceEntry”、“IGenericService”、“IService”、“IServiceCallSite”,这篇介绍下“IGenericService、"IService"、"IServiceCallSite"实现类

GenericService

做为IGenericService的唯一实现类,该类言简意赅,请看代码:

技术分享
internal class GenericService : IGenericService
    {
        private readonly ServiceDescriptor _descriptor;

        public GenericService(ServiceDescriptor descriptor)
        {
            _descriptor = descriptor;
        }

        public ServiceLifetime Lifetime
        {
            get { return _descriptor.Lifetime; }
        }

        public IService GetService(Type closedServiceType)
        {
            Type[] genericArguments = closedServiceType.GetTypeInfo().GenericTypeArguments;
            Type closedImplementationType =
                _descriptor.ImplementationType.MakeGenericType(genericArguments);

            var closedServiceDescriptor = new ServiceDescriptor(closedServiceType, closedImplementationType, Lifetime);
            return new Service(closedServiceDescriptor);
        }
    }
GenericService

[var b=typeof(List<B>).GetTypeInfo().GenericTypeArguments会返回{Type(B)}(也就是包含B类型的数组);

typeof(List<>).MakeGenericType(b) 会返回List<B>类型]

所以GenericService的GetService方法的入参是已经“实参化”的泛型类型,类似于IList<B>,而内部的_descriptor.ImplementationType类型则是非“实参化”的泛型,类似于List<>,所以该方法会在一个类似于[Service(IList<B>,List<B>)]的Service。

InstanceService

这个类实现IService, IServiceCallSite俩个接口。直接将已经有的实例作为注入的类型。

技术分享
internal class InstanceService : IService, IServiceCallSite
    {
        private readonly ServiceDescriptor _descriptor;

        public InstanceService(ServiceDescriptor descriptor)
        {
            _descriptor = descriptor;
        }

        public IService Next { get; set; }

        public ServiceLifetime Lifetime
        {
            get { return _descriptor.Lifetime; }
        }

        public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain)
        {
            return this;
        }

        public object Invoke(ServiceProvider provider)
        {
            return _descriptor.ImplementationInstance;
        }

        public Expression Build(Expression provider)
        {
            return Expression.Constant(_descriptor.ImplementationInstance, _descriptor.ServiceType);
        }
    }
InstanceService

FactoryService

这个类也实现IService, IServiceCallSite俩个接口。直接将已经有的工厂作为注入的类型。

技术分享
internal class FactoryService : IService, IServiceCallSite
    {
        private readonly ServiceDescriptor _descriptor;

        public FactoryService(ServiceDescriptor descriptor)
        {
            _descriptor = descriptor;
        }

        public IService Next { get; set; }

        public ServiceLifetime Lifetime
        {
            get { return _descriptor.Lifetime; }
        }

        public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain)
        {
            return this;
        }

        public object Invoke(ServiceProvider provider)
        {
            return _descriptor.ImplementationFactory(provider);
        }

        public Expression Build(Expression provider)
        {
            Expression<Func<IServiceProvider, object>> factory =
                serviceProvider => _descriptor.ImplementationFactory(serviceProvider);

            return Expression.Invoke(factory, provider);
        }
    }
FactoryService

Service

InstanceService、FactoryService不同Service只实现IService接口,而Service内部包含三个实现IServiceCallSite的内部类,[上一篇中,有关于IService和IServiceCallSite为什么不合并成同一个接口,此处就有了答案,将代码彻底的解耦;当然多个接口设计可能是当初就是这么设计,也可能进一步重构修改成这样的]。

先看下程序的主要架构代码:

 internal class Service : IService
    {
        private readonly ServiceDescriptor _descriptor;

        public Service(ServiceDescriptor descriptor)
        {
            _descriptor = descriptor;
        }

        public IService Next { get; set; }

        public ServiceLifetime Lifetime
        {
            get { return _descriptor.Lifetime; }
        }


         public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain);
}

现在主要问题就围绕着方法( IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain))展开了。

我们先回想下ServiceTable类的构造函数

技术分享
public ServiceTable(IEnumerable<ServiceDescriptor> descriptors)
        {
            _services = new Dictionary<Type, ServiceEntry>();
            _genericServices = new Dictionary<Type, List<IGenericService>>();

            foreach (var descriptor in descriptors)
            {
                var serviceTypeInfo = descriptor.ServiceType.GetTypeInfo();
                if (serviceTypeInfo.IsGenericTypeDefinition)
                {
                    Add(descriptor.ServiceType, new GenericService(descriptor));
                }
                else if (descriptor.ImplementationInstance != null)
                {
                    Add(descriptor.ServiceType, new InstanceService(descriptor));
                }
                else if (descriptor.ImplementationFactory != null)
                {
                    Add(descriptor.ServiceType, new FactoryService(descriptor));
                }
                else
                {
                    Add(descriptor.ServiceType, new Service(descriptor));
                }
            }
        }
ServiceTable构造函数

对于ServiceDescriptor对象,只有提供ImplementationInstance和ImplementationFactory都没有提供的时候,也就是只提供ImplementationType才会创建Service类。所以在Service类内部想创建对象的实例,只有通过反射创建对象这一条路可走了。如果通过反射创建,就分几种情况需要考虑了(静态构造函数不再此考虑之内):

  1. 有公开的无参构造函数
  2. 只有一个公开的构造函数,并且有非无参构造函数
  3. 有多个构造函数

下面是Service内部的实现代码,可能是我下的版本问题,这个方法存在巨大问题,首先是这里面标识着一个大大的[todo]

[“ // TODO: actual service-fulfillment constructor selection”,也就是说第三种情况,开源的代码并没有实现,所以我们使用的时候,注入的类中不要有2个能够公开访问的非静态构造函数(虽然日常我们开发的一般都不会有,或者会按照规范进行开发,但是第三方的类是保不住的)。]

技术分享
 public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain)
        {
            ConstructorInfo[] constructors = _descriptor.ImplementationType.GetTypeInfo()
                .DeclaredConstructors
                .Where(IsInjectable)
                .ToArray();

            // TODO: actual service-fulfillment constructor selection
            if (constructors.Length == 1)
            {
                ParameterInfo[] parameters = constructors[0].GetParameters();
                IServiceCallSite[] parameterCallSites = new IServiceCallSite[parameters.Length];
                for (var index = 0; index != parameters.Length; ++index)
                {
                    parameterCallSites[index] = provider.GetServiceCallSite(parameters[index].ParameterType, callSiteChain);
                
                    if (parameterCallSites[index] == null && parameters[index].HasDefaultValue)
                    {
                        parameterCallSites[index] = new ConstantCallSite(parameters[index].DefaultValue);
                    }
                    if (parameterCallSites[index] == null)
                    {
                        throw new InvalidOperationException(Resources.FormatCannotResolveService(
                                parameters[index].ParameterType, 
                                _descriptor.ImplementationType));
                    }
                }
                return new ConstructorCallSite(constructors[0], parameterCallSites);
            }

            return new CreateInstanceCallSite(_descriptor);
        }
CreateCallSite

 即使是没有实现第三种情况下,这个方法也是相当复杂的,下面我们进一步解析。

 ConstructorInfo[] constructors = _descriptor.ImplementationType.GetTypeInfo()
                .DeclaredConstructors
                .Where(IsInjectable)
                .ToArray();

这部分代码是获取注册的注入类型的所有公开的构造函数,其中IsInjectable,是获取包含非无参,非静态构造函数的func,代码如下所示:

技术分享
 private static bool IsInjectable(ConstructorInfo constructor)
        {
            return constructor.IsPublic && constructor.GetParameters().Length != 0;
        }
IsInjectable

最结尾处:return new CreateInstanceCallSite(_descriptor);是使用无参构造函数进行反射

技术分享
private class CreateInstanceCallSite : IServiceCallSite
        {
            private readonly ServiceDescriptor _descriptor;

            public CreateInstanceCallSite(ServiceDescriptor descriptor)
            {
                _descriptor = descriptor;
            }

            public object Invoke(ServiceProvider provider)
            {
                try
                {
                    return Activator.CreateInstance(_descriptor.ImplementationType);
                }
                catch (Exception ex) when (ex.InnerException != null)
                {
                    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
                    // The above line will always throw, but the compiler requires we throw explicitly.
                    throw;
                }
            }

            public Expression Build(Expression provider)
            {
                return Expression.New(_descriptor.ImplementationType);
            }
        }
CreateInstanceCallSite

现在所有的问题就落入了如何根据类型,并且该类型只有一个有参的构造函数。

如果我们想根据反射调用类型的有参的构造函数,那么就需要知道参数对象的实例;参数对象的实例可能已经存在,也可能是依赖注入的。如果是依赖注入的,那么是实例注入或者工厂注入,那么问题不大。但是如果都不是而是类型注入,有需要考虑该类型注入是否有一有参的构造函数。如此该函数内部必然有递归调用。所以我们需要遍历所有的parameters(ParameterInfo[] parameters = constructors[0].GetParameters()),首先根据ServiceProvider*对象的GetService*方法去获取参数实例,如果获取的结果为null则,判断该参数是否有默认值,没有默认值则抛出异常,当全部参数参数都齐备后,使用创建ConstructorCallSite实例返回。

[*ServiceProvider对象是创建所有注入类的接口,所以实例参数自然也使用这该类创建]

[*实际上调用的不是GetService方法,而是GetServiceCallSite方法。获取的不是参数的实例,而是能够创建参数实例的IServiceCallSite的对象。所以最后创建的ConstructorCallSite参数也略有不同;并且对于返回为null但有默认值的参数,也需要使用ConstantCallSite进行包装]

如下所示:ConstantCallSite和ConstructorCallSite的源码

技术分享
private class ConstantCallSite : IServiceCallSite
        {
            private readonly object _defaultValue;

            public ConstantCallSite(object defaultValue)
            {
                _defaultValue = defaultValue;
            }

            public object Invoke(ServiceProvider provider)
            {
                return _defaultValue;
            }

            public Expression Build(Expression provider)
            {
                return Expression.Constant(_defaultValue);
            }
        }
ConstantCallSite
技术分享
 private class ConstructorCallSite : IServiceCallSite
        {
            private readonly ConstructorInfo _constructorInfo;
            private readonly IServiceCallSite[] _parameterCallSites;

            public ConstructorCallSite(ConstructorInfo constructorInfo, IServiceCallSite[] parameterCallSites)
            {
                _constructorInfo = constructorInfo;
                _parameterCallSites = parameterCallSites;
            }

            public object Invoke(ServiceProvider provider)
            {
                object[] parameterValues = new object[_parameterCallSites.Length];
                for (var index = 0; index != parameterValues.Length; ++index)
                {
                    parameterValues[index] = _parameterCallSites[index].Invoke(provider);
                }

                try
                {
                    return _constructorInfo.Invoke(parameterValues);
                }
                catch (Exception ex) when (ex.InnerException != null)
                {
                    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
                    // The above line will always throw, but the compiler requires we throw explicitly.
                    throw;
                }
            }

            public Expression Build(Expression provider)
            {
                var parameters = _constructorInfo.GetParameters();
                return Expression.New(
                    _constructorInfo,
                    _parameterCallSites.Select((callSite, index) =>
                        Expression.Convert(
                            callSite.Build(provider),
                            parameters[index].ParameterType)));
            }
        }
ConstructorCallSite

最后由于核心注入类ServiceProvider无法为未进行注册的简单类型进行转化,所以默认的构造函数中包含基本类型直接注册会出错的,代码如下所示:

public interface IThrowError{

}
public class ThrowError{
      public ThrowError(bool throw){
      
      }
}

 public static IServiceCollection DefaultServices()
 {
       var services = new ServiceCollection();

       services.AddTransient<IThrowError, ThrowError>();
                       return services;
}

但是为基本类型提供默认值,即将ThrowError的构造函数修改为public ThrowError(bool throw=false){},是不会出错的。
当然也可以使用工厂为即有默认值,但是又没有默认值构造函数的类。如下面所示:

技术分享
services.AddTransient<IFactoryService>(provider =>
            {
                var fakeService = provider.GetService<IFakeService>();
                return new TransientFactoryService
                {
                    FakeService = fakeService,
                    Value = 42
                };
            });
工厂注册

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。