首页 > 其他分享 >聊一聊如何整合Microsoft.Extensions.DependencyInjection和Castle.Core(完结篇)

聊一聊如何整合Microsoft.Extensions.DependencyInjection和Castle.Core(完结篇)

时间:2024-01-13 14:33:43浏览次数:36  
标签:完结篇 ServiceType 拦截器 Core sp descriptor 聊一聊 services var

前言

书接上回,上回我们了解了 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...");
   }
}

运行如下
image
,image

可以看到拦截器这时候是异步方法,并且可以明显看到注入被简化了。
大家可以考虑下为什么 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底层整理。接下来我们可以做很多东西,比如 事务拦截器、幂等拦截器、重试拦截器、缓存拦截器等等
打好基础,后续可以自己去实现。
这里还有几个问题 ,大家可以思考下

  1. 我们如何能整合两种拦截器 既可以传一些常量又不影响我们的服务注入拦截器
  2. 拦截器是否可以再套用拦截器
  3. 假设我们再日志拦截器上打了日志拦截器 会怎么样

这些都是一些比较有意思的问题,相信这些问题的思考会让大家对动态代理的理解更深,并可以灵活的将其用到自己的项目中。

源码及声明

当前示例代码已传至 https://gitee.com/gainorloss_259/microsoft-castle.git
如转载请注明出处,谢谢!

标签:完结篇,ServiceType,拦截器,Core,sp,descriptor,聊一聊,services,var
From: https://www.cnblogs.com/gainorloss/p/17962327

相关文章

  • [转][C#][.net Core]
    中文提示:连接数据库过程中发生错误,检查服务器是否正常连接字符串是否正确,错误信息:Onlytheinvariantcultureissupportedinglobalization-invariantmode.Seehttps://aka.ms/GlobalizationInvariantModeformoreinformation.(Parameter'name')en-usisaninvalid......
  • Asp .Net Core 系列: 集成 Consul 实现 服务注册与健康检查
    目录什么是Consul?安装和运行ConsulAsp.NetCore如何集成Consul实现服务注册和健康检查Consul.AspNetCore中的AddConsul和AddConsulServiceRegistration方法究竟做了什么?AddConsul方法AddConsulServiceRegistration方法配置Consul检查服务封装成扩展效果什么是C......
  • 聊一聊如何整合Microsoft.Extensions.DependencyInjection和Castle.Core(三)
    前言今天的第三篇,感觉没啥人看呀,难道没有兄弟跟我有同样的整合需求吗???手动,本文会简短一些,介绍下CastleCore作为代理库的一些缺点甚至是硬伤异步支持先上代码///<summary>///异常捕获、日志记录和耗时监控拦截器2024-1-1221:28:22///</summary>publicclassCatch......
  • netcore webpi 通过signalr 给vue项目推送消息
     最近项目上需要做个服务给前端推消息,首先就想到了signalr,关于signalr详情可以参考微软官方文档(ASP.NETCoreSignalR概述|MicrosoftLearn),微软现在也有使用教程(ASP.NETCoreSignalR入门|MicrosoftLearn)微软教程是通过使用库管理器(LibMan)从unpkg 获取客户端库,如......
  • .NET Core MemoryCache缓存批量获取Key或者删除
    .NetCore下使用缓存,除了大家耳熟能详的Redis做分布式缓存外,本地内存缓存也会一起结合来使用,它存取更快,使我们的应用达到极致性能要求。这也是我们经常提到的3级或者4级缓存,每一层都有自己的使用场景,优缺点,结合业务特点来选择合适的才是王道。这里我们就使用Net原生的Microsoft......
  • 用友全球司库十问(完结篇)|如何构建司库信创体系化能力?
    用友BIP全球司库十问系列,以聚焦企业金融资源运作,深度剖析企业价值创造,通过详解全球司库管理体系建设过程,向您献上精彩分呈的最佳实践与精进不休的理论总结。它涵盖账户管理、资金预算、资金结算、票据管理、资金流动性分析、资金流动调配、融资管理、资金计划、银企直联、资金管理......
  • 无涯教程-Redis - ZSCORE 命令函数
    RedisZSCORE命令返回键排序后的元素的得分,如果元素不存在于排序集中,或者键不存在,则返回nil。ZSCORE-返回值返回元素的分数值。ZSCORE-语法以下是RedisZSCORE命令的基本语法。redis127.0.0.1:6379>ZSCOREkeymemberZSCORE-示例redis127.0.0.1:6379>ZADDm......
  • 无涯教程-Redis - ZREMRANGEBYSCORE 命令函数
    RedisZREMRANGEBYSCORE命令删除存储在键中的排序集中的所有元素,这些元素的分数介于最小和最大(含)之间。ZREMRANGEBYSCORE-返回值返回删除的元素数量。ZREMRANGEBYSCORE-语法以下是RedisZREMRANGEBYSCORE命令的基本语法。redis127.0.0.1:6379>ZREMRANGEBYSCORE......
  • AWS IoT Core 实战指南
    AmazonWebServices(AWS)提供了全球范围内的托管服务,其中包括AWSIoTCore,专为连接和管理物联网设备而设计。这个实战指南将带你一步步了解如何使用AWSIoTCore来注册设备、提高安全性、进行通信以及利用设备影子功能。设备注册1.创建Thing(设备)在AWSIoT控制台中,创建一......
  • openGauss学习笔记-188 openGauss 数据库运维-常见故障定位案例-core问题定位
    openGauss学习笔记-188openGauss数据库运维-常见故障定位案例-core问题定位188.1磁盘满故障引起的core问题188.1.1问题现象TPCC运行时,注入磁盘满故障,数据库进程gaussdbcore掉,如下图所示。188.1.2原因分析数据库本身机制,在磁盘满时,Xlog日志无法进行写入,通过panic日志退......