動手造輪子:實現一個簡單的依賴注入(二) --- 服務註冊優化
阿新 • • 發佈:2019-11-28
動手造輪子:實現一個簡單的依賴注入(二) --- 服務註冊優化
Intro
之前實現的那版依賴注入框架基本可用,但是感覺還是不夠靈活,而且註冊服務和解析服務在同一個地方感覺有點彆扭,有點職責分離不夠。於是借鑑 Autofac 的做法,增加了一個 ServiceContainerBuilder
來負責註冊服務,ServiceContainer
負責解析服務,並且增加了一個 ServiceContainerModule
可以支援像 Autofac 中 Module
/RegisterAssemblyModules
一樣註冊服務
實現程式碼
ServiceContainerBuilder
增加 ServiceContainerBuild
IServiceContainer
的擴充套件方法變成 IServiceContainerBuilder
的擴充套件
public interface IServiceContainerBuilder { IServiceContainerBuilder Add(ServiceDefinition item); IServiceContainerBuilder TryAdd(ServiceDefinition item); IServiceContainer Build(); } public class ServiceContainerBuilder : IServiceContainerBuilder { private readonly List<ServiceDefinition> _services = new List<ServiceDefinition>(); public IServiceContainerBuilder Add(ServiceDefinition item) { if (_services.Any(_ => _.ServiceType == item.ServiceType && _.GetImplementType() == item.GetImplementType())) { return this; } _services.Add(item); return this; } public IServiceContainerBuilder TryAdd(ServiceDefinition item) { if (_services.Any(_ => _.ServiceType == item.ServiceType)) { return this; } _services.Add(item); return this; } public IServiceContainer Build() => new ServiceContainer(_services); }
IServiceContainer
增加 ServiceContainerBuilder
之後就不再支援註冊服務了,ServiceContainer
這個型別也可以變成一個內部類了,不必再對外暴露
public interface IServiceContainer : IScope, IServiceProvider { IServiceContainer CreateScope(); } internal class ServiceContainer : IServiceContainer { private readonly IReadOnlyList<ServiceDefinition> _services; public ServiceContainer(IReadOnlyList<ServiceDefinition> serviceDefinitions) { _services = serviceDefinitions; // ... } // 此處約省略一萬行程式碼 ... }
ServiceContainerModule
定義了一個 ServiceContainerModule
來實現像 Autofac 那樣,在某一個程式集內定義一個 Module 註冊程式集內需要註冊的服務,在服務註冊的地方呼叫 RegisterAssemblyModules
來掃描所有程式集並註冊自定義 ServiceContainerModule
需要註冊的服務
public interface IServiceContainerModule
{
void ConfigureServices(IServiceContainerBuilder serviceContainerBuilder);
}
public abstract class ServiceContainerModule : IServiceContainerModule
{
public abstract void ConfigureServices(IServiceContainerBuilder serviceContainerBuilder);
}
自定義 ServiceContainerModule
使用示例:
public class TestServiceContainerModule : ServiceContainerModule
{
public override void ConfigureServices(IServiceContainerBuilder serviceContainerBuilder)
{
serviceContainerBuilder.AddSingleton<IIdGenerator>(GuidIdGenerator.Instance);
}
}
RegisterAssemblyModules
擴充套件方法實現如下:
public static IServiceContainerBuilder RegisterAssemblyModules(
[NotNull] this IServiceContainerBuilder serviceContainerBuilder, params Assembly[] assemblies)
{
#if NET45
// 解決 asp.net 在 IIS 下應用程式域被回收的問題
// https://autofac.readthedocs.io/en/latest/register/scanning.html#iis-hosted-web-applications
if (null == assemblies || assemblies.Length == 0)
{
if (System.Web.Hosting.HostingEnvironment.IsHosted)
{
assemblies = System.Web.Compilation.BuildManager.GetReferencedAssemblies()
.Cast<Assembly>().ToArray();
}
}
#endif
if (null == assemblies || assemblies.Length == 0)
{
assemblies = AppDomain.CurrentDomain.GetAssemblies();
}
foreach (var type in assemblies.WhereNotNull().SelectMany(ass => ass.GetTypes())
.Where(t => t.IsClass && !t.IsAbstract && typeof(IServiceContainerModule).IsAssignableFrom(t))
)
{
try
{
if (Activator.CreateInstance(type) is ServiceContainerModule module)
{
module.ConfigureServices(serviceContainerBuilder);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
return serviceContainerBuilder;
}
使用示例
使用起來除了註冊服務變化了之外,別的地方並沒有什麼不同,看一下單元測試程式碼
public class DependencyInjectionTest : IDisposable
{
private readonly IServiceContainer _container;
public DependencyInjectionTest()
{
var containerBuilder = new ServiceContainerBuilder();
containerBuilder.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
containerBuilder.AddScoped<IFly, MonkeyKing>();
containerBuilder.AddScoped<IFly, Superman>();
containerBuilder.AddScoped<HasDependencyTest>();
containerBuilder.AddScoped<HasDependencyTest1>();
containerBuilder.AddScoped<HasDependencyTest2>();
containerBuilder.AddScoped<HasDependencyTest3>();
containerBuilder.AddScoped(typeof(HasDependencyTest4<>));
containerBuilder.AddTransient<WuKong>();
containerBuilder.AddScoped<WuJing>(serviceProvider => new WuJing());
containerBuilder.AddSingleton(typeof(GenericServiceTest<>));
containerBuilder.RegisterAssemblyModules();
_container = containerBuilder.Build();
}
[Fact]
public void Test()
{
var rootConfig = _container.ResolveService<IConfiguration>();
Assert.Throws<InvalidOperationException>(() => _container.ResolveService<IFly>());
Assert.Throws<InvalidOperationException>(() => _container.ResolveRequiredService<IDependencyResolver>());
using (var scope = _container.CreateScope())
{
var config = scope.ResolveService<IConfiguration>();
Assert.Equal(rootConfig, config);
var fly1 = scope.ResolveRequiredService<IFly>();
var fly2 = scope.ResolveRequiredService<IFly>();
Assert.Equal(fly1, fly2);
var wukong1 = scope.ResolveRequiredService<WuKong>();
var wukong2 = scope.ResolveRequiredService<WuKong>();
Assert.NotEqual(wukong1, wukong2);
var wuJing1 = scope.ResolveRequiredService<WuJing>();
var wuJing2 = scope.ResolveRequiredService<WuJing>();
Assert.Equal(wuJing1, wuJing2);
var s0 = scope.ResolveRequiredService<HasDependencyTest>();
s0.Test();
Assert.Equal(s0._fly, fly1);
var s1 = scope.ResolveRequiredService<HasDependencyTest1>();
s1.Test();
var s2 = scope.ResolveRequiredService<HasDependencyTest2>();
s2.Test();
var s3 = scope.ResolveRequiredService<HasDependencyTest3>();
s3.Test();
var s4 = scope.ResolveRequiredService<HasDependencyTest4<string>>();
s4.Test();
using (var innerScope = scope.CreateScope())
{
var config2 = innerScope.ResolveRequiredService<IConfiguration>();
Assert.True(rootConfig == config2);
var fly3 = innerScope.ResolveRequiredService<IFly>();
fly3.Fly();
Assert.NotEqual(fly1, fly3);
}
var flySvcs = scope.ResolveServices<IFly>();
foreach (var f in flySvcs)
f.Fly();
}
var genericService1 = _container.ResolveRequiredService<GenericServiceTest<int>>();
genericService1.Test();
var genericService2 = _container.ResolveRequiredService<GenericServiceTest<string>>();
genericService2.Test();
}
public void Dispose()
{
_container.Dispose();
}
}
Reference
- https://github.com/WeihanLi/WeihanLi.Common/tree/dev/src/WeihanLi.Common/DependencyInjection
- https://www.cnblogs.com/weihanli/p/implement-dependency-injection-01.html
- https://www.cnblogs.com/weihanli/p/implement-dependency-injection.html
- https://autofac.org/
- https://autofac.readthedocs.io/en/latest/register/scanning.html