- A+
所属分类:.NET技术
介绍
ABP的依赖注入系统是基于Microsoft的依赖注入扩展库(Microsoft.Extensions.DependencyInjection nuget包)开发的。所以我们采用dotnet自带的注入方式也是支持的。
- 由于ABP是一个模块化框架,因此每个模块都定义它自己的服务并在它自己的单独模块类中通过依赖注入进行注册.例:
public class BlogModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { //在此处注入依赖项 // dotnet自带依赖注入方式也是支持的 context.services.AddTransient context.services.AddScoped context.services.AddSingleton } }
Autofac
Autofac 是.Net世界中最常用的依赖注入框架之一. 相比.Net Core标准的依赖注入库, 它提供了更多高级特性, 比如动态代理和属性注入.
集成
1.安装 Volo.Abp.Autofac nuget 包到你的项目 (对于一个多项目应用程序, 建议安装到可执行项目或者Web项目中.)
2.模块添加 AbpAutofacModule 依赖:
[DependsOn(typeof(AbpAutofacModule))] public class MyModule : AbpModule { //... } }
3.配置 AbpApplicationCreationOptions 用 Autofac 替换默认的依赖注入服务. 根据应用程序类型, 情况有所不同
- ASP.NET Core 应用程序
public class Program { public static int Main(string[] args) { CreateHostBuilder(args).Build().Run(); } internal static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) .UseAutofac(); //Integrate Autofac! }
- 控制台应用程序
namespace AbpConsoleDemo { class Program { static void Main(string[] args) { using (var application = AbpApplicationFactory.Create<AppModule>(options => { options.UseAutofac(); //Autofac integration })) { //... } } } }
依照约定的注册
如果实现这些接口,则会自动将类注册到依赖注入:
- ITransientDependency 注册为transient生命周期.
- ISingletonDependency 注册为singleton生命周期.
- IScopedDependency 注册为scoped生命周期.
默认特定类型
一些特定类型会默认注册到依赖注入.例子:
- 模块类注册为singleton.
- MVC控制器(继承Controller或AbpController)被注册为transient.
- MVC页面模型(继承PageModel或AbpPageModel)被注册为transient.
- MVC视图组件(继承ViewComponent或AbpViewComponent)被注册为transient.
- 应用程序服务(实现IApplicationService接口或继承ApplicationService类)注册为transient.
- 存储库(实现IRepository接口)注册为transient.
- 域服务(实现IDomainService接口)注册为transient.
手动注册
在某些情况下,你可能需要向IServiceCollection手动注册服务,尤其是在需要使用自定义工厂方法或singleton实例时.在这种情况下,你可以像Microsoft文档描述的那样直接添加服务.
public class BlogModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { context.services.AddTransient<ITestMicrosoftManager, TestMicrosoftManager>(); } }
如何使用
构造函数注入
- 构造方法注入是将依赖项注入类的首选方式
属性注入
- Microsoft依赖注入库不支持属性注入.但是,ABP可以与第三方DI提供商(例如Autofac)集成,以实现属性注入。
- 属性注入依赖项通常被视为可选依赖项.这意味着没有它们,服务也可以正常工作.Logger就是这样的依赖项,MyService可以继续工作而无需日志记录.
public class MyService : ITransientDependency { public ILogger<MyService> Logger { get; set; } public MyService() { Logger = NullLogger<MyService>.Instance; } public void DoSomething() { //...使用 Logger 写日志... } }
IServiceProvider
直接从IServiceProvider解析服务.在这种情况下,你可以将IServiceProvider注入到你的类并使用
public class MyService : ITransientDependency { private readonly IServiceProvider _serviceProvider; public MyService(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public void DoSomething() { var taxCalculator = _serviceProvider.GetService<ITaxCalculator>(); //... } }
服务替换
在某些情况下,需要替换某些接口的实现.
- ITestManager有一个默认实现DefaultManager,但是我现在想替换成TestReplaceManager,该如何操作呢?
原生dotnet方式替换
services.Replace(ServiceDescriptor.Transient<ITestManager, TestReplaceManager>());
Abp支持
- 加上Dependency特性标签
- 加上ExposeServices特性标签
[Dependency(ReplaceServices = true)] [ExposeServices(typeof(ITestManager))] public class TestReplaceManager : ITestManager, ITransientDependency { public void Print() { Console.WriteLine("TestReplaceManager"); } }
问题
- 有时候我们实现了ITransientDependency,ISingletonDependency,IScopedDependency但是再运行是还是提示依赖注入失败?
- 实现类的名称拼写错误
- 比如接口名称为ITestAppService,但是实现类为DefaultTessAppService,这个时候编译不会报错,但是运行报错,下面会基于源码分析。
public class DefaultTessAppService : ApplicationService, ITestAppService { // .... }
- 我通过[Dependency(ReplaceServices = true)]替换服务没有生效?
- 请添加[ExposeServices(typeof(ITestManager))]显示暴露服务,下面会基于源码分析。
源码分析
- 进入到Startup.AddApplication源码
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddApplication<xxxManagementHttpApiHostModule>(); } }
- 进入到await app.ConfigureServicesAsync()源码
public async static Task<IAbpApplicationWithExternalServiceProvider> CreateAsync( [NotNull] Type startupModuleType, [NotNull] IServiceCollection services, Action<AbpApplicationCreationOptions>? optionsAction = null) { var app = new AbpApplicationWithExternalServiceProvider(startupModuleType, services, options => { options.SkipConfigureServices = true; optionsAction?.Invoke(options); }); await app.ConfigureServicesAsync(); return app; }
- 主要查看ConfigureServices下的Services.AddAssembly(assembly)方法。
public virtual async Task ConfigureServicesAsync() { // 省略... var assemblies = new HashSet<Assembly>(); //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); } } } try { await module.Instance.ConfigureServicesAsync(context); } catch (Exception ex) { throw new AbpInitializationException($"An error occurred during {nameof(IAbpModule.ConfigureServicesAsync)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex); } } // 省略... }
4.进入下面AddAssembly下AddType的逻辑
public class DefaultConventionalRegistrar : ConventionalRegistrarBase { public override void AddType(IServiceCollection services, Type type) { if (IsConventionalRegistrationDisabled(type)) { return; } // 查看是否有DependencyAttribute特性标签 var dependencyAttribute = GetDependencyAttributeOrNull(type); // 判断是否有实现接口,注入对于的类型。 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); } } } } // GetLifeTimeOrNull protected virtual ServiceLifetime? GetLifeTimeOrNull(Type type, DependencyAttribute? dependencyAttribute) { return dependencyAttribute?.Lifetime ?? GetServiceLifetimeFromClassHierarchy(type) ?? GetDefaultLifeTimeOrNull(type); } // abp 三个生命周期 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; }
5.重点到了,看下为什么名称错误为什么导致注入失败。
- 通过接口的名称去获取实现。
- 也能解释有时候不显示指定ExposeServices可能替换失败的问题
public class ExposeServicesAttribute : Attribute, IExposedServiceTypesProvider { // 省略... private static List<Type> GetDefaultServices(Type type) { var serviceTypes = new List<Type>(); foreach (var interfaceType in type.GetTypeInfo().GetInterfaces()) { var interfaceName = interfaceType.Name; if (interfaceType.IsGenericType) { interfaceName = interfaceType.Name.Left(interfaceType.Name.IndexOf('`')); } // 查询到实现类的名称是否是移除I if (interfaceName.StartsWith("I")) { interfaceName = interfaceName.Right(interfaceName.Length - 1); } // 查询到实现类的名称是否以接口名结尾 if (type.Name.EndsWith(interfaceName)) { serviceTypes.Add(interfaceType); } } return serviceTypes; } }
Abp vNext Pro
- Abp Vnext Pro Github地址 的 Vue3 实现版本 开箱即用的中后台前端/设计解决方案.
- 文档地址
- 演示地址:用户名admin 密码1q2w3E*
- Abp Vnext Pro Suite Github地址 代码生成器。
- 演示地址:用户名admin 密码1q2w3E*
- 视频教程