在 DependencyInjection项目代码分析4-微软的实现(1)中介绍了“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 != ;
}

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 = ; 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 =
};
});

工厂注册

[Asp.net 5] DependencyInjection项目代码分析4-微软的实现(2)的更多相关文章

  1. [Asp.net 5] DependencyInjection项目代码分析-目录

    微软DI文章系列如下所示: [Asp.net 5] DependencyInjection项目代码分析 [Asp.net 5] DependencyInjection项目代码分析2-Autofac [ ...

  2. [Asp.net 5] DependencyInjection项目代码分析4-微软的实现(5)(IEnumerable<>补充)

    Asp.net 5的依赖注入注入系列可以参考链接: [Asp.net 5] DependencyInjection项目代码分析-目录 我们在之前讲微软的实现时,对于OpenIEnumerableSer ...

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

    这个系列已经写了5篇,链接地址如下: [Asp.net 5] DependencyInjection项目代码分析 [Asp.net 5] DependencyInjection项目代码分析2-Auto ...

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

    这个系列已经写了6篇,链接地址如下: [Asp.net 5] DependencyInjection项目代码分析 [Asp.net 5] DependencyInjection项目代码分析2-Auto ...

  5. [Asp.net 5] DependencyInjection项目代码分析

    最近在研究开源代码,正好发现Asp.net5的源码,下载地址:https://github.com/aspnet. 今天主要讲的是DependencyInjection这部分,抛砖引玉,供大家参考,也 ...

  6. [Asp.net 5] DependencyInjection项目代码分析2-Autofac

    Microsoft.Framework.DependencyInjection.Autofac源码分析 该工程只有一个代码静态类AutofacRegistration,但是该类有3个扩展方法,以及3个 ...

  7. [Asp.net 5] DependencyInjection项目代码分析3-Ninject

    Microsoft.Framework.DependencyInjection.Ninject 该工程内部共包含5个类文件,底层使用Ninject实现依赖注入,工程截图如下: 从文件命名可以看出,Ni ...

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

    前面俩种实现中,很多内部细节都无法知道,微软的框架也是为了屏蔽具体实现,只让我们关注接口.但是人都是充满好奇的,依赖注入到底是怎么实现的呢? 微软又有怎样的实现呢?下面就为大家一一呈现(说实话,代码真 ...

  9. Jenkins+Gradle+Sonar进行Java项目代码分析

    Jenkins+Maven+Sonar与Jenkins+Gradle+Sonar配置方法很相似,区别就是Java项目所用的编译工具不同,一个是maven,一个是gradle 使用maven编译工具的可 ...

随机推荐

  1. Web 数据存储总结

    随着Web应用程序的出现,也产生了对于能够在客户端上存储用户信息能力的要求.这个问题的第一个解决方案是以cookie形似出现的.网景公司在一份名为“Persistent Client State: H ...

  2. Linux 循环

    200 ? "200px" : this.width)!important;} --> 简介 if循环 if conditon then commandselse comma ...

  3. [php入门] 4、HTML基础入门一篇概览

    [php入门] 1.从安装开发环境环境到(庄B)做个炫酷的登陆应用 [php入门] 2.基础核心语法大纲 [php入门] 3.WAMP中的集成MySQL相关基础操作 1.HTML的作用 HTML是超文 ...

  4. 获取IOS应用的子目录

    在开发IOS应用时,我们经常需要将素材分类,并放入相应地子目录中. 在开发代码时,需要访问这些素材时,就需要获取对应的子目录路径.那么如何获取呢? 获取应用路径 首先,要找到应用所在的路径. NSSt ...

  5. Visual Studio 2015速递(2)——提升效率和质量(VS2015核心竞争力)

    系列文章 Visual Studio 2015速递(1)——C#6.0新特性怎么用 Visual Studio 2015速递(2)——提升效率和质量(VS2015核心竞争力) Visual Studi ...

  6. java 堆栈分析4

    jprofiler ,又是一款好工具... —— 不过显然,我觉得有了jvisualvm就足够了,难道它会比jvisualvm还强大很多!?? 什么时候需要它呢?它有什么特别好用的地方吗? 带来什么方 ...

  7. List.Foreach与C#的foreach的区别

    几年前参加面试时就被提问过,现在面试别人时也经常提到这个问题. 今天小试了一下.得出如下几点: 1. 首先,mscorlib里System.Collections.Generic. List<T ...

  8. Apache Commons CLI命令行启动

    今天又看了下Hangout的源码,一般来说一个开源项目有好几种启动方式--比如可以从命令行启动,也可以从web端启动.今天就看看如何设计命令行启动... Apache Commons CLI Apac ...

  9. fir.im Weekly - 2015 年开发者调查报告

    终于一脚迈入了 2016 年.无论你是否准备好,未来已经汹涌扑来-- 新年第一期的 fir.im Weekly 干货颇多,来看一看:) 2015 Developer Survey stackoverf ...

  10. fir.im Weekly - 94 个 iOS 开发资源推荐

    距离 2016 年还有 17 个日夜,而你和回家只隔了一张 12306 验证码的距离,祝大家抢票顺利.本期 fir.im Weekly 收集了一些优秀的 GitHub 源码.开发工具和动画特效,希望对 ...