[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编译工具的可 ...
随机推荐
- 软件测试基本理论-IBM模式
软件测试基本理论(1) IBM生产模式 1 参考书目 <IBM-从菜鸟到测试架构师-一个测试工程师的成长日记> 出版社:电子工业出版社 印次:2013年6月 作者:IBM主要工程师 2 ...
- [.net 面向对象编程基础] (9) 类和类的实例
[.net 面向对象编程基础] (9) 类和类的实例 类 ,顾名思义就是分类.类别的意思.我们要面向对象编程,就需要对不同的事物进行分类.类可以说是.net面向对象的核心. 类:就是具有相同的属性和功 ...
- Asp.net下使用HttpModule模拟Filter,实现权限控制
在asp.net中,我们为了防止用户直接从Url中访问指定的页面而绕过登录验证,需要给每个页面加上验证,或者是在模板页中加上验证.如果说项目比较大的话,添加验证是一件令人抓狂的事情,本次,我就跟大家分 ...
- IOS 手势-轻点、触摸、手势、事件
1.概念 手势是从你用一个或多个手指接触屏幕时开始,直到手指离开屏幕为止所发生的所有事件.无论手势持续多长时间,只要一个或多个手指仍在屏幕上,这个手势就存在. 触摸是指把手指放到IOS设备的屏幕上,从 ...
- ASP.NET将原始图片按照指定尺寸等比例缩放显示图片
网站上可能会有很多图片,比如产品图片等,而且他们可能大小不一,宽度和高度也不一定一样,有的很大有的很小.如果放在一张网页上,可能会破坏版面,但是如果强制让他们按照指定的宽度和高度显示,因为比例不同还会 ...
- Yii 框架学习--02 进阶
应用结构 入口文件 文件位置: web/index.php <?php //开启debug,应用会保留更多日志信息,如果抛出异常,会显示详细的错误调用堆栈 defined('YII_DEBUG' ...
- Piwik 扩展获取客户端IP地址
piwik 数据库浏览记录存放在piwik_log_visit表中 piwik_log_visit 表有个location_ip字段,该字段存储为Byte[]字节 解析出来后的IP地址为 XXX.XX ...
- jQuery 滚动条 滚动到底部(下拉到底部) 加载数据(触发事件、处理逻辑)、分页加载数据
1.针对浏览器整个窗口滚动 主要代码: <script type="text/javascript"> ; function GetProductListPageFun ...
- 每天一个linux命令(11):nl命令
nl命令在linux系统中用来计算文件中行号.nl 可以将输出的文件内容自动的加上行号!其默认的结果与 cat -n 有点不太一样, nl 可以将行号做比较多的显示设计,包括位数与是否自动补齐 0 等 ...
- javascript_core_02之函数、作用域
1.函数:封装一项任务步骤清单的代码段: ①声明:function 函数名(参数列表){ 步骤清单代码:return 返回值:} ②返回值:使调用者获得函数执行结果,return只返回,不保存: ③存 ...