Spiga

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步骤

  1. Volo.Abp.Autofac
  2. 添加 AbpAutofacModule 依赖
  3. 启动时配置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;
}