菜渣開源一個基於 EMIT 的 AOP 庫(.NET Core)

Nuget 庫地址:https://www.nuget.org/packages/CZGL.AOP/git

Github 庫地址:https://github.com/whuanle/CZGL.AOPgithub

CZGL.AOP 是 基於 EMIT 編寫的 一個簡單輕量的AOP框架,支持非侵入式代理,支持.NET Core/ASP.NET Core,以及支持多種依賴注入框架。web

1,快速入門

CZGL.AOP 使用比較簡單,你只須要使用 [Interceptor] 特性標記須要代理的類型,而後使用繼承 ActionAttribute 的特性標記要被代理的方法或屬性。api

1.1 繼承 ActionAttribute 特性

ActionAttribute 是用於代理方法或屬性的特性標記,不能直接使用,須要繼承後重寫方法。框架

示例以下:異步

public class LogAttribute : ActionAttribute
    {
        public override void Before(AspectContext context)
        {
            Console.WriteLine("執行前");
        }

        public override object After(AspectContext context)
        {
            Console.WriteLine("執行後");
            if (context.IsMethod)
                return context.MethodResult;
            else if (context.IsProperty)
                return context.PropertyValue;
            return null;
        }
    }

Before 會在被代理的方法執行前或被代理的屬性調用時生效,你能夠經過 AspectContext 上下文,獲取、修改傳遞的參數。ide

After 在方法執行後或屬性調用時生效,你能夠經過上下文獲取、修改返回值。函數

1.2 標記代理類型

在被代理的類型中,使用 [Interceptor] 特性來標記,在須要代理的方法中,使用 繼承了 ActionAttribute 的特性來標記。ui

此方法是侵入式的,須要在編譯前完成。代理

[Interceptor]
public class Test : ITest
{
    [Log] public virtual string A { get; set; }
    [Log]
    public virtual void MyMethod()
    {
        Console.WriteLine("運行中");
    }
}

注意的是,一個方法或屬性只能設置一個攔截器。

2,如何建立代理類型

CZGL.AOP 有多種生成代理類型的方式,下面介紹簡單的方式。

請預先建立以下代碼:

public class LogAttribute : ActionAttribute
    {
        public override void Before(AspectContext context)
        {
            Console.WriteLine("執行前");
        }

        public override object After(AspectContext context)
        {
            Console.WriteLine("執行後");
            if (context.IsMethod)
                return context.MethodResult;
            else if (context.IsProperty)
                return context.PropertyValue;
            return null;
        }
    }

    public interface ITest
    {
        void MyMethod();
    }

    [Interceptor]
    public class Test : ITest
    {
        [Log] public virtual string A { get; set; }
        public Test()
        {
            Console.WriteLine("構造函數沒問題");
        }
        [Log]
        public virtual void MyMethod()
        {
            Console.WriteLine("運行中");
        }
    }

2.1 經過API直接建立

經過 CZGL.AOP 中的 AopInterceptor 類,你能夠生成代理類型。

示例以下:

ITest test1 = AopInterceptor.CreateProxyOfInterface<ITest, Test>();
            Test test2 = AopInterceptor.CreateProxyOfClass<Test>();
            test1.MyMethod();
            test2.MyMethod();

CreateProxyOfInterface 經過接口建立代理類型;CreateProxyOfClass 經過類建立代理類型;

默認調用的是無參構造函數。

2,建立代理類型

經過API

你能夠參考源碼解決方案

中的 ExampleConsole 項目。

若是要直接使用 AopInterceptor.CreateProxyOfInterfaceAopInterceptor.CreateProxyOfClass 方法,經過接口或類來建立代理類型。

ITest test1 = AopInterceptor.CreateProxyOfInterface<ITest, Test>();
        Test test2 = AopInterceptor.CreateProxyOfClass<Test>();

若是要指定實例化的構造函數,能夠這樣:

// 指定構造函數
            test2 = AopInterceptor.CreateProxyOfClass<Test>("aaa", "bbb");
            test2.MyMethod();

經過 Microsoft.Extensions.DependencyInjection

Microsoft.Extensions.DependencyInjection 是 .NET Core/ASP.NET Core 默認的依賴注入容器。

若是須要支持 ASP.NET Core 中使用 AOP,你能夠在 Nuget 包中安裝 CZGL.AOP.MEDI

