【Autofac笔记】注册、解析
概述
注册:
注册就是将组件注册到容器,并指定暴露那些服务
默认暴露自身,也可以使用As()
暴露其他服务,但请注意, 一旦你将组件暴露为一个特定的服务, 默认的服务 (组件类型) 将被覆盖.
解析:
在注册完组件并暴露相应的服务后, 你可以从容器或其子生命周期中解析服务.
推荐从子声明周期中解析服务,如果从根容器中解析服务容易内存泄漏
注册的几种方式
组件注册有三种方式:反射、现成实例、Lambda表达式
反射
builder.RegisterType<ConsoleLogger>();
builder.RegisterType(typeof(ConfigReader));
当使用基于反射的组件时, Autofac 自动为你的类从容器中寻找匹配拥有最多参数的构造方法,可以使用UsingConstructor
手动指定一个构造函数
builder.RegisterType<MyComponent>().UsingConstructor(typeof(ILogger), typeof(IConfigReader));
实例
builder.RegisterInstance(output).As<TextWriter>();
有时候你也许会希望提前生成一个对象的实例并将它加入容器以供注册组件时使用
如果你想自己控制生命周期而不是让Autofac来帮你调用 Dispose,此时你需要通过 ExternallyOwned 方法来注册实例:
builder.RegisterInstance(new StringWriter()).ExternallyOwned();
lambda表达式
当创建实例不再是简单的调用构造方法, 就需要使用Lambda表达式,这种注册方式更灵活
builder.Register(c => new UserSession(DateTime.Now.AddMinutes(25)));
builder.Register(c => new A(c.Resolve<B>()));
参数c
是 组件上下文 (一个 IComponentContext 对象) , 该参数可以引入其他依赖的
通过参数值选择具体的实现:
builder.Register<CreditCard>(
(c, p) =>
{
var accountId = p.Named<string>("accountId");
if (accountId.StartsWith("9"))
{
return new GoldCard(accountId);
}
else
{
return new StandardCard(accountId);
}
});
获取服务,传参:
var card = container.Resolve<CreditCard>(new NamedParameter("accountId", "12345"));
扫描程序集(批量注册)
Autofac可以在程序集中通过约定找到和注册组件. 你可以扫描和注册单独的类型, 也可以专门扫描 Autofac模块.
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces();
每次RegisterAssemblyTypes()
方法调用将应用一组规则 - 如果有多组不同的组件注册时, 我们有必要多次调用 RegisterAssemblyTypes() .
过滤类型
默认地, 程序中所有具体的类都将被注册. 包括嵌套的私有类. 你可以使用LINQ表达式过滤注册的类型集合.
builder.RegisterAssemblyTypes(asm)
.PublicOnly();//过滤公共类型
.Where(t => t.Name.EndsWith("Repository"));//过滤Repository结尾的类型
.Except<MyUnwantedType>();//排除类型
注册模块
我们通过RegisterAssemblyModules()
方法进行模块扫描, 它通过提供的程序集扫描 Autofac模块, 然后使用当前的container builder来注册它们.
例如, 假设下面两个普通的模块类在同一个程序集中, 并且每个模块注册一个组件:
public class AModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register(c => new AComponent()).As<AComponent>();
}
}
RegisterAssemblyModules()
的重载 不接受类型参数 , 它将会注册所提供程序集列表中的所有实现 IModule 的类. 在下面的示例中 所有的模块 都将被注册:
var assembly = typeof(AComponent).Assembly;
var builder = new ContainerBuilder();
// Registers both modules
builder.RegisterAssemblyModules(assembly);
也可以注册指定模块:
containerBuilder.RegisterModule<XXXModule>();
注册泛型组件
组件、服务都是泛型:
builder.RegisterGeneric(typeof(NHibernateRepository<>))
.As(typeof(IRepository<>))
.InstancePerLifetimeScope();
非泛型组件、指定参数的泛型服务:
containerBuilder.RegisterAssemblyTypes(typeof(OldSiteMessageHandleTest<>).Assembly)
.Where(t => !t.IsAbstract && !t.IsGenericType && t.IsClass && typeof(HandleBaseTest).IsAssignableFrom(t))
.AsSelf()
.As(t =>
{
var genericArgumentTypes = t.BaseType.GetGenericArguments();
if (genericArgumentTypes.Length == 0)
{
throw new NullReferenceException("注册类型异常");
}
return typeof(OldSiteMessageHandleTest<>).MakeGenericType(genericArgumentTypes[0]);
});
注册时传参
注册组件 时你可以提供一组参数, 可以在基于该组件的 服务解析 时使用. (如果你想要在解析时提供参数, 当然也是可以的.)
参数匹配方式:
NamedParameter
- 通过名字匹配目标参数TypedParameter
- 通过类型匹配目标参数 (需要匹配具体的类型)ResolvedParameter
- 复杂参数的匹配
NamedParameter
和 TypedParameter
只支持常量值.
ResolvedParameter
可以用于提供不同的值来从容器中动态获取对象, 例如, 通过名字解析服务.
注册传参:
builder.RegisterType<ConfigReader>()
.As<IConfigReader>()
.WithParameter("configSectionName", "sectionName");
builder.RegisterType<ConfigReader>()
.As<IConfigReader>()
.WithParameter(new TypedParameter(typeof(string), "sectionName"));
builder.RegisterType<ConfigReader>()
.As<IConfigReader>()
.WithParameter(
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == typeof(string) && pi.Name == "configSectionName",
(pi, ctx) => "sectionName"));
暴露服务
默认地,类型注册时大部分情况下暴露它们自身,你可以让一个组件暴露任意数量的服务:
builder.RegisterType<CallLogger>()
.As<ILogger>()
.As<ICallInterceptor>();
暴露服务后, 你就可以解析基于该服务的组件了. 但请注意, 一旦你将组件暴露为一个特定的服务, 默认的服务 (组件类型) 将被覆盖.
如果你既想组件暴露一系列特定的服务, 又想让它暴露默认的服务, 可以使用 AsSelf 方法:
builder.RegisterType<CallLogger>()
.AsSelf()
.As<ILogger>()
.As<ICallInterceptor>();
As()
方法还有接收lambda表达式的重载
builder.RegisterAssemblyTypes(asm)
.As(t => t.GetInterfaces()[0]);
组件分配给泛型服务:
builder.RegisterAssemblyTypes(asm)
.AsClosedTypesOf(typeof(IRepository<>));
有条件的注册
你只想要在其他一些组件未被注册的情况下才注册组件; 或者只想在其他一些组件已被注册的情况下.
这边有两种好用的注册扩展方法:
OnlyIf()
- 提供一个表达式, 使用一个IComponentRegistry
来决定注册是否发生.
IfNotRegistered()
- 有其他服务已被注册的情况下阻止注册发生的快捷方法.
这些方法在 ContainerBuilder.Build() 时执行并且以实际组件注册的顺序执行:
builder.RegisterType<ServiceA>()
.As<IService>();
builder.RegisterType<ServiceB>()
.As<IService>()
.IfNotRegistered(typeof(IService));//服务已注册ServiceA,不会再注册
OnlyIf:
builder.RegisterType<Manager>()
.As<IManager>()
.OnlyIf(reg =>
reg.IsRegistered(new TypedService(typeof(IService))) &&
reg.IsRegistered(new TypedService(typeof(HandlerB))));
激活后事件
builder.Register(c => new A()).OnActivated(e => e.Instance.B = e.Context.Resolve<B>());
属性注入
builder.RegisterType<A>().PropertiesAutowired();
注入指定属性
builder.RegisterType<A>().WithProperty("PropertyName", propertyValue);
解析方式
- Resolve()
解析,如果未注册,会抛异常
var service = scope.Resolve<IService>();
- ResolveOptional()
解析不会抛异常,如果你不清楚一个服务是否被注册使用此方法
var service = scope.ResolveOptional<IService>();
- TryResolve()
解析不会抛异常,如果你不清楚一个服务是否被注册使用此方法
IProvider provider = null;
if(scope.TryResolve<IProvider>(out provider))
{
// Do something with the resolved provider value.
}
解析时传参
上一篇文章中说到注册时可以传参,解析时同样也可以
参数类型:NamedParameter
、TypedParameter
、ResolvedParameter
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")));
参考:
https://autofac-.readthedocs.io/en/latest/register/index.html
https://autofac-.readthedocs.io/en/latest/resolve/index.html