聊一聊如何整合Microsoft.Extensions.DependencyInjection和Castle.Core(完结篇)
前言
書接上回,上回我們了解了 castle 代理的一些缺點,本文將開始操作整合 Microsoft.Extension.Dependency和Castle,以讓默認的容器可以支持攔截器
我們將以進階的形式逐步完善我們的封裝,以實現一個更方便易用、普適、高性能的基礎設施庫。
基礎版
還是先上代碼, 這是基礎版本我們要達成的目標,僅需定義一個特性即可完成攔截的目標
/// <summary>
///
/// </summary>
public abstract class InterceptorBaseAttribute : Attribute, IInterceptor
{
void IInterceptor.Intercept(IInvocation invocation)
{
var returnType = invocation.Method.ReturnType;
var builder = AsyncMethodBuilder.TryCreate(returnType);
if (builder != null)
{
var asyncInvocation = new AsyncInvocation(invocation);
var stateMachine = new AsyncStateMachine(asyncInvocation, builder, task: InterceptAsync(asyncInvocation));
builder.Start(stateMachine);
invocation.ReturnValue = builder.Task();
}
else
{
Intercept(invocation);
}
}
protected virtual void Intercept(IInvocation invocation) { }
protected abstract ValueTask InterceptAsync(IAsyncInvocation invocation);
......
}
如上是我們定義的攔截器基類,我們想要達到的目標是,只要繼承該基類,并覆寫InterceptAsync 方法即可實現具有特定功能的攔截類,而容器會自動代理到該攔截類,實現攔截。
這里要感謝 https://github.com/stakx/DynamicProxy.AsyncInterceptor 的作者,該庫采用 MIT 的許可使用協議,我們可以直接參考使用。
接下來,是重頭戲,考慮到易用性,我們以 Microsoft.Extension.DependencyInjection 為基本庫,實現一個擴展類,用于實現攔截器功能。
代碼如下:
public static class CastleServiceCollectionExtensions
{
public static IServiceCollection ConfigureCastleDynamicProxy(this IServiceCollection services)
{
services.TryAddSingleton<ProxyGenerator>(sp => new ProxyGenerator());
//TODO:1.從IServiceCollection中獲取 方法定義InterceptorBaseAttribute特性子類的ServiceDescriptor
//TODO:2.逐個處理,獲取每個ServiceDescriptor中的ServiceType,識別是具體類還是接口,然后獲取InterceptorBaseAttribute特性子類的實例
//作為攔截器,借用proxyGenerator 去創建對應的代理然后添加到IServiceCollection中
//TODO:3 移除原始對應的ServiceType注冊
return services;
}
}
在注釋中我們簡單描述了該擴展方法的實現過程,我們采用移花接木的方式替換掉原有ServiceType的注冊,將代理對象注冊為ServiceType的實現即可。
第一步我們這么實現
var descriptors = services.Where(svc =>svc.ServiceType.GetMethods()
.Any(i => i.GetCustomAttributes(false).Any(i => i.GetType().IsAssignableTo(typeof(InterceptorBaseAttribute))))).ToList();
第二步的核心是 ServiceDescriptor 中 三種生成場景的分開處理,至于是哪三種場景可以看下我的第一篇文章 https://www.cnblogs.com/gainorloss/p/17961153
- descriptor.ImplementationType 有值:已知ServiceType和ImplementationType
偽代碼如下
implementationFactory = sp =>
{
var generator = sp.GetRequiredService<ProxyGenerator>();
var interceptors = GetInterceptors(descriptor.ServiceType);//獲取攔截器 galoS@2024-1-12 14:47:47
var proxy = descriptor.ServiceType.IsClass
? generator.CreateClassProxy(descriptor.ServiceType, interceptors.ToArray())
: generator.CreateInterfaceProxyWithoutTarget(descriptor.ServiceType, interceptors.ToArray());
return proxy;
};
- descriptor.ImplementationInstance 有值:已知ServiceType和 實現對象實例
implementationFactory = sp =>
{
var generator = sp.GetRequiredService<ProxyGenerator>();
var interceptors = GetInterceptors(descriptor.ServiceType, sp);//獲取攔截器 galoS@2024-1-12 14:47:47
var proxy = descriptor.ServiceType.IsClass
? generator.CreateClassProxyWithTarget(descriptor.ServiceType, descriptor.ImplementationInstance, interceptors.ToArray())
: generator.CreateInterfaceProxyWithTarget(descriptor.ServiceType, descriptor.ImplementationInstance, interceptors.ToArray());
return proxy;
};
- descriptor.ImplementationFactory 有值:已知ServiceType和 實現工廠方法
implementationFactory = sp =>
{
var generator = sp.GetRequiredService<ProxyGenerator>();
var interceptors = GetInterceptors(descriptor.ServiceType, sp);//獲取攔截器 galoS@2024-1-12 14:47:47
var proxy = descriptor.ServiceType.IsClass
? generator.CreateClassProxyWithTarget(descriptor.ServiceType, descriptor.ImplementationInstance, interceptors.ToArray())
: generator.CreateInterfaceProxyWithTarget(descriptor.ServiceType, descriptor.ImplementationInstance, interceptors.ToArray());
return proxy;
};
可以看到 2,3比較雷同,因為拿到 實例和通過委托傳入IServiceProvider拿到實例,其實結果是相似的,最終我們都使用工廠注入的形式 生成新的 ServiceDescriptor services.AddTransinet(descriptor.ServiceType, implementationFactory);。
最后一步 移除即可
偽代碼如下services.Remove(descriptor);
改造一下之前的代碼并測試
var services = new ServiceCollection();
services.AddLogging();//此處添加日志服務 偽代碼 以便獲取ILogger<SampleService>
services.TryAddTransient<SampleService>();
services.TryAddTransient<ISampleService, SampleService>();
services.ConfigureCastleDynamicProxy();//一定要在最后,不然會有些服務無法代理到 2024-1-13 13:53:05
var sp = services.BuildServiceProvider();
var proxy = sp.GetRequiredService<SampleService>();
var name = await proxy.ShowAsync();
/// <summary>
/// 異常捕獲、日志記錄和耗時監控 攔截器 2024-1-12 21:28:22
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class CatchLoggingInterceptor : InterceptorBaseAttribute
{
protected override async ValueTask InterceptAsync(IAsyncInvocation invocation)
{
//TODO:類注釋所寫的邏輯
await Console.Out.WriteLineAsync("Interceptor starting...");
Console.WriteLine("Interceptor starting...");
await invocation.ProceedAsync();
await Console.Out.WriteLineAsync("Interceptor ended...");
}
}
運行如下
,
可以看到攔截器這時候是異步方法,并且可以明顯看到注入被簡化了。
大家可以考慮下為什么 services.ConfigureCastleDynamicProxy() 一定要在BuildServiceProvider()之前,其他注入之后
進階版本
進階 版本這里我們不再詳細描述,直接看源碼 https://gitee.com/gainorloss_259/microsoft-castle.git
主要解決的問題是castle 攔截器不支持 依賴ioc的服務
使用偽代碼如下
public class SampleService : ISampleService
{
[CatchLoggingInterceptor]
[Interceptor(typeof(LoggingInterceptor))]//第二種使用方式
public virtual Task<string> ShowAsync()
{
Console.WriteLine(nameof(ShowAsync));
return Task.FromResult(nameof(ShowAsync));
}
}
//定義攔截器
internal class LoggingInterceptor : InterceptorBase
{
private readonly ILogger<LoggingInterceptor> _logger;
public LoggingInterceptor(ILogger<LoggingInterceptor> logger)
{
_logger = logger;
}
protected override async ValueTask InterceptAsync(IAsyncInvocation invocation)
{
await Console.Out.WriteLineAsync(nameof(LoggingInterceptor));
await invocation.ProceedAsync();
}
}
總結
以上 整合的核心方案及細節已經介紹完畢了,接下來有時間的話可以出一篇對本整合做性能測試的文章;
AOP 是一個很強大的東西,我們基本已經完成了一個比較普適、比較易用的aop底層整理。接下來我們可以做很多東西,比如 事務攔截器、冪等攔截器、重試攔截器、緩存攔截器等等
打好基礎,后續可以自己去實現。
這里還有幾個問題 ,大家可以思考下
- 我們如何能整合兩種攔截器 既可以傳一些常量又不影響我們的服務注入攔截器
- 攔截器是否可以再套用攔截器
- 假設我們再日志攔截器上打了日志攔截器 會怎么樣
這些都是一些比較有意思的問題,相信這些問題的思考會讓大家對動態代理的理解更深,并可以靈活的將其用到自己的項目中。
源碼及聲明
當前示例代碼已傳至 https://gitee.com/gainorloss_259/microsoft-castle.git
如轉載請注明出處,謝謝!
總結
以上是生活随笔為你收集整理的聊一聊如何整合Microsoft.Extensions.DependencyInjection和Castle.Core(完结篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java日期时间处理详解
- 下一篇: 春眠不觉晓,Java数据类型知多少?基础