若是你在控制檯下使用 Microsoft.Extensions.DependencyInjection,你能夠使用名爲 BuildAopProxyIServiceCollection 拓展方法來爲容器中的類型,生成代理類型。

示例以下:

IServiceCollection _services = new ServiceCollection();
            _services.AddTransient<ITest, Test>();
            var serviceProvider = _services.BuildAopProxy().BuildServiceProvider();
            serviceProvider.GetService<ITest>();
            return serviceProvider;

你能夠參考源碼解決方案中的 ExampleMEDI 項目。

若是你要在 ASP.NET Core 中使用,你能夠在 Startup 中,ConfigureServices 方法的最後一行代碼使用 services.BuildAopProxy();

public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.BuildAopProxy();
        }

還能夠在 ProgramIHostBuilder 中使用 .UseServiceProviderFactory(new AOPServiceProxviderFactory()) 來配置使用 CZGL.AOP。

示例:

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
            .UseServiceProviderFactory(new AOPServiceProxviderFactory())
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });

能夠參考解決方案中的 ExampleConsoleExampleWebMEDI 兩個項目。

你沒必要擔憂引入 CZGL.AOP 後,使用依賴注入會使程序變慢或者破壞容器中的原有屬性。CZGL.AOP 只會在建立容器時處理須要被代理的類型,不會影響容器中的服務,也不會干擾到依賴注入的執行。

經過 Autofac

若是須要在 Autofac 中使用 AOP,則須要引用 CZGL.AOP.Autofac 包。

若是你在控制檯程序中使用 Autofac,則能夠在 Build() 後面使用 BuildAopProxy()

ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<Test>().As<ITest>();
            var container = builder.Build().BuildAopProxy();

            using (ILifetimeScope scope = container.BeginLifetimeScope())
            {
                // 獲取實例
                ITest myService = scope.Resolve<ITest>();
                myService.MyMethod();
            }

            Console.ReadKey();
        }

要注意的是,在已經完成的組件註冊建立一個新的容器後,才能調用 BuildAopProxy() 方法,

這樣針對一個新的容器你能夠考慮是否須要對容器中的組件進行代理。

若是在 ASP.NET Core 中使用 Autofac,你須要在 Program 類的 IHostBuilder 中使用:

.UseServiceProviderFactory(new AutofacServiceProviderFactory())

若是須要代理已經註冊的組件,則將其替換爲:

.UseServiceProviderFactory(new CZGL.AOP.Autofac.AOPServiceProxviderFactory())

請參考 源碼解決方案中的 ExampleAutofacExampleWebAutofac 兩個項目。

3,深刻使用

代理類型

要被代理的類型,須要使用 [Interceptor]來標記,例如:

[Interceptor]
    public class Test : ITest
    {
    }

支持泛型類型。

被代理的類型必須是可被繼承的。

類型的構造函數沒有限制,你能夠隨意編寫。

在使用 API 建立代理類型而且實例化時,你能夠指定使用哪一個構造函數。

例如:

string a="",b="",c="";
			ITest test1 = AopInterceptor.CreateProxyOfInterface<ITest, Test>(a,b,c);

API 會根據參數的多少以及參數的類型自動尋找合適的構造函數。

方法、屬性代理

爲了代理方法或屬性,你須要繼承 ActionAttribute 特性,而後爲方法或屬性標記此特性,而且將方法或屬性設置爲 virtual

一個類型中的不一樣方法,能夠使用不一樣的攔截器。

[Log1]
        public virtual void MyMethod1(){}
        
        [Log2]
        public virtual void MyMethod2(){}

對於屬性,能夠在屬性上直接使用特性,或者只在 get 或 set 構造器使用。

[Log] public virtual string A { get; set; }
        
        // 或
        public virtual string A { [Log] get; set; }
        
        // 或
        public virtual string A { get; [Log] set; }

若是在屬性上使用特性,至關於 [Log] get; [Log] set;

上下文

一個簡單的方法或屬性攔截標記是這樣的:

public class LogAttribute : ActionAttribute
    {
        public override void Before(AspectContext context)
        {
            Console.WriteLine("執行前");
        }

        public override object After(AspectContext context)
        {
            Console.WriteLine("執行後");
            if (context.IsMethod)
                return context.MethodResult;
            else if (context.IsProperty)
                return context.PropertyValue;
            return null;
        }
    }

