.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和IServiceScope3個對象的裝飾類,在不改變原有邏輯的基礎(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