國內開源社區巨做AspectCore-Framework入門

前些天和張隊(善友),lemon(浩洋),斌哥(項斌)等MVP大咖一起吃飯,你們聊到了lemon名下的AOP這個項目,我這小白聽得一臉懵逼,後面回來作了一下功課,查了下資料,在lemon的Github上把這個項目學習了一下,收穫頗豐,讓我這個沒有接觸過AOP的Coder歎爲觀止,陷入了對lemon的深深崇拜,在這裏把學習的新的體會分享給你們.git

什麼是AOP?

在軟件業,AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,經過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。github

有點深奧, 舉個栗子imageweb

若是說以前作的一個系統專門給內部的服務提供接口的,由於是在內網中訪問,因此就沒有加上認證服務,如今這個系統要公開出來,一樣的那套接口要給外部系統服務了,那麼此時,就要進行認證,認證經過才能獲取接口的數據.編程

傳統的作法是,修改每個接口.這樣就會形成代碼改動過大,很恐怖.api

 

這個時候AOP就能夠登場了,咱們能夠在這一類服務的前面,加上一個一系列上一刀,在切出來的裂縫裏面插入認證方法.框架

image

 

然而,怎麼插入這個切面是關鍵.AOP 實現會採用一些常見方法:async

  • 使用預處理器(如 C++ 中的預處理器)添加源代碼。
  • 使用後處理器在編譯後的二進制代碼上添加指令。
  • 使用特殊編譯器在編譯時添加代碼。
  • 在運行時使用代碼攔截器攔截執行並添加所需的代碼。

可是常見仍是後處理和代碼攔截兩種方式ide

  • 後處理,或者叫 靜態織入post

    指使用 AOP 框架提供的命令進行編譯,從而在編譯階段就可生成 AOP 代理類,所以也稱爲編譯時加強或靜態織入。學習

    在dotnet 中通常在編譯時經過在MSBiuld執行自定義的Build Task來攔截編譯過程,在生成的程序集裏插入本身的IL。

    dotnet 框架表明: PostSharp

     

  • 代碼攔截,或者叫 動態代理

    在運行時在內存中「臨時」生成 AOP 動態代理類,所以也被稱爲運行時加強或動態代理。

    在dotnet 中通常在運行時經過Emit技術生成動態程序集和動態代理類型從而對目標方法進行攔截。

    dotnet 框架表明: Castle DynamicProxyAspectCore

引用https://github.com/dotnetcore/AspectCore-Framework/blob/master/docs/0.AOP%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8D.md

AspectCore-Framework的代碼攔截

我這裏直接應用AOP Demo中的一段代碼來講說這個攔截.

public class CustomInterceptor : AbstractInterceptor
    {
        public async override Task Invoke(AspectContext context, AspectDelegate next)
        {
            try
            {
                Console.WriteLine("Before service call");
                await next(context);
            }
            catch (Exception)
            {
                Console.WriteLine("Service threw an exception!");
                throw;
            }
            finally
            {
                Console.WriteLine("After service call");
            }
        }
    }

 

代碼中,其實執行到 await next(context)的時候,纔會真正去調用那個被攔截的方法.

這樣,咱們就能夠靈活地在代碼調用前,調用後作咱們想作的事情了.甚至能夠把代碼包在一個try…catch...中來捕獲異常.

開始AspcetCore的表演

新建一個web應用程序後,從 Nuget 安裝 AspectCore.Extensions.DependencyInjection 包.

PM>   Install-Package AspectCore.Extensions.DependencyInjection

而後.咱們就能夠來定義咱們的攔截器了,我定義了一個這樣的攔截器.

public override async Task Invoke(AspectContext context, AspectDelegate next)
        {
            var logger = context.ServiceProvider.GetService<ILogger<AuthenticateInterceptor>>();
            try
            {
                var apiRequest = (ApiRequest) context.Parameters.FirstOrDefault();
                if (apiRequest == null || apiRequest.Name != "admin")
                {
                    logger.LogWarning("unauthorized");
                    return;
                }
                logger.LogInformation(apiRequest.Message);
                await next(context);
            }
            catch (Exception e)
            {
                logger?.LogWarning("Service threw an exception!");
                throw;
            }

            finally
            {
                logger.LogInformation("Finished service call");
            }
        }

當ApiRequest爲空或者Name不等於admin的時候之家返回並記錄未受權.

不然,調用該調用的方法並記錄ApiRequest中的Message.

而後,我定義一個UserService.

using System;

namespace AspceptCoreDemo
{
    public interface IUserService
    {
        String GetUserName(ApiRequest req);
    }

    public class UserService : IUserService
    {
        public string GetUserName(ApiRequest req)
        {
            if (req == null)
                return null;

            Console.WriteLine($"The User Name is {req.Name}");
            return req.Name;
        }
    }
}

在Controler中調用注入UserServce並調用該Service.

using Microsoft.AspNetCore.Mvc;

namespace AspceptCoreDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private readonly IUserService _userService;

        public ValuesController(IUserService userService)
        {
            _userService = userService;
        }

        [HttpPost]
        public ActionResult<string> Post(ApiRequest req)
        {
            return _userService.GetUserName(req);
        }
    }
}

註冊IUserservice並在ConfigureServices中配置建立代理類型的容器:

public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IUserService, UserService>();
            services.AddMvc();
            services.AddDynamicProxy(config =>
            {
                config.Interceptors.AddTyped<AuthenticateInterceptor>();
            });

            return services.BuildAspectInjectorProvider();
        }

