传统 vs ABP
传统方式:
services.AddScoped<IUserService,UserService>();services.AddTransient<IEmailService,EmailService>();ABP 方式:
publicclassUserService:IUserService,ITransientDependency{}背后的原理是什么?直接看DefaultConventionalRegistrar.cs源码。
源码位置
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs
约定注册的核心流程
publicoverridevoidAddType(IServiceCollectionservices,Typetype){// 1. 检查是否禁用了约定注册if(IsConventionalRegistrationDisabled(type))return;// 2. 获取生命周期vardependencyAttribute=GetDependencyAttributeOrNull(type);varlifeTime=GetLifeTimeOrNull(type,dependencyAttribute);if(lifeTime==null)return;// 没实现标记接口,不注册// 3. 获取暴露的服务类型(注册为哪个接口)varexposedServiceAndKeyedServiceTypes=GetExposedKeyedServiceTypes(type).Concat(GetExposedServiceTypes(type).Select(t=>newServiceIdentifier(t))).ToList();// 4. 触发暴露事件(其他模块可以修改)TriggerServiceExposing(services,type,exposedServiceAndKeyedServiceTypes);// 5. 逐一添加到 DI 容器foreach(varexposedServiceTypeinexposedServiceAndKeyedServiceTypes){varserviceDescriptor=CreateServiceDescriptor(type,...,lifeTime);services.Add(serviceDescriptor);}}第一步:生命周期判定
GetLifeTimeOrNull的优先级:
protectedvirtualServiceLifetime?GetLifeTimeOrNull(Typetype,DependencyAttribute?dependencyAttribute){returndependencyAttribute?.Lifetime// 1. [Dependency] 属性指定??GetServiceLifetimeFromClassHierarchy(type)// 2. 标记接口??GetDefaultLifeTimeOrNull(type);// 3. 默认值(null = 不注册)}GetServiceLifetimeFromClassHierarchy检查类的继承链:
if(typeof(ITransientDependency).IsAssignableFrom(type))returnServiceLifetime.Transient;if(typeof(ISingletonDependency).IsAssignableFrom(type))returnServiceLifetime.Singleton;if(typeof(IScopedDependency).IsAssignableFrom(type))returnServiceLifetime.Scoped;三个标记接口都是空接口,没有任何方法,纯标记用途:
publicinterfaceITransientDependency{}// 空接口publicinterfaceISingletonDependency{}// 空接口publicinterfaceIScopedDependency{}// 空接口第二步:暴露服务类型判定
ExposedServiceExplorer.GetExposedServices的默认逻辑:
// 1. 有 [ExposeServices] → 用属性指定的类型// 2. 没有 → 按约定推断:// 遍历类实现的所有接口// 接口名去掉 I 前缀,剩下的跟类名比较// 如果类名以它结尾(不区分大小写),则匹配比如:
publicclassProductService:IProductService,ITransientDependency// IProductService → 去掉 I → ProductService → 类名 ProductService 以 ProductService 结尾 → 匹配// 相当于 [ExposeServices(typeof(IProductService))]第三步:ServiceDesciptor 注册
最后调用services.Add(serviceDescriptor),但还有两个变体:
if(dependencyAttribute?.ReplaceServices==true)services.Replace(serviceDescriptor);// 替换已有注册elseif(dependencyAttribute?.TryRegister==true)services.TryAdd(serviceDescriptor);// 不存在时才注册elseservices.Add(serviceDescriptor);// 默认:直接注册启动时扫描谁?
ABP 启动时,每个模块的ConfigureServices阶段会调用:
context.Services.AddAssemblyOf<TModule>();这个方法扫描当前模块所在程序集的所有公开类型,对每个类型执行DefaultConventionalRegistrar.AddType。所以一个类只要在模块的程序集中、实现了标记接口,就会被自动注册。
实战:如何排查注册问题
如果发现IServiceProvider.GetService<T>()返回 null,按这个顺序排查:
- 检查类是否在模块的程序集中(在哪个项目?那个项目被引用了?)
- 检查类是否实现了标记接口(
ITransientDependency/ISingletonDependency/IScopedDependency) - 检查是否写了
[DisableConventionalRegistration] - 检查生命周期选择是否正确
对比总结
// 方式 1:标记接口(最常用)publicclassMyService:ITransientDependency{}// 方式 2:标记接口 + 显示的暴露接口[ExposeServices(typeof(IUserService))]publicclassUserService:IUserService,ITransientDependency{}// 方式 3:[Dependency] 精细控制[Dependency(ReplaceServices=true)]publicclassMockService:IRealService,ITransientDependency{}// 方式 4:禁用自动注册,手动管理[DisableConventionalRegistration]publicclassHelperService{}// 需要手动 services.AddSingleton<HelperService>()