基于 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的主要原因包括:

  1. 模块化:通过将关注点与业务逻辑分离,AOP使得代码更加模块化,便于重用和维护。
  2. 松耦合:AOP降低了不同模块之间的耦合性,使得系统更加灵活和可扩展。
  3. 代码重用:AOP提高了代码的重用性,减少了代码的冗余。
  4. 提高开发效率:AOP通过提供声明式的方式来配置切面,使得开发者可以更快地实现横切关注点。
  5. 增强系统可维护性:由于关注点分离和代码重用,AOP使得系统更加易于维护。

在.NET环境中,也有一些常用的AOP框架,如:

  1. AspectCore:AspectCore是一个轻量级的AOP框架,支持多种IOC容器,并且支持接口和类的拦截。它已经在多个项目中得到了应用,并且具有良好的性能和灵活性。
  2. PostSharp:PostSharp是一个功能强大的AOP框架,它支持在编译时注入切面,从而避免了运行时的性能开销。PostSharp提供了丰富的功能和选项,使得开发者可以轻松地实现各种横切关注点。
  3. 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/

总结

问题:

  1. 存在服务注册被覆盖问题。
  2. 可能会影响获取服务的性能。
  3. 自动生成的代码还没有格式化,哪个大佬知道,指导一下。
  4. Roslyn(Microsoft.CodeAnalysis.CSharp 4.10.0) 不能同 CodeDom(8.0) 一起使用,否则会报错不能加载 CodeDom 的程序集。,我不知到为啥不兼容。
  5. 代码生成项目(RoslynProxy.SourceGenerator) 引用了代理服务项目 (RoslynProxy.Core),会导致引用代码生成项目的项目不能在编译时自动生成代码。
posted @ 2024-07-26 21:45  RafaelLxf  阅读(82)  评论(0)    收藏  举报