前言

书接上回,上回我们了解了 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底层整理。接下来我们可以做很多东西,比如 事务拦截器、幂等拦截器、重试拦截器、缓存拦截器等等

打好基础,后续可以自己去实现。

这里还有几个问题 ,大家可以思考下

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

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

源码及声明

当前示例代码已传至 https://gitee.com/gainorloss_259/microsoft-castle.git

如转载请注明出处,谢谢!

聊一聊如何整合Microsoft.Extensions.DependencyInjection和Castle.Core(完结篇)的更多相关文章

  1. DotNetCore跨平台~一起聊聊Microsoft.Extensions.DependencyInjection

    写这篇文章的心情:激动 Microsoft.Extensions.DependencyInjection在github上同样是开源的,它在dotnetcore里被广泛的使用,比起之前的autofac, ...

  2. 解析 Microsoft.Extensions.DependencyInjection 2.x 版本实现

    项目使用了 Microsoft.Extensions.DependencyInjection 2.x 版本,遇到第2次请求时非常高的内存占用情况,于是作了调查,本文对 3.0 版本仍然适用. 先说结论 ...

  3. 使用诊断工具观察 Microsoft.Extensions.DependencyInjection 2.x 版本的内存占用

    目录 准备工作 大量接口与实现类的生成 elasticsearch+kibana+apm asp.net core 应用 请求与快照 Kibana 上的请求记录 请求耗时的分析 请求内存的分析 第2次 ...

  4. Microsoft.Extensions.DependencyInjection 之三:展开测试

    目录 前文回顾 IServiceCallSite CallSiteFactory ServiceProviderEngine CompiledServiceProviderEngine Dynamic ...

  5. Microsoft.Extensions.DependencyInjection 之三:反射可以一战(附源代码)

    目录 前文回顾 IServiceCallSite CallSiteFactory ServiceProviderEngine CompiledServiceProviderEngine Dynamic ...

  6. Microsoft.Extensions.DependencyInjection 之二:使用诊断工具观察内存占用

    目录 准备工作 大量接口与实现类的生成 elasticsearch+kibana+apm asp.net core 应用 请求与快照 Kibana 上的请求记录 请求耗时的分析 请求内存的分析 第2次 ...

  7. Microsoft.Extensions.DependencyInjection 之一:解析实现

    [TOC] 前言 项目使用了 Microsoft.Extensions.DependencyInjection 2.x 版本,遇到第2次请求时非常高的内存占用情况,于是作了调查,本文对 3.0 版本仍 ...

  8. 使用 Microsoft.Extensions.DependencyInjection 进行依赖注入

    没有 Autofac DryIoc Grace LightInject Lamar Stashbox Unity Ninject 的日子,才是好日子~~~~~~~~~~ Using .NET Core ...

  9. MvvmLight + Microsoft.Extensions.DependencyInjection + WpfApp(.NetCore3.1)

    git clone MvvmLight失败,破网络, 就没有直接修改源码的方式来使用了 Nuget安装MvvmLightLibsStd10 使用GalaSoft.MvvmLight.Command命名 ...

  10. Microsoft.Extensions.DependencyInjection中的Transient依赖注入关系,使用不当会造成内存泄漏

    Microsoft.Extensions.DependencyInjection中(下面简称DI)的Transient依赖注入关系,表示每次DI获取一个全新的注入对象.但是使用Transient依赖注 ...

随机推荐

  1. Vue2系列(lqz)——6-Vue-cli、7-Vue插件、8-Vue第三方框架之ElementUi

    文章目录 6 Vue-CLI 项目搭建 1 单文件组件 2 Vue-CLI 项目搭建 2.1 环境搭建 2.2 项目的创建 创建项目 启动/停止项目 打包项目 package.json中 2.3 认识 ...

  2. Hello-FPGA CoaXPress 2.0 FPGA HOST IP Core PCIe Demo User Manual

    目录 1 说明 4 2 设备连接 7 3 VIVADO FPGA工程 8 4 调试说明 9 图 1‑1 资料目录 4 图 1‑2 VIVADO工程目录结构 5 图 1‑3 VS软件工程目录 5 图 1 ...

  3. SpringBoot如何缓存方法返回值?

    目录 Why? HowDo annotation MethodCache MethodCacheAspect controller SpringCache EnableCaching Cacheabl ...

  4. Unity - UIWidgets 5. Redux接入(一) 基本接入

    对响应式的数据组织结构不太熟, 直接尝试Redux走起 参考资料 Redux的基本概念 state 一个字段用于存储状态 存储state的地方称为"store", 类似Model\ ...

  5. 一文读懂强化学习:RL全面解析与Pytorch实战

    在本篇文章中,我们全面而深入地探讨了强化学习(Reinforcement Learning)的基础概念.主流算法和实战步骤.从马尔可夫决策过程(MDP)到高级算法如PPO,文章旨在为读者提供一套全面的 ...

  6. iOS性能优化之内存分析

    成功之前我们要做应该做的事情,成功之后我们才可以做喜欢做的事情.   从苹果的开发者文档里可以看到内存分类如下所示,其中 Leaked memory和 Abandoned memory 都属于应该释放 ...

  7. 关于如何来测一款app的思考

    最近工作当中需要整体测一遍app,需要全方面思考并且尽可能覆盖所有待测点,因为整理总结了这篇关于app测试的总体大纲 一.功能测试 1.1界面测试 1.1.1导航测试 ---是否易于导航.导航是否直观 ...

  8. 【Javaweb】瑞吉外卖你冲不冲?冲冲!冲!冲冲!(数据库环境搭建)(maven项目搭建)一

    图形界面创建数据库(Navicat) 命令行方式创建 瑞吉项目一共涉及到十一张表 导入表结构,既可以使用上面的图形界面,也可以使用MySQL命令: 通过命令导入表结构时,注意sql文件不要放在中文目录 ...

  9. wps表格怎么打印选中区域的内容?

    打印选中区域的内容,您可以按照以下步骤进行操作: 选择要打印的区域 打开 WPS 表格,在工作表中选择您希望打印的区域.您可以拖动鼠标或使用键盘中的方向键来选择单元格. 设置打印区域 一旦您选中了需要 ...

  10. 使用 anasible 搭建一个多 master 多 worker 的 k8s 集群

    使用 ansible 搭建一个多 master 多 worker 的 k8s 集群 kubernetes + istio 是目前最强大,也是最易于使用的服务网格方案.要使用kubernetes + i ...