.Net Core 原生支持依赖注入(Dependency Injection)
构造注入
一般在Service中通过加入Add…将服务加入DI构造容器中,如下代码:
public static class Programm
{
public static void Main(string[] args)
{
CreateDefaultHostBuider(args).Build().Run();
}
private static IHostBuilder CreateDefaultHostBuider(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
services.AddLogging()
.AddTransient<IAspectLoadService, AspectLoadService>() // Service Injection
.AddHostedService<Start>()
);
}
}
看下接口IAspectLoadService以及它的实现IAspectLoadService。
public interface IAspectLoadService
{
IAspect Load(IAspectReference reference);
}
public class AspectLoadService : IAspectLoadService
{
public IAspect Load(IAspectReference reference)
{
return new Aspect(reference, "TestAspect");
}
}
所以在Start中我们需要使用AspectLoadService时,只要将接口IAspectLoadService放入构造函数中,如下:
public class Start : BackgroundService
{
private readonly IAspectLoadService _aspectLoadService;
public Start(IAspectLoadService aspectLoadService) // 构造函数注入
{
_aspectLoadService = aspectLoadService;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_aspectLoadService.Load(new AspectReference());
return Task.CompletedTask;
}
}
不足之处
以上方法可以使得DI在项目中使用起来,但是每次添加新的服务,需要在ConfigureServices中注册。这样的实现很容易出现,
- 忘记注册
- 在多人开发团队中,很有可能同时修改注册代码导致冲突。
- 如果注册服务较多,不利于代码阅读和整洁。
改进目标
我们希望服务是否可被注入是由服务自身决定的,这样就可以解决上述问题了。
改进思路
采用反射
可以通过给服务加注标注就可以决定改服务是否可以被注入,如下,
[ServiceLocate(typeof(IAspectLoadService))] // 给Service标注是否可以被注入
public class AspectLoadService : IAspectLoadService
{
public IAspect Load(IAspectReference reference)
{
return new Aspect(reference, "TestAspect");
}
}
这样就可以避免开发之间的影响,因为只关注自己开发的服务了。
在注册服务的时候统一查看ServiceLocate的标注,然后统一注入容器,如下,
public static class Programm
{
public static void Main(string[] args)
{
CreateDefaultHostBuider(args).Build().Run();
}
private static IHostBuilder CreateDefaultHostBuider(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
services.AddLogging()
.RegisterDomain("TestCommon") // 统一查看ServiceLocate标注,然后注册
.AddHostedService<Start>()
);
}
}
这里的"TestComon"是要查看的程序集的名称。
具体实现
先看下ServiceLocate,该Attribute需要两个参数:服务的接口以及注册服务的类型,默认为Transient
public class ServiceLocateAttribute : Attribute
{
private Type _iService;
private ServiceType _serviceType;
public ServiceLocateAttribute(Type iService, ServiceType type = ServiceType.Transient)
{
_iService = iService;
_serviceType = type;
}
public Type IService { get => _iService; set => _iService = value; }
public ServiceType ServiceType { get => _serviceType; set => _serviceType = value; }
}
再看下方法RegisterDomain,
public static IServiceCollection RegisterDomain(this IServiceCollection services, params string[] domains)
{
var domainList = domains.Append("Common.Core").ToList();
var asms = domainList.Select(domain => Assembly.Load(domain)).ToList();
var impls = asms.SelectMany(asm => asm.GetExportedTypes().Where(t => !t.IsInterface).ToList()).ToList();
foreach (var impl in impls)
{
var serviceLocateObject = impl.GetCustomAttribute(typeof(ServiceLocateAttribute));
if (serviceLocateObject != null)
{
var iFace = (serviceLocateObject as ServiceLocateAttribute).IService;
var serviceType = (serviceLocateObject as ServiceLocateAttribute).ServiceType;
switch (serviceType)
{
case ServiceType.Scoped:
services.AddScoped(iFace, impl);
break;
case ServiceType.Singleton:
services.AddSingleton(iFace, impl);
break;
default:
services.AddTransient(iFace, impl);
break;
}
}
}
return services;
}
以下是添加默认程序集(自定义)
var domainList = domains.Append(“Common.Core”).ToList();
通过反射获取所有需要查看的程序集
var asms = domainList.Select(domain => Assembly.Load(domain)).ToList();
获取所有程序集中的实现,
var impls = asms.SelectMany(asm => asm.GetExportedTypes().Where(t => !t.IsInterface).ToList()).ToList();
然后进入循环检查和服务注入,
foreach (var impl in impls)
{
var serviceLocateObject = impl.GetCustomAttribute(typeof(ServiceLocateAttribute));
if (serviceLocateObject != null)
{
var iFace = (serviceLocateObject as ServiceLocateAttribute).IService;
var serviceType = (serviceLocateObject as ServiceLocateAttribute).ServiceType;
switch (serviceType)
{
case ServiceType.Scoped:
services.AddScoped(iFace, impl);
break;
case ServiceType.Singleton:
services.AddSingleton(iFace, impl);
break;
default:
services.AddTransient(iFace, impl);
break;
}
}
}