AspectContext 的屬性說明以下:

字段 說明
Type 當前被代理類型生成的代理類型
ConstructorParamters 類型被實例化時使用的構造函數的參數,若是構造函數沒有參數,則 MethodValues.Length = 0,而不是 MethodValues 爲 null。
IsProperty 當前攔截的是屬性
PropertyInfo 當前被執行的屬性的信息,可爲 null。
PropertyValue 但調用的是屬性時,返回 get 的結果或 set 的 value 值。
IsMethod 當前攔截的是方法
MethodInfo 當前方法的信息
MethodValues 方法被調用時傳遞的參數,若是此方法沒有參數,則 MethodValues.Length = 0,而不是 MethodValues 爲 null
MethodResult 方法執行返回的結果(若是有)

攔截方法或屬性的參數

經過上下文,你能夠修改方法或屬性的參數以及攔截返回結果:

public class LogAttribute : ActionAttribute
    {
        public override void Before(AspectContext context)
        {
            // 攔截並修改方法的參數
            for (int i = 0; i < context.MethodValues.Length; i++)
            {
                context.MethodValues[i] = (int)context.MethodValues[i] + 1;
            }
            Console.WriteLine("執行前");
        }

        public override object After(AspectContext context)
        {
            Console.WriteLine("執行後");

            // 攔截方法的執行結果
            context.MethodResult = (int)context.MethodResult + 664;

            if (context.IsMethod)
                return context.MethodResult;
            else if (context.IsProperty)
                return context.PropertyValue;
            return null;
        }
    }

    [Interceptor]
    public class Test
    {
        [Log]
        public virtual int Sum(int a, int b)
        {
            Console.WriteLine("運行中");
            return a + b;
        }
    }
Test test = AopInterceptor.CreateProxyOfClass<Test>();

            Console.WriteLine(test.Sum(1, 1));

方法的參數支持 inrefout;支持泛型方法泛型屬性;支持異步方法;

非侵入式代理

此種方式不須要改動被代理的類型,你也能夠代理程序集中的類型。

public class LogAttribute : ActionAttribute
    {
        public override void Before(AspectContext context)
        {
            Console.WriteLine("執行前");
        }

        public override object After(AspectContext context)
        {
            Console.WriteLine("執行後");
            if (context.IsMethod)
                return context.MethodResult;
            else if (context.IsProperty)
                return context.PropertyValue;
            return null;
        }
    }
public class TestNo
    {
        public virtual string A { get; set; }
        public virtual void MyMethod()
        {
            Console.WriteLine("運行中");
        }
    }
TestNo test3 = AopInterceptor.CreateProxyOfType<TestNo>(new ProxyTypeBuilder()
                .AddProxyMethod(typeof(LogAttribute), typeof(TestNo).GetMethod(nameof(TestNo.MyMethod)))
                .AddProxyMethod(typeof(LogAttribute), typeof(TestNo).GetProperty(nameof(TestNo.A)).GetSetMethod()));

經過 ProxyTypeBuilder 來構建代理類型。

代理方法或屬性都是使用 AddProxyMethod,第一個參數是要使用的攔截器,第二個參數是要攔截的方法。

若是要攔截屬性,請分開設置屬性的 getset 構造。

若是多個方法或屬性使用同一個攔截器,則能夠這樣:

TestNo test3 = AopInterceptor.CreateProxyOfType<TestNo>(
                new ProxyTypeBuilder(new Type[] { typeof(LogAttribute) })
                .AddProxyMethod("LogAttribute", typeof(TestNo).GetMethod(nameof(TestNo.MyMethod)))
                .AddProxyMethod("LogAttribute", typeof(TestNo).GetProperty(nameof(TestNo.A)).GetSetMethod()));
TestNo test3 = AopInterceptor.CreateProxyOfType<TestNo>(
                new ProxyTypeBuilder(new Type[] { typeof(LogAttribute) })
                .AddProxyMethod("LogAttribute", typeof(TestNo).GetMethod(nameof(TestNo.MyMethod)))
                .AddProxyMethod(typeof(LogAttribute2), typeof(TestNo).GetProperty(nameof(TestNo.A)).GetSetMethod()));

在構造函數中傳遞過去所須要的攔截器,而後在攔截時使用。

相關文章
相關標籤/搜索