ABP成长系列4:DI
2024-05-04 21:43:26前面重点介绍了ABP.vNext的模块化,以及模块化带来的核心价值动态API,今天我们开看一下ABP DI的扩展。
一、ABP DI功能介绍
1. 组件自动注册
- SkipAutoServiceRegistration
- AddAssemblyOf
- AddAssembly
public class BlogModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
SkipAutoServiceRegistration = true;
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAssemblyOf<BlogModule>();
}
}
2. 默认的注册
- 模块类注册为singleton
- MVC控制器(继承Controller或AbpController)被注册为transient
- MVC页面模型(继承PageModel或AbpPageModel)被注册为transient
- MVC视图组件(继承ViewComponent或AbpViewComponent)被注册为transient
- 应用程序服务(实现IApplicationService接口或继承ApplicationService类)注册为transient
- 存储库(实现IRepository接口)注册为transient
- 域服务(实现IDomainService接口)注册为transient
3. 依赖接口
- ITransientDependency 注册为transient生命周期
- ISingletonDependency 注册为singleton生命周期
- IScopedDependency 注册为scoped生命周期
4. 特性
- DependencyAttribute:控制服务的注册行为和生命周期
- ExposeServicesAttribute:控制服务以哪些类型注册到容器中
[ExposeServices(typeof(IProductService))] // 只以 IProductService 注册
[Dependency(ServiceLifetime.Singleton)] // 作为单例注册
public class ProductService : IProductService, IOtherInterface
{
// 即使实现了 IOtherInterface,也不会被注册
}
[ExposeServices(IncludeDefaults = true, IncludeSelf = true)] // 暴露所有默认接口和自身
[Dependency(ReplaceServices = true)] // 替换现有注册
public class SpecialLogger : ILogger, IDisposable
{
}
5. 手动注册
手动注册就是自己写默认的注册代码,ABP只是扩展了DI,默认的DI使用方法依然有效。
// 模块中注册服务
public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// 手动注册
context.Services.AddTransient<IMyService, MyService>();
// 约定式注册
context.Services.AddAssemblyOf<MyModule>();
}
}
6. 高级特性
IServiceCollection.OnRegistred
在注册到依赖注入的每个服务上执行一个操作
通常用于向服务添加拦截器
//使用示例
public override void PreConfigureSevices(ServiceConfigurationContext context)
{
context.Services.OnRegistred(onServiceRegistredContext =>
{
if(typeof(IMyAuthorizedService).IsAssignableFrom(onServiceRegistredContext.ImplementationType)
&& !DynamicProxyIgnorelype.Contains(onServiceRegistredContext.ImplementationType))
{
onServiceRegistredContet.Interceptors.TryAdd<AuthorizationInterceptor>
}
}
}
二、Autofoc
收先我们要知道Autofac不是一个性能优先的框架,它确实提供了很多方便的功能。如果要考虑极值的性能,请放弃它。
ABP vNext最初基于Autofac,后来替换为原生DI系统+自定义扩展。
1. Autofoc的优缺点
优点:
- 更丰富的生命周期管理
- 更强大的AOP支持
- 更灵活的注册语法
缺点:
- 应用启动时间增加
- 与某些ASP.NET Core组件可能存在兼容性问题
- 增加学习曲线
2. 集成Autofoc步骤
- Volo.Abp.Autofac
- 添加 AbpAutofacModule 依赖
- 启动时配置Autofac
using (var application = AbpApplicationFactory.Create<AppModule>(options =>
{
options.UseAutofac(); //Autofac integration
}))
3. 使用场景
使用原生DI+ABP扩展的场景:
- 标准Web应用程序
- 性能敏感型应用
- 希望最小化依赖的项目
可能需要Autofac的场景:
- 需要复杂生命周期管理的应用
- 重度依赖AOP的项目
- 已有大量基于Autofac的遗留代码
三、源码解读
源码在Volo.Abp.DependencyInjection,下面是类关系图
ABP.vNext DI的源码比较简单,我们来分析一下:
1. 核心1:AddAssembly
在模块化源码里面,还记得下面这段代码吗?
- AbpApplicationBase==>ConfigureServices方法全部模块使用同个IOC容器,并且所有模块按顺序执行。
//上面是PreConfigureServices
//ConfigureServices--先把所有的模块按顺序去执行ConfigureServices
foreach (var module in Modules)
{
if (module.Instance is AbpModule abpModule)
{
if (!abpModule.SkipAutoServiceRegistration) //判断是否全局关闭的动态注册
{
var assembly = module.Type.Assembly;
if (!assemblies.Contains(assembly))
{
Services.AddAssembly(assembly); //这里实现基于约定的批量注册,后面再分析源码
assemblies.Add(assembly);
}
}
}
// ...
}
//后面是PostConfigureServices
- 跟踪AddAssembly方法,这里只是提供一个抽象的AddType方法,判断规则由继承内具体的实现
//ConventionalRegistrarBase.cs
public abstract class ConventionalRegistrarBase : IConventionalRegistrar
{
public virtual void AddAssembly(IServiceCollection services, Assembly assembly)
{
var types = AssemblyHelper
.GetAllTypes(assembly)
.Where(
type => type != null &&
type.IsClass &&
!type.IsAbstract &&
!type.IsGenericType
).ToArray();
AddTypes(services, types);
}
public virtual void AddTypes(IServiceCollection services, params Type[] types)
{
foreach (var type in types)
{
AddType(services, type);
}
}
public abstract void AddType(IServiceCollection services, Type type);
//...
}
- 默认实现DefaultConventionalRegistrar
//TODO: Make DefaultConventionalRegistrar extensible, so we can only define GetLifeTimeOrNull to contribute to the convention. This can be more performant!
public class DefaultConventionalRegistrar : ConventionalRegistrarBase
{
public override void AddType(IServiceCollection services, Type type)
{
// 检查类型是否标记为不自动注册
if (IsConventionalRegistrationDisabled(type))
{
return;
}
// 检查类型是否有 [Dependency] 特性,该特性可以自定义注册行为。
var dependencyAttribute = GetDependencyAttributeOrNull(type);
// 决定服务的生命周期(Transient/Scoped/Singleton),优先使用 [Dependency] 中指定的生命周期。
var lifeTime = GetLifeTimeOrNull(type, dependencyAttribute);
if (lifeTime == null)
{
return;
}
// 获取该类型应该以哪些服务类型注册(通常是实现的接口和自身)。
var exposedServiceTypes = GetExposedServiceTypes(type);
// 触发服务暴露事件——允许其他模块在服务注册前修改要暴露的服务类型。
TriggerServiceExposing(services, type, exposedServiceTypes);
foreach (var exposedServiceType in exposedServiceTypes)
{
// 创建并注册服务描述符
var serviceDescriptor = CreateServiceDescriptor(
type,
exposedServiceType,
exposedServiceTypes,
lifeTime.Value
);
if (dependencyAttribute?.ReplaceServices == true) //替换现有注册
{
services.Replace(serviceDescriptor);
}
else if (dependencyAttribute?.TryRegister == true) //仅当不存在时注册
{
services.TryAdd(serviceDescriptor);
}
else
{
services.Add(serviceDescriptor); //默认情况:直接添加
}
}
}
}
2. 核心2:对象生命周期(Transient/Scoped/Singleton)
我们进入上面代码GetLifeTimeOrNull方法内部,可以看到会根据接口类型注册对应的生命周期。
//ConventionalRegistrarBase.cs
protected virtual ServiceLifetime? GetLifeTimeOrNull(Type type, [CanBeNull] DependencyAttribute dependencyAttribute)
{
return dependencyAttribute?.Lifetime ?? GetServiceLifetimeFromClassHierarchy(type) ?? GetDefaultLifeTimeOrNull(type);
}
protected virtual ServiceLifetime? GetServiceLifetimeFromClassHierarchy(Type type)
{
if (typeof(ITransientDependency).GetTypeInfo().IsAssignableFrom(type))
{
return ServiceLifetime.Transient;
}
if (typeof(ISingletonDependency).GetTypeInfo().IsAssignableFrom(type))
{
return ServiceLifetime.Singleton;
}
if (typeof(IScopedDependency).GetTypeInfo().IsAssignableFrom(type))
{
return ServiceLifetime.Scoped;
}
return null;
}
protected virtual ServiceLifetime? GetDefaultLifeTimeOrNull(Type type)
{
return null;
}
3. 核心3:ExposeServicesAttribute
- ServiceTypes:显式指定要暴露的服务类型数组
- IncludeDefaults:是否包含默认服务类型(按照约定推断的接口)
- IncludeSelf:是否包含自身类型
public Type[] GetExposedServiceTypes(Type targetType)
{
var serviceList = ServiceTypes.ToList();
if (IncludeDefaults)
{
foreach (var type in GetDefaultServices(targetType))
{
serviceList.AddIfNotContains(type);
}
if (IncludeSelf)
{
serviceList.AddIfNotContains(targetType);
}
}
else if (IncludeSelf)
{
serviceList.AddIfNotContains(targetType);
}
return serviceList.ToArray();
}
private static List<Type> GetDefaultServices(Type type)
{
var serviceTypes = new List<Type>();
foreach (var interfaceType in type.GetTypeInfo().GetInterfaces())
{
var interfaceName = interfaceType.Name;
if (interfaceName.StartsWith("I")) //默认实现就是去掉接口前面的首字母I后的方法
{
interfaceName = interfaceName.Right(interfaceName.Length - 1);
}
if (type.Name.EndsWith(interfaceName))
{
serviceTypes.Add(interfaceType);
}
}
return serviceTypes;
}