[Asp.net 5] DependencyInjection项目代码分析4-微软的实现(2)
在 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类内部想创建对象的实例,只有通过反射创建对象这一条路可走了。如果通过反射创建,就分几种情况需要考虑了(静态构造函数不再此考虑之内):
- 有公开的无参构造函数
- 只有一个公开的构造函数,并且有非无参构造函数
- 有多个构造函数
下面是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)的更多相关文章
- [Asp.net 5] DependencyInjection项目代码分析-目录
微软DI文章系列如下所示: [Asp.net 5] DependencyInjection项目代码分析 [Asp.net 5] DependencyInjection项目代码分析2-Autofac [ ...
- [Asp.net 5] DependencyInjection项目代码分析4-微软的实现(5)(IEnumerable<>补充)
Asp.net 5的依赖注入注入系列可以参考链接: [Asp.net 5] DependencyInjection项目代码分析-目录 我们在之前讲微软的实现时,对于OpenIEnumerableSer ...
- [Asp.net 5] DependencyInjection项目代码分析4-微软的实现(3)
这个系列已经写了5篇,链接地址如下: [Asp.net 5] DependencyInjection项目代码分析 [Asp.net 5] DependencyInjection项目代码分析2-Auto ...
- [Asp.net 5] DependencyInjection项目代码分析4-微软的实现(4)
这个系列已经写了6篇,链接地址如下: [Asp.net 5] DependencyInjection项目代码分析 [Asp.net 5] DependencyInjection项目代码分析2-Auto ...
- [Asp.net 5] DependencyInjection项目代码分析
最近在研究开源代码,正好发现Asp.net5的源码,下载地址:https://github.com/aspnet. 今天主要讲的是DependencyInjection这部分,抛砖引玉,供大家参考,也 ...
- [Asp.net 5] DependencyInjection项目代码分析2-Autofac
Microsoft.Framework.DependencyInjection.Autofac源码分析 该工程只有一个代码静态类AutofacRegistration,但是该类有3个扩展方法,以及3个 ...
- [Asp.net 5] DependencyInjection项目代码分析3-Ninject
Microsoft.Framework.DependencyInjection.Ninject 该工程内部共包含5个类文件,底层使用Ninject实现依赖注入,工程截图如下: 从文件命名可以看出,Ni ...
- [Asp.net 5] DependencyInjection项目代码分析4-微软的实现(1)
前面俩种实现中,很多内部细节都无法知道,微软的框架也是为了屏蔽具体实现,只让我们关注接口.但是人都是充满好奇的,依赖注入到底是怎么实现的呢? 微软又有怎样的实现呢?下面就为大家一一呈现(说实话,代码真 ...
- Jenkins+Gradle+Sonar进行Java项目代码分析
Jenkins+Maven+Sonar与Jenkins+Gradle+Sonar配置方法很相似,区别就是Java项目所用的编译工具不同,一个是maven,一个是gradle 使用maven编译工具的可 ...
随机推荐
- 使用you-get下载视频网站视频或其他
使用you-get下载视频网站视频或其他 文/玄魂 目录 使用you-get下载视频网站视频或其他 前言 1.1 下载.安装 依赖 exe安装 pip安装 Antigen安装 Git 克隆源码 Hom ...
- 第二次作业:Github的优点和缺点
---恢复内容开始--- GitHub的优势和劣势 简介: Github是一个代码托管平台和开发者社区,开发者可以在Github上创建自己的开源项目并与其他开发者协作编码.创业公司可以用它来托管软件项 ...
- [.net 面向对象编程基础] (9) 类和类的实例
[.net 面向对象编程基础] (9) 类和类的实例 类 ,顾名思义就是分类.类别的意思.我们要面向对象编程,就需要对不同的事物进行分类.类可以说是.net面向对象的核心. 类:就是具有相同的属性和功 ...
- WCF基础教程之开篇:创建、测试和调用WCF
一转眼,又半个月没有更新博客了.说实话,最近确实是有点忙.不过即使再忙忙,也要抽空来学习一些东西.最近用WCF比较多,就来跟大家分享一下关于WCF的知识吧!为了让大家都能看懂,照顾一些没有学过WCF的 ...
- DOM+CSS3实现小游戏SwingCopters
前些日子看到了一则新闻,flappybird原作者将携新游戏SwingCopters来袭,准备再靠这款姊妹篇游戏引爆大众眼球.就是下面这个小游戏: 前者的传奇故事大家都有耳闻,至于这第二个游戏能否更加 ...
- ASP.NET中一种超简单的Ajax解决方案
为什么是Ajax2? 因为之前有一个blqw.Ajax,并且已经在项目中投入使用了,但是没有这个方便,这个是后来才弄的,为了纪念第一版的blqw.Ajax,所以这个就2了... 话说看了评论才发现,原 ...
- C++ static
本文主要记录的C++中static的一些内容,内容简单,仅仅作为梳理一下知识,如有错误请留言指出. static的作用 在函数体,一个被声明为static的变量,在这一函数被调用的过程里,其数值维持不 ...
- Flex contextMenu
没想到再这里也要跌跌撞撞. 这个东西我很久之前就懂了的啊! 最开始是这么写的: private function init():void { var contextMenu:ContextMenu = ...
- java加密-解密小结
加密算法可以分为 双向加密(对称加密.不对称加密) 单向加密(不可逆加密)—— MD5.sha.hmac... 在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密 有: ...
- js模仿新浪微博限制字数输入
功能:实现新浪微博输入字数提醒功能:最多输入140个字,当输入字时,上面提示部分字数发生变化,如果字数小于25,字体颜色变红:当可输入字数为0时,强制不能输入,如果用中文输入法 一次性输入很多字,那么 ...