基于 Roslyn 的 AOP 切面拦截器
基于 Roslyn 的 AOP 切面拦截器
每一个编程小白,都有一个自己喜欢的小目标,我的小目标就是,写一个自己的 AOP 拦截器。
第一次用 PostSharp AOP 框架,感觉好神奇,怎么一个特性标记,方法就可以拦截了,好奇妙呀!所以一直心心念念想实现自己的 AOP 框架。
先后了解了一些 AOP 原理,大部分都是基于 IL 静态注入的(在编译时进行的代码注入),这个对于普通开发者来说,
学习成本有点高,所以自己基于 Roslyn 及 .NET DispatchProxy 代理来实现了一个 AOP 切面拦截器。
源代码:https://gitee.com/lan-xiaofang/roslyn-proxy.git/
参考文献:
Roslyn 也叫 .NET Compiler Platform SDK: https://learn.microsoft.com/zh-cn/dotnet/csharp/roslyn-sdk/
学习 C# 语法树,语义模型,代码自动生成,代码注入到执行上下文。
Roslyn 示例:https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md/
学习如何打包代码生成项目。
要了解代理模式
为什么要学代理模式?代理模式是 AOP 实现中常用的一种技术手段。
- 静态代理
// 1. 先定义一个接口和日志类型
public class IMyService{
void Show();
}
public class Logger{
static void Info(string info){
Console.WriteLine(info)
}
}
// 2. 然后定义一个基础服务 MyService
public class MyService : IMyService{
public void Show(){
Console.WriteLine("Hello!");
}
}
// 2. 然后使用 MyServiceProxy 代理 IMyService 的实例
public class MyServiceProxy : IMyService{
private readonly IMyService _service;
public MyServiceProxy(IMyService service){
_service = service;
}
public void Show(){
try{
Logger.Info("我要开始执行 Show 方法了...")
_service.Show();
Logger.Info("Show 方法顺利完成了!")
}
catch(ex){
// 记录
Logger.info(ex.ToString())
}
}
}
IMyService proxy = MyServiceProxy(new MyService());
// 3. 此时 MyServiceProxy 代理了 MyService
proxy.Show();
// 这就是一个简单的静态代理。。。
- 动态代理
动态代理,就是在运行时,根据我们的业务参数,动态的选择我们所需的代理实例。
例如:
// 1. 创建接口
public interface IMyInterface{
}
// 2. 创建接口类型1
public interface MyInterface1 : IMyInterface{
}
// 3. 创建接口类型2
public interface MyInterface2 : IMyInterface{
}
// 4. 创建 MyInterface1 的代理
class MyInterface1Proxy : IMyInterface{
public MyInterface1Proxy(IMyInterface i){
}
}
// 5. 创建 MyInterface2 的代理
class MyInterface2Proxy : IMyInterface{
public MyInterface1Proxy(IMyInterface i){
}
}
// 6. 代理工厂,在运行时,根据 IMyInterface 的实例类型,动态创建代理实例
public class ProxyFactory{
public IMyInterface GetProxy(IMyInterface i){
if (i is MyInterface1){
return MyInterface1Proxy(i);
}
else
if (i is MyInterface2){
return MyInterface2Proxy(i)
}
throw new InvalidOpeartion();
}
}
// 7. 根据接口实例,动态创建代理类型
var factory = new ProxyFactory();
IMyInterface proxy1 = factory.GetProxy(new MyInterface1()); // 返回 MyInterface1Proxy 的实例
IMyInterface proxy2 = factory.GetProxy(new MyInterface2()); // 返回 MyInterface2Proxy 的实例
为什么要使用 AOP 呢?
面向切面编程(AOP,Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(如日志、事务、安全等)从业务逻辑中分离出来,并以“切面”的形式进行管理和维护,从而提高了代码的模块性、可重用性和可维护性。使用AOP的主要原因包括:
- 模块化:通过将关注点与业务逻辑分离,AOP使得代码更加模块化,便于重用和维护。
- 松耦合:AOP降低了不同模块之间的耦合性,使得系统更加灵活和可扩展。
- 代码重用:AOP提高了代码的重用性,减少了代码的冗余。
- 提高开发效率:AOP通过提供声明式的方式来配置切面,使得开发者可以更快地实现横切关注点。
- 增强系统可维护性:由于关注点分离和代码重用,AOP使得系统更加易于维护。
在.NET环境中,也有一些常用的AOP框架,如:
- AspectCore:AspectCore是一个轻量级的AOP框架,支持多种IOC容器,并且支持接口和类的拦截。它已经在多个项目中得到了应用,并且具有良好的性能和灵活性。
- PostSharp:PostSharp是一个功能强大的AOP框架,它支持在编译时注入切面,从而避免了运行时的性能开销。PostSharp提供了丰富的功能和选项,使得开发者可以轻松地实现各种横切关注点。
- Castle DynamicProxy:虽然Castle DynamicProxy本身不是一个专门的AOP框架,但它是一个强大的动态代理库,可以用来实现AOP的一些功能。通过结合Castle DynamicProxy和其他工具或框架,开发者可以实现自己的AOP解决方案。
基于 Roslyn 的 AOP 切面拦截器示例
先看一个简单的示例,新建一个控制台程序,且项目文件内容为
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Interceptor.RoslynProxy.Core" Version="1.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>
</Project>
新建 Program.cs 文件,拷贝以下代码:
using Microsoft.Extensions.DependencyInjection;
using RoslynProxy.Core;
using RoslynProxy.Core.RoslynProxyAttributes;
namespace RoslynCodeGenerator
{
// 必须定义一个具有 partial 修饰符且继承自 RoslynProxyServiceCollection 的一个类型
// 代码中不能定义多个继承自 RoslynProxyServiceCollection 的类型
public partial class MyRoslynProxyServiceCollection : RoslynProxyServiceCollection
{
}
public interface IMyService
{
void Show();
}
public class MyService : IMyService
{
void IMyService.Show()
{
Console.WriteLine("This is MyService.");
}
}
// 只需要实现 RoslynDispatchProxy 类型,就可以自动代理 IMyService 接口
// 不许要您手动注册服务 IMyService 服务,由 Roslyn 框架自动注册
// 默认注册为瞬时服务
//[ScopedRoslynProxy] 注册为作用域
[SingletonRoslynProxy] // 注册为单例
public class MyServiceProxy : RoslynDispatchProxy<MyServiceProxy, IMyService, MyService>
{
protected override void BeginInvoke()
{
Console.WriteLine("MyServiceProxy BeginInvoke");
}
protected override void EndInvoke()
{
Console.WriteLine("MyServiceProxy EndInvoke");
}
}
public partial class Program
{
static void Main(string[] args)
{
ServiceCollection services = new ServiceCollection();
// 添加代理组件
// 在这里会注册注册 MyServiceProxy 代理相关的服务
services.AddProxyServices<MyRoslynProxyServiceCollection>();
var provider = services.BuildServiceProvider();
var servuce1 = provider.GetRequiredService<IMyService>();
var servuce2 = provider.GetRequiredService<IMyService>();
Console.WriteLine(servuce1 == servuce2);
servuce2.Show();
}
}
}
运行程序,输出:
True # 我注册为单例,所以两个实例相同
MyServiceProxy BeginInvoke # 方法执行前
This is MyService. # 执行 Show 方法
MyServiceProxy EndInvoke # 方法执行后
yes,成功实现了拦截!!!!!!!!!!!!!!!!!!!
技术实现
MyRoslynProxyServiceCollection服务注册分布类实现,这个代码由编译器自动创建,并应用到应用程序中
using Microsoft.Extensions.DependencyInjection;
namespace RoslynProxy.Builder
{
public partial class MyRoslynProxyServiceCollection
{
public override void RegisterProxyServices(Microsoft.Extensions.DependencyInjection.IServiceCollection services)
{
{
// 编译器会自动获取所有继承自 RoslynDispatchProxy 类型的服务,并注册它
services.AddTransient<RoslynProxy.Builder.MyOtherService>();
// RoslynProxy.Builder.MyOtherServiceProxy.GetProxyService 是 RoslynDispatchProxy 上的静态方法
services.AddScoped(RoslynProxy.Builder.MyOtherServiceProxy.GetProxyService);
}
{
services.AddTransient<RoslynProxy.Builder.MyService>();
services.AddSingleton(RoslynProxy.Builder.MyProxyService.GetProxyService);
}
}
}
};
- 注册代理服务扩展
using Microsoft.Extensions.DependencyInjection;
namespace RoslynProxy.Core
{
public static class ServiceCollectionExtensions
{
// 由开发者,调用 AddProxyServices 方法,注册代理服务
public static IServiceCollection AddProxyServices<T>(this IServiceCollection services) where T : RoslynProxyServiceCollection, new()
{
var service = new T();
// 在这里注册所有代理服务
service.RegisterProxyServices(services);
return services;
}
}
}
RoslynDispatchProxy代理基类实现,通过它获取我们的服务的代理
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Reflection;
namespace RoslynProxy.Core
{
/// <summary>
/// Roslyn 代理基类
/// </summary>
/// <typeparam name="TProxyService">代理服务类型</typeparam>
/// <typeparam name="TService">代理接口</typeparam>
/// <typeparam name="TImplementation">代理接口的实例类型</typeparam>
public abstract class RoslynDispatchProxy<TProxyService, TService, TImplementation> : DispatchProxy
where TService : class
where TProxyService : RoslynDispatchProxy<TProxyService, TService, TImplementation>
where TImplementation : TService
{
protected TService TargetService { get; private set; } = null!;
protected IServiceProvider ServiceProvider { get; private set; } = null!;
/// <summary>
/// 获取代理服务
/// </summary>
public static TService GetProxyService(IServiceProvider serviceProvider)
{
// 创建代理服务
var proxyService = Create<TService, TProxyService>();
if (proxyService is TProxyService dispatchProxy)
{
dispatchProxy.ServiceProvider = serviceProvider;
dispatchProxy.TargetService = serviceProvider.GetRequiredService<TImplementation>();
}
return proxyService;
}
// 覆写 Invoke 方法来处理方法调用
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
if (targetMethod != null)
{
// 检查是否是属性的 getter 或 setter
if (targetMethod.IsSpecialName && (targetMethod.Name.StartsWith("get_") || targetMethod.Name.StartsWith("set_")))
{
// 调用目标对象上的 getter 或 setter
return targetMethod.Invoke(TargetService, args);
}
else
{
BeginInvoke();
// 调用目标对象上的方法
var result = targetMethod.Invoke(TargetService, args);
EndInvoke();
return result;
}
}
return null;
}
/// <summary>
/// 开始调用前
/// </summary>
protected abstract void BeginInvoke();
/// <summary>
/// 调用完成后
/// </summary>
protected abstract void EndInvoke();
}
}
- 使用 Roslyn 创建
MyRoslynProxyServiceCollection类型,并添执行上下文
using Microsoft.CodeAnalysis;
namespace RoslynProxy.SourceGenerator
{
[Generator]
internal class ProxyServiceSourceGenerator : ISourceGenerator
{
// RoslynProxyServiceTypeBuilder 类型是创建 MyRoslynProxyServiceCollection.g.cs 源代码
private readonly RoslynProxyServiceTypeBuilder _builder = new RoslynProxyServiceTypeBuilder();
public void Execute(GeneratorExecutionContext context)
{
// 这里会返回源代码信息,然后添加到执行上下文
if (_builder.TryBuildSource(context.Compilation, out var typeInfo))
{
context.AddSource(typeInfo.ClassName + ".g.cs", typeInfo.Source);
}
}
public void Initialize(GeneratorInitializationContext context)
{
}
}
}
RoslynProxyServiceTypeBuilder 类型,见源代码:https://gitee.com/lan-xiaofang/roslyn-proxy.git/
总结
问题:
- 存在服务注册被覆盖问题。
- 可能会影响获取服务的性能。
- 自动生成的代码还没有格式化,哪个大佬知道,指导一下。
- Roslyn(Microsoft.CodeAnalysis.CSharp 4.10.0) 不能同 CodeDom(8.0) 一起使用,否则会报错不能加载 CodeDom 的程序集。,我不知到为啥不兼容。
- 代码生成项目(RoslynProxy.SourceGenerator) 引用了代理服务项目 (RoslynProxy.Core),会导致引用代码生成项目的项目不能在编译时自动生成代码。
浙公网安备 33010602011771号