之前實現的那版依賴註入框架基本可用,但是感覺還是不夠靈活,而且註冊服務和解析服務在同一個地方感覺有點彆扭,有點職責分離不夠。於是借鑒 Autofac 的做法,增加了一個 `ServiceContainerBuilder` 來負責註冊服務,`ServiceContainer`負責解析服務,並且增加了一... ...
動手造輪子:實現一個簡單的依賴註入(二) --- 服務註冊優化
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