三、服务解析(Resolving Services)
当你完成组件注册,并将组件暴露为适当的服务后你就可以通过容器或者容器的子生命周期域来解析服务(After you have your components registered with appropriate services exposed, you can resolve services from the built container and child lifetime scopes)。你可以通过Resolve()方法来解析服务。
var builder = new ContainerBuilder(); builder.RegisterType<MyComponent>.As<IService>(); var container = builder.Build(); using(var scope = container.BeginLifetimeScope()) {
var service = scope.Resolve<IService>(); }
你可能已经注意到例子中是通过容器的子生命周期域对服务进行解析而不是直接使用容器(container)来接服务的——你也应该以这样的方式来解析服务。
虽然也可以直接使用容器(container)来解析服务,但如果你这样做可能导致应用程序内存泄漏。所以建议你总是尽可能的在自生命周期域中解析服务,这样autofac能够帮助你妥善的处理对象销毁以及垃圾回收工作。你将在今后的章节中了解到更详细的内容。
解析服务时,autofac将自动的连接服务的整个依赖层次,并且自动构件服务所需要的依赖项。当你的程序中存在循环依赖项或者依赖项缺失,那么程序将会抛出DependencyResolutionException异常。
假设你有一个服务,但不能确定它是否已经注册到容器当中,你可以尝试使用ResolveOptional() 或者TryResolve()方法进行条件解析:
//如果已经注册了IService服务,那么它将被正确的解析, //如果没有对IService服务进行注册,那么该方法将返回null var service = scope.ResolveOptional<IService>(); //如果IProvider进行了注册,那么provider将获得对象, //如果IProvider没有注册,那么你可以进行其他操作 IProvider provider = null if(scope.TryResolve<IProvider>(out provider)) { //服务被成功解析 }
3.1、为解析传递参数(Passing Parameters to Resolve)
当你解析服务的时候,可能会需要额外的参数才能完成解析。(作为代替,如果在组件注册时就能明确该参数的值,你也可以直接在注册时提供该参数)。
Resolve()方法用可变参数列表的方式接受参数。委托工厂(delegate factories)和Function<T>隐式关系类型(Function<T> implicit relationship type)也可以在解析时传递参数。(这段不是很明确,贴出原文等待高人指导:The Resolve() methods accept the same parameter types available at registration time using a variable-length argument list. Alternatively, delegate factories and the Func<T> implicit relationship type also allow ways to pass parameters during resolution.)
3.1.1、可用参数类型(Available Parameter Types)
autofac提供了几种不同的参数匹配策略:
- NamedParameter——通过参数名称进行匹配
- TypedParameter——通过参数类型进行匹配
- ResolvedParameter——灵活的参数匹配
NamedParameter和TypedParameter只能够提供常量值
ResolvedParameter提供的值可以从容器中动态的检索,比如通过名字来解析一个服务。(ResolvedParameter can be used as a way to supply values dynamically retrieved from the container, e.g. by resolving a service by name.)
3.1.2、参数与反射组件(Parameters with Reflection Components)
使用基于反射进行创建的组件时,组件的构造函数可能会需要一个只有在运行时才能确定的值作为参数。你可向Resolve()方法传递一个parameter参数(个人理解这个parameter指代的是NamedParameter、TypedParameter、ResolveParameter)从而帮助容器创建该组件。
如下的ConfigurationReader类,需要一个配置节(configuration section)名称作为参数:
public class ConfigReader : IConfigReader { public ConfigReader(string configurationSectionName) { //保存配置节名称 } //基于configurationSectionName读取配置 }
你可以按下面的方式为Resolve()传递一个parameter参数
var reader = container.Resolve<ConfigReader>(new NamedParameter("configurationSectionName", "sectionName"));
上面的例子中NamedParameter将按照名称(configurationSectionName)将值(sectionName)映射到ConfigReader的构造函数。
如果你的组件有多个参数,同样可以使用Parameter参数传递给Resolve()方法:
var service = scope.Resolve<AnotherService>( new NamedParameter("id", "service-identifier"), new TypedParameter(typeof(Guid), Guid.NewGuid()), new ResolvedParameter( (pi, ctx) => pi.ParameterType == typeof(ILog) && pi.Name == "logger", (pi, ctx) => LogManager.GetLogger("service")));
3.1.3、参数与Lambda表达式组件
使用Lambda表达式注册的组件,在使用Resolve()方法传递parameter参数时需要在注册组件的Lambda表达式中增加一些额外的处理。
在组件注册的表达式中,可以通过改变委托签名来使用传入的参数。之前使用的委托签名只有一个IComponentContext类型参数,而新的委托签名具有一个IComponentContext类型参数和一个IEnumerable<Parameter>类型的参数:
//注册是使用具有两个参数的委托 //c=IComponentContext,当前组件上下文,用于从容器中动态解析依赖 //p=IEnumerable<Parameter>,传入的参数集合 builder.Register((c, p) =>new ConfigReader(p.Named<string>("configSectionName"))).As<IConfigReader>();
现在解析IConfigReader时,你的lambda表达是就能够使用传入的参数了:
IConfigReader reader = container.Resolve<IConfigReader>(new NamedParameter("configSectionName", "sectionName"));
3.1.4、非显示调用Resolve传参(Passing Parameters Without Explicitly Calling Resolve)
autofac支持两种特性,允许自动生成服务工厂,该服务工厂可以在解析组件时使用强类型参数列表。
- Delegate Factories允许你定义工厂委托方法
- Func<T>隐式关心类型(Func<T> implicit relationship type)提供自动生成的工厂方法(The Func<T> implicit relationship type can provide an automatically-generated factory function.)
3.2、隐式关系类型(Implicit Relationship Types)
autofac能够自动完成一些特定类型的解析工作,这些特定类型的组件和服务有着间接的关系,要使用这些关系,你只需要正常的注册你的组件,但是在调用Resolve()方法时,稍稍改变传入的参数或参数类型以此表明你要使用它们之间的间接关系。(这段话的意思我是理解了,但好难用中文来表述啊!我还是贴出原文吧。Autofac supports automatically resolving particular types implicitly to support special relationships between components and services. To take advantage of these relationships, simply register your components as normal, but change the constructor parameter in the consuming component or the type being resolved in the Resolve() call so it takes in the specified relationship type.要是看原文还不理解,也没关系,接着往下看一定会明白,而且会发现这段是作者在语言描述上卖了萌,哈哈)
比如说,Autofac进行构造函数注入,而这个构造函数要求提供一个IEnumerable<ITask>类型的参数,autofac可能会找不到IEnumerable<ITask>匹配的组件,这种情况下它会去寻找所有ITask相对应的组件,并使用这些组件进行注入。(没看懂也别担心,下面还会有例子进行说明的)
3.2.1、支持的关系类型(Supported Relationship Types)
下表总结了.net中,autofac支持的(隐式)关系类型,以下的每种类型都会有更详细的描述和用
关系 | 类型 | 含义 |
A需要B(A need B) |
B | 直接依赖(Direct Dependency) |
将来的某一点A需要B(A need B at some point in the future) |
Lazy<B> | 延迟实例化(Delayed Instantiation) |
A需要B直到将来某一点(A need B until some point int future) |
Owned<B> | 控制生命周期(Controlled Lifetime) |
A需要创建B的实例(A need to create instances of B) |
Func<B> | 动态实例化(Dynamic Instantiation) |
A为B提供类型为X和Y的参数(A provides paramteres of types X and Y to B) |
Func<X,Y,B> |
参数化的实例化(Parameterized Instantiation) |
A需要所有B(A need all kinds of B) |
IEnumerable<B>, IList<B>, |
枚举(Enumeration) |
A needs to know X about B |
Meta<B> and Meta<B,X> | Metadata Interrogation |
A需要基于X对B进行选择 |
IIndex<X,B> | Keyed Service Lookup |
明天继续...
PS:本系列博客是对autofac英文资料的翻译,主要目的是为了提高自己英文阅读能力,同时能够帮助有需要的人,原文地址http://autofac.readthedocs.org/en/latest/getting-started/index.html。3.2节真心难翻译!