.NET Core 依賴注入改造(1)- 命名服務(wù)
.NET Core 依賴注入改造(2)- 委托轉(zhuǎn)換
.NET Core 依賴注入改造(3)- ILogger
.NET Core 依賴注入改造(4)- ActivatorUtilities
.NET Core 依賴注入改造(5)- Context
.NET Core 依賴注入改造(附1)- Autowired
一闷叉、
一定有人跟我一樣想過趁蕊,在任何時候都可以輕易的得到一個IServerProvider
;
在Web項目中我們可以通過HttpContext.RequestServices
來獲取鹿蜀,但是其他項目目前官方還沒有這樣的上下文對象可用夏哭;
所以老規(guī)矩自己改造一個检柬。
二、
真正改造之前竖配,需要了解一下:Scope何址。
Scope可以理解為作用域;
ServiceScope為某個ServiceProvider對象圈定了一個“作用域”进胯,枚舉類型ServiceLifetime中的Scoped選項指的就是這么一個ServiceScope用爪。在依賴注入的應(yīng)用編程接口中,ServiceScope通過一個名為IServiceScope的接口來表示胁镐。如下面的代碼片段所示偎血,繼承自IDisposable接口的IServiceScope具有一個唯一的只讀屬性ServiceProvider返回確定這個服務(wù)范圍邊界的ServiceProvider。表示ServiceScope由它對應(yīng)的工廠ServiceScopeFactory來創(chuàng)建盯漂,后者體現(xiàn)為具有如下定義的接口IServiceScopeFactory颇玷。
摘自:artech
三、
Scope的創(chuàng)建就缆,正如上面所說帖渠,涉及到了三個對象IServiceProvider
,IServiceScopeFactory
和IServiceScope
:
從
IServiceProvider
中獲取IServiceScopeFactory
服務(wù)竭宰,創(chuàng)建IServiceScope
空郊,從Scope中得到限定作用域的IServiceProvider
用代碼來表示就是:
IServiceProvider serviceProvider = ...份招;
var factory = (IServiceScopeFactory)serviceProvider.GetService(typeof(IServiceScopeFactory));
using(var scope = factory.CreateScope())
{
var provider = scope.ServiceProvider;
}
ps:IServiceScope
同時也是一個IDispose
對象,這是非常重要的狞甚,這可以使我們方便的跟蹤IServiceScope
的生命周期
四脾还、
正確的Scope使用方式應(yīng)該是有層級的;
同一線程同一作用域中同一層級的Scope
應(yīng)該只有一個
如:
using (var scope1 = _provider.CreateScope())
{
using (var scope2 = scope1.ServiceProvider.CreateScope())
{
Parallel.For(0, 10, i => // 異步時入愧,不同線程可以存在同一層級的Scope
{
using (var scope3 = scope2.ServiceProvider.CreateScope())
{
using (var scope4 = scope3.ServiceProvider.CreateScope())
{
}
}
});
}
}
下面這種用法是錯的:scope1/2/3/4都屬于同一層級;
using (var scope1 = _provider.CreateScope())
using (var scope2 = _provider.CreateScope())
using (var scope3 = _provider.CreateScope())
using (var scope4 = _provider.CreateScope())
{
action(scope1);
action(scope2);
action(scope3);
action(scope4);
// 在這種情況下鄙漏,上下文中存在多個同級作用域, Scope 無法確定
}
五棺蛛、
了解了上面這些東西之后怔蚌,自己要做一個服務(wù)上下文還是比較簡單的,首先分別創(chuàng)建IServiceProvider
旁赊,IServiceScopeFactory
和IServiceScope
3個對象的裝飾類桦踊,在不改變原有邏輯的基礎(chǔ)上,增加新的行為终畅。
在裝飾類中拓展Scope的創(chuàng)建和銷毀行為籍胯,創(chuàng)建時將Scope中的IServiceProvider
放到上下文中,在Scope銷毀時离福,從上下文中移除杖狼,并將之前的IServiceProvider
重新放進去。
源碼在這里
下面摘取部分重要代碼
IServiceProvider裝飾類
class SupportContextServiceProvider : IServiceProvider
{
private readonly IServiceProvider _provider;
public SupportContextServiceProvider Parent { get; }
public SupportContextServiceProvider Root { get; }
public SupportContextServiceProvider(IServiceProvider provider, SupportContextServiceProvider parent)
{
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
Parent = parent;
Root = parent?.Root ?? this;
ServiceContext.Push(this); // 設(shè)置到上下文
}
private int _disposed;
public bool IsDisposed => _disposed > 0;
internal void Dispose()
{
if (_disposed == 0 && Interlocked.Increment(ref _disposed) == 1)
{
ServiceContext.PopTo(Parent); // 重置上下文到上一層
}
}
public object GetService(Type serviceType)
{
var value = _provider.GetService(serviceType);
if (value is IServiceScopeFactory factory)
{
return new SupportContextServiceScopeFactory(this, factory); // 裝飾Factory
}
if (ReferenceEquals(value, _provider))
{
return this; // 裝到底
}
return value;
}
}
這個類主要用于將 IServiceProvider
設(shè)置到上下文妖爷,另外對IServiceScopeFactory
服務(wù)進行裝飾
IServiceScopeFactory裝飾類
class SupportContextServiceScopeFactory : IServiceScopeFactory
{
private readonly SupportContextServiceProvider _provider;
private readonly IServiceScopeFactory _factory;
public SupportContextServiceScopeFactory(SupportContextServiceProvider provider, IServiceScopeFactory factory)
{
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
}
public IServiceScope CreateScope() => new SupportContextServiceScope(_provider, _factory.CreateScope());
}
這個類只做一件事蝶涩,裝飾IServiceScope
IServiceScope 裝飾類
class SupportContextServiceScope : IServiceScope
{
private readonly IServiceScope _scope;
public SupportContextServiceScope(SupportContextServiceProvider parent, IServiceScope scope)
{
if (parent == null)
{
throw new ArgumentNullException(nameof(parent));
}
_scope = scope ?? throw new ArgumentNullException(nameof(scope));
ServiceProvider = new SupportContextServiceProvider(scope.ServiceProvider, parent);
}
public IServiceProvider ServiceProvider { get; }
public void Dispose()
{
_scope.Dispose();
((SupportContextServiceProvider)ServiceProvider).Dispose();
}
~SupportContextServiceScope()
{
((SupportContextServiceProvider)ServiceProvider).Dispose();
}
}
做2件事,裝飾IServiceProvider
絮识,并在銷毀時調(diào)用SupportContextServiceProvider.Dispose
六绿聘、
現(xiàn)在就剩下一個上下文對象 ServiceContext
,這個對象比較復(fù)雜次舌,所以放到最后來再講熄攘;
首先,在.net core中有一個對象是專門用來處理類似“上下文”這種需求的AsyncLocal<T>彼念;
基于任務(wù)的異步編程模型傾向于抽象的線程挪圾,使用
AsyncLocal<T>
實例可用于跨線程保存數(shù)據(jù)。
但是考慮跨線程銷毀Scope的情況(雖然使用中需要避免這種情況)国拇,但代碼還是要嚴(yán)謹(jǐn)洛史;
所以不能直接使用AsyncLocal<IServiceProvider>
惯殊;
使用一個ServiceProviderAccessor
來訪問酱吝;
而這個ServiceProviderAccessor
只做一件事,當(dāng)provider
被標(biāo)識為IsDisposed
時返回provider.Parent
[DebuggerDisplay("{DebugText}")]
class ServiceProviderAccessor
{
public ServiceProviderAccessor(SupportContextServiceProvider provider) => _provider = provider;
private SupportContextServiceProvider _provider;
internal SupportContextServiceProvider Provider
{
get
{
var current = _provider;
while (current?.IsDisposed == true)
{
_provider = current = current.Parent;
}
return current;
}
}
private string DebugText() =>
$"Provider: {_provider}{(_provider?.IsDisposed == true ? " (disposed)" : "")}";
}
他的初始化就放在IServiceProvider
裝飾類里土思;
class SupportContextServiceProvider : IServiceProvider
{
private SupportContextServiceProvider() => Accessor = new ServiceProviderAccessor(this);
internal ServiceProviderAccessor Accessor { get; }
}
ServiceContext 上下文
public static class ServiceContext
{
private static AsyncLocal<ServiceProviderAccessor> _value =
new AsyncLocal<ServiceProviderAccessor>(LocalValueChanged);
public static IServiceProvider Provider => _value.Value?.Provider;
private static SupportContextServiceProvider ProviderImpl
{
get => _value.Value?.Provider;
set
{
var accessor = value.Accessor;
if (!ReferenceEquals(accessor, _value.Value))
{
_value.Value = value.Accessor;
}
}
}
internal static void Push(SupportContextServiceProvider provider)
{
if (provider != null)
{
ProviderImpl = provider;
}
}
internal static bool PopTo(SupportContextServiceProvider provider)
{
provider = provider.Accessor.Provider;
if (provider != null)
{
ProviderImpl = provider;
}
}
private static void LocalValueChanged(AsyncLocalValueChangedArgs<ServiceProviderAccessor> obj)
{
if (obj.ThreadContextChanged)
{
var prev = obj.PreviousValue?.Provider;
var curr = obj.CurrentValue?.Provider;
if (curr == null || prev?.IsDisposed == false)
{
ProviderImpl = prev;
}
}
}
}
LocalValueChanged
方法是當(dāng)AsyncLocal<T>
值發(fā)生變更時被調(diào)用的务热;
其中obj.ThreadContextChanged
用于指示是否是由于上下文切換引起的值改變忆嗜;
當(dāng)因為線程切換發(fā)生Scope變更時,如果前一個Scope還沒有銷毀崎岂,那么就帶回來捆毫;
為了處理類似這種情況:
IServiceProvider provider = ...;
IServiceScope scope; //上下文 = provider
await Task.Run(() =>
{
scope = provider.CreateScope(); // 上下文 = scope
});
action(scope); // 上下文 = provider (這里顯然是錯的)
scope.Dispose();
有看官可能會說了,哪有人寫這樣的代碼...
那我給他換個樣子:
IServiceProvider provider = ...;
using (IServiceScope scope = await CreateScopeAsync(provider))
{
action(scope);
}
與剛才那個是一回事冲甘;
再來體會下這句話
當(dāng)因為線程切換發(fā)生Scope變更時绩卤,如果前一個Scope還沒有銷毀,那么就帶回來
ServiceContextFactory
public static class ServiceContextFactory
{
public static IServiceProvider Create(IServiceProvider provider) =>
new SupportContextServiceProvider(provider, null);
}
七江醇、
在 Core Web 中測試一下:
先在 Startup.ConfigureServices
創(chuàng)建支持上下文的服務(wù)提供程序
public class Startup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
return ServiceContextFactory.Create(services.BuildServiceProvider());
}
}
然后在Controller
中驗證下
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
var b = ReferenceEquals(HttpContext.RequestServices, ServiceContext.Provider);
return new string[] { "value1", "value2"};
}
結(jié)果
八濒憋、
github:https://github.com/blqw/blqw.DI/tree/master/src/blqw.DI.Context
nuget:https://www.nuget.org/packages/blqw.DI.Context