須要注意的是紅色背景處,默認的ConfigureService返回類型是空的,咱們要修改爲爲返回類型是IServiceProvider.

1.全局攔截

咱們在上面的ConfigureService配置的AuthenticateInterceptor默認狀況下是全局的,即這裏的IUserService它會攔截,固然若是新增了一個IRoleServce它也是會攔截的.

我把程序運行起來用PostMan訪問Api進行測試.下圖是Post的數據和返回結果.

image

說明接口是正常工做的,成功地把傳過去的Name原樣返回.

那麼攔截器有沒有生效呢?我看看CMD的輸出.

image

若是咱們修改一下Name不等於Admin,預期應該是返回空,而且日誌打印出未受權,是否是這樣呢?

image

image

完美,與預期徹底相同.

能夠發現,這正是咱們在攔截器中所做的工做,說明攔截器對該UserService生效了.

2.做用於特定的Service或者Method的全局攔截器

若是咱們不想對全部Servce或是Method都攔截,只攔截指定的Servce或者Method呢?

其實,咱們是能夠配置全局攔截器的做用域的.

services.AddDynamicProxy(config =>
            {
                //支持通配符,只對IRole開頭的Servce有效
                config.Interceptors.AddTyped<AuthenticateInterceptor>(Predicates.ForService("IRole*"));
            });

我用以上方法配置爲該過濾器只對IRole開頭的Servce有效,那麼,當咱們讓問IUserServce時,該攔截器確定是不會生效的,事實是否是這樣呢?

image

即便Name不是admin,結果也返回了,說明確實是沒有生效的.

還能夠用如下方法指定過濾器做用於的Method.

 
//支持通配符,只對Name結尾的方法有效
config.Interceptors.AddTyped<AuthenticateInterceptor>(Predicates.ForMethod("*Name"));

 

3.全局過濾器忽略配置

忽略配置有兩種方法

一種是爲Service或者Method打上[NonAspect] 標籤,那個過濾器就不會對該處生效了.

public interface IUserService
    {
        [NonAspect]
        String GetUserName(ApiRequest req);
    }

此時,即便Name不等於Admin,也是有結果返回會的.

image

說明此時配置器對GetUserName方法確實沒有生效.

 

另一種是 全局忽略配置,亦支持通配符:

services.AddDynamicProxy(config =>
{
    //App1命名空間下的Service不會被代理
    config.NonAspectPredicates.AddNamespace("App1");

    //最後一級爲App1的命名空間下的Service不會被代理
    config.NonAspectPredicates.AddNamespace("*.App1");

    //ICustomService接口不會被代理
    config.NonAspectPredicates.AddService("ICustomService");

    //後綴爲Service的接口和類不會被代理
    config.NonAspectPredicates.AddService("*Service");

    //命名爲Query的方法不會被代理
    config.NonAspectPredicates.AddMethod("Query");

    //後綴爲Query的方法不會被代理
    config.NonAspectPredicates.AddMethod("*Query");
});

 

4.攔截器中的依賴注入

對攔截器中有get和set權限的屬性標記[AspectCore.Injector.FromContainerAttribute]特性,便可自動注入該屬性.

[NonAspect]
    public class AuthenticateInterceptor : AbstractInterceptor
    {
        [FromContainer] 
public ILogger<AuthenticateInterceptor> Logger { get; set
; }

        public override async Task Invoke(AspectContext context, AspectDelegate next)
        {
            try
            {
                var apiRequest = (ApiRequest) context.Parameters.FirstOrDefault();
                if (apiRequest == null || apiRequest.Name != "admin")
                {
                    Logger.LogWarning("unauthorized");
                    return;
                }
                Logger.LogInformation(apiRequest.Message);
                await next(context);
            }
            catch (Exception e)
            {
                Logger?.LogWarning("Service threw an exception!");
                throw;
            }

            finally
            {
                Logger.LogInformation("Finished service call");
            }
        }
    }

也能夠攔截器上下文AspectContext能夠獲取當前Scoped的ServiceProvider

利用該ServiceProvider來對依賴項賦值.

[NonAspect]
    public class AuthenticateInterceptor : AbstractInterceptor
    {

        public override async Task Invoke(AspectContext context, AspectDelegate next)
        {
            
var logger = context.ServiceProvider.GetService<ILogger<AuthenticateInterceptor>>
();
            try
            {
                var apiRequest = (ApiRequest) context.Parameters.FirstOrDefault();
                if (apiRequest == null || apiRequest.Name != "admin")
                {
                    logger.LogWarning("unauthorized");
                    return;
                }
                logger.LogInformation(apiRequest.Message);
                await next(context);
            }
            catch (Exception e)
            {
                logger?.LogWarning("Service threw an exception!");
                throw;
            }

            finally
            {
                logger.LogInformation("Finished service call");
            }
        }
    }

 

本文只是對AsceptCore最簡單的一套流程end to end 地進行了敘述,這還只是AsceptCore的冰山一角.在此向開發處如此牛逼AOP框架的小夥致敬!!

 

“解é」å§¿åŠ¿â€çš„图片搜索ç»「æžœ

 

歡迎訪問

https://github.com/dotnetcore/AspectCore-Framework/blob/master/docs/1.%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97.md

解鎖更多新姿式!!!

 

本博客Demo地址

https://github.com/liuzhenyulive/AspceptCoreDemo

AsceptCore地址

https://github.com/dotnetcore/AspectCore-Framework

相關文章
相關標籤/搜索