天天看點

從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】架構之十 || AOP面向切面程式設計淺解析:簡單日志記錄 + 服務切面緩存代碼已上傳Github+Gitee,文末有位址 零、今天完成的深紅色部分一、面向切面程式設計AOP實作日志記錄接口使用情況功能(服務層)二、需求2.面向切面程式設計AOP實作接口資料的緩存功能三、還有其他的一些問題需要考慮四、結語五、CODE

代碼已上傳Github+Gitee,文末有位址

  上回《

從壹開始前後端分離【 .NET Core2.0 Api + Vue 2.0 + AOP + 分布式】架構之九 || 依賴注入IoC學習 + AOP界面程式設計初探

》咱們說到了依賴注入Autofac的使用,不知道大家對IoC的使用用怎樣的感覺,我個人表示還是比較可行的,至少不用自己再關心一個個複雜的執行個體化服務對象了,直接通過接口就滿足需求,當然還有其他的一些功能,我還沒有說到,抛磚引玉嘛,大家如果有好的想法,歡迎留言,也可以來群裡,大家一起學習讨論。昨天在文末咱們說到了AOP面向切面程式設計的定義和思想,我個人簡單使用了下,感覺主要的思路還是通過攔截器來操作,就像是一個中間件一樣,今天呢,我給大家說兩個小栗子,當然,你也可以合并成一個,也可以自定義擴充,因為我們是真個系列是基于Autofac架構,是以今天主要說的是基于Autofac的Castle動态代理的方法,靜态注入的方式以後有時間可以再補充。

  時間真快,轉眼已經十天過去了,感謝大家的鼓勵,批評指正,希望我的文章,對您有一點點兒的幫助,哪怕是有學習新知識的動力也行,至少至少,可以為以後跳槽增加新的談資 [哭笑],這些天我們從面向對象OOP的開發,後又轉向了面向接口開發,到分層解耦,現在到了面向切面程式設計AOP,往下走将會是,分布式,微服務等等,技術真是永無止境啊!好啦,馬上開始動筆。

 零、今天完成的深紅色部分

從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】架構之十 || AOP面向切面程式設計淺解析:簡單日志記錄 + 服務切面緩存代碼已上傳Github+Gitee,文末有位址 零、今天完成的深紅色部分一、面向切面程式設計AOP實作日志記錄接口使用情況功能(服務層)二、需求2.面向切面程式設計AOP實作接口資料的緩存功能三、還有其他的一些問題需要考慮四、結語五、CODE

一、面向切面程式設計AOP實作日志記錄接口使用情況功能(服務層)

首先想一想,如果有一個需求(這個隻是我的一個想法,真實工作中可能用不上),要記錄整個項目的接口和調用情況,當然如果隻是控制器的話,還是挺簡單的,直接用一個過濾器或者一個中間件,還記得咱們開發Swagger攔截權限驗證的中間件麼,那個就很友善的把使用者調用接口的名稱記錄下來,當然也可以寫成一個切面,但是如果想看下與Service或者Repository層的調用情況呢,好像目前咱們隻能在Service層或者Repository層去寫日志記錄了,那樣的話,不僅工程大(當然你可以用工廠模式),而且耦合性瞬間就高了呀,想象一下,如果日志要去掉,關閉,修改,需要改多少地方!您說是不是,好不容易前邊的工作把層級的耦合性降低了。别慌,這個時候就用到了AOP和Autofac的Castle結合的完美解決方案了。

  經過這麼多天的開發,幾乎每天都需要引入Nuget包哈,我個人表示也不想再添加了,現在都已經挺大的了(47M當然包括全部dll檔案),今天不會辣!其實都是基于昨天的兩個Nuget包中已經自動生成的Castle元件。請看以下步驟:

1、在IBlogArticleServices.cs定義一個擷取部落格清單接口 ,并在BlogArticleServices實作該接口

   public interface IBlogArticleServices :IBaseServices<BlogArticle>
    {
        Task<List<BlogArticle>> getBlogs();
    }

   public class BlogArticleServices : BaseServices<BlogArticle>, IBlogArticleServices
    {
        IBlogArticleRepository dal;
        public BlogArticleServices(IBlogArticleRepository dal)
        {
            this.dal = dal;
            base.baseDal = dal;
        }
        /// <summary>
        /// 擷取部落格清單
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task<List<BlogArticle>> getBlogs()
        {
            var bloglist = await dal.Query(a => a.bID > 0, a => a.bID);

            return bloglist;

        }
    }      

2、在API層中添加對該接口引用(注意RESTful接口路徑命名規範,我這麼寫隻是為了測試)

    /// <summary>
        /// 擷取部落格清單
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [Route("GetBlogs")]
        public async Task<List<BlogArticle>> GetBlogs()
        {

            return await blogArticleServices.getBlogs();
        }      

 3、在Blog.Core建立檔案夾AOP,并添加攔截器BlogLogAOP,并設計其中用到的日志記錄Logger方法或者類

從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】架構之十 || AOP面向切面程式設計淺解析:簡單日志記錄 + 服務切面緩存代碼已上傳Github+Gitee,文末有位址 零、今天完成的深紅色部分一、面向切面程式設計AOP實作日志記錄接口使用情況功能(服務層)二、需求2.面向切面程式設計AOP實作接口資料的緩存功能三、還有其他的一些問題需要考慮四、結語五、CODE

關鍵的一些知識點,注釋中已經說明了,主要是有以下:

1、繼承接口IInterceptor

2、執行個體化接口IINterceptor的唯一方法Intercept

3、void Proceed();表示執行目前的方法和object ReturnValue { get; set; }執行後調用,object[] Arguments參數對象

4、中間的代碼是建立一個類,還是單寫,就很随意了。

  /// <summary>
    /// 攔截器BlogLogAOP 繼承IInterceptor接口
    /// </summary>
    public class BlogLogAOP : IInterceptor
    {

        /// <summary>
        /// 執行個體化IInterceptor唯一方法 
        /// </summary>
        /// <param name="invocation">包含被攔截方法的資訊</param>
        public void Intercept(IInvocation invocation)
        {
            //記錄被攔截方法資訊的日志資訊
            var dataIntercept = $"{DateTime.Now.ToString("yyyyMMddHHmmss")} " +
                $"目前執行方法:{ invocation.Method.Name} " +
                $"參數是: {string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray())} \r\n";

            //在被攔截的方法執行完畢後 繼續執行目前方法
            invocation.Proceed();

            dataIntercept += ($"被攔截方法執行完畢,傳回結果:{invocation.ReturnValue}");

            #region 輸出到目前項目日志
            var path = Directory.GetCurrentDirectory() + @"\Log";
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }

            string fileName = path + $@"\InterceptLog-{DateTime.Now.ToString("yyyyMMddHHmmss")}.log";

            StreamWriter sw = File.AppendText(fileName);
            sw.WriteLine(dataIntercept);
            sw.Close(); 
            #endregion

        }
    }      

4、添加到Autofac容器中,實作注入

還記得昨天的容器麼,先把攔截器注入,然後對程式集的注入方法中添加攔截器服務即可

      builder.RegisterType<BlogLogAOP>();//可以直接替換其他攔截器!一定要把攔截器進行注冊

            var assemblysServices = Assembly.Load("Blog.Core.Services");

            //builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();//指定已掃描程式集中的類型注冊為提供所有其實作的接口。

            builder.RegisterAssemblyTypes(assemblysServices)
                      .AsImplementedInterfaces()
                      .InstancePerLifetimeScope()
                      .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy;
                      .InterceptedBy(typeof(BlogLogAOP));//可以直接替換攔截器      

注意其中的倆個方法

.EnableInterfaceInterceptors()//對目标類型啟用接口攔截。攔截器将被确定,通過在類或接口上截取屬性, 或添加 InterceptedBy ()

.InterceptedBy(typeof(BlogLogAOP));//允許将攔截器服務的清單配置設定給注冊。

說人話就是,将攔截器加上要注入容器的的接口或者類上

5、運作項目,嗯,你就看到這根目錄下生成了一個Log檔案夾,裡邊有日志記錄,當然記錄很簡陋,裡邊是擷取到的實體類,大家可以自己根據需要擴充

從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】架構之十 || AOP面向切面程式設計淺解析:簡單日志記錄 + 服務切面緩存代碼已上傳Github+Gitee,文末有位址 零、今天完成的深紅色部分一、面向切面程式設計AOP實作日志記錄接口使用情況功能(服務層)二、需求2.面向切面程式設計AOP實作接口資料的緩存功能三、還有其他的一些問題需要考慮四、結語五、CODE

這裡,面向服務層的日志記錄就完成了,大家感覺是不是很平時的不一樣?

二、需求2.面向切面程式設計AOP實作接口資料的緩存功能

想一想,如果我們要實作緩存功能,一般咱們都是将資料擷取到以後,定義緩存,然後在其他地方使用的時候,在根據key去擷取目前資料,然後再操作等等,平時都是在API接口層擷取資料後進行緩存,今天咱們可以試試,在接口之前就緩存下來。

1、老規矩,定義一個緩存類和接口,你會問了,為什麼上邊的日志沒有定義,因為我會在之後講Redis的時候用到這個緩存接口

   /// <summary>
    /// 簡單的緩存接口,隻有查詢和添加,以後會進行擴充
    /// </summary>
    public interface ICaching
    {
        object Get(string cacheKey);

        void Set(string cacheKey, object cacheValue);
    }

   /// <summary>
    /// 執行個體化緩存接口ICaching
    /// </summary>
    public class MemoryCaching : ICaching
    {
        //引用Microsoft.Extensions.Caching.Memory;這個和.net 還是不一樣,沒有了Httpruntime了
        private IMemoryCache _cache;
        //還是通過構造函數的方法,擷取
        public MemoryCaching(IMemoryCache cache)
        {
            _cache = cache;
        }

        public object Get(string cacheKey)
        {
            return _cache.Get(cacheKey);
        }

        public void Set(string cacheKey, object cacheValue)
        {
            _cache.Set(cacheKey, cacheValue, TimeSpan.FromSeconds(7200));
        }
    }      

2、定義一個攔截器還是繼承IInterceptor,并實作Intercept

  /// <summary>
    /// 面向切面的緩存使用
    /// </summary>
    public class BlogCacheAOP : IInterceptor
    {
        //通過注入的方式,把緩存操作接口通過構造函數注入
        private ICaching _cache;
        public BlogCacheAOP(ICaching cache)
        {
            _cache = cache;
        }
        //Intercept方法是攔截的關鍵所在,也是IInterceptor接口中的唯一定義
        public void Intercept(IInvocation invocation)
        {
            //擷取自定義緩存鍵
            var cacheKey = CustomCacheKey(invocation);
            //根據key擷取相應的緩存值
            var cacheValue = _cache.Get(cacheKey);
            if (cacheValue != null)
            {
                //将目前擷取到的緩存值,指派給目前執行方法
                invocation.ReturnValue = cacheValue;
                return;
            }
            //去執行目前的方法
            invocation.Proceed();
            //存入緩存
            if (!string.IsNullOrWhiteSpace(cacheKey))
            {
                _cache.Set(cacheKey, invocation.ReturnValue);
            }
        }

        //自定義緩存鍵
        private string CustomCacheKey(IInvocation invocation)
        {
            var typeName = invocation.TargetType.Name;
            var methodName = invocation.Method.Name;
            var methodArguments = invocation.Arguments.Select(GetArgumentValue).Take(3).ToList();//擷取參數清單,最多三個

            string key = $"{typeName}:{methodName}:";
            foreach (var param in methodArguments)
            {
                key += $"{param}:";
            }

            return key.TrimEnd(':');
        }
        //object 轉 string
        private string GetArgumentValue(object arg)
        {
            if (arg is int || arg is long || arg is string)
                return arg.ToString();

            if (arg is DateTime)
                return ((DateTime)arg).ToString("yyyyMMddHHmmss");

            return "";
        }
    }      

注釋的很清楚,基本都是情況

3、ConfigureServices不用動,隻需要改下攔截器的名字就行

注意:

//将 TService 中指定的類型的範圍服務添加到實作

services.AddScoped<ICaching, MemoryCaching>();//記得把緩存注入!!!

從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】架構之十 || AOP面向切面程式設計淺解析:簡單日志記錄 + 服務切面緩存代碼已上傳Github+Gitee,文末有位址 零、今天完成的深紅色部分一、面向切面程式設計AOP實作日志記錄接口使用情況功能(服務層)二、需求2.面向切面程式設計AOP實作接口資料的緩存功能三、還有其他的一些問題需要考慮四、結語五、CODE

4、運作,你會發現,首次緩存是空的,然後将Repository倉儲中取出來的資料存入緩存,第二次使用就是有值了,其他所有的地方使用,都不用再寫了,而且也是面向整個程式集合的

從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】架構之十 || AOP面向切面程式設計淺解析:簡單日志記錄 + 服務切面緩存代碼已上傳Github+Gitee,文末有位址 零、今天完成的深紅色部分一、面向切面程式設計AOP實作日志記錄接口使用情況功能(服務層)二、需求2.面向切面程式設計AOP實作接口資料的緩存功能三、還有其他的一些問題需要考慮四、結語五、CODE
從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】架構之十 || AOP面向切面程式設計淺解析:簡單日志記錄 + 服務切面緩存代碼已上傳Github+Gitee,文末有位址 零、今天完成的深紅色部分一、面向切面程式設計AOP實作日志記錄接口使用情況功能(服務層)二、需求2.面向切面程式設計AOP實作接口資料的緩存功能三、還有其他的一些問題需要考慮四、結語五、CODE

三、還有其他的一些問題需要考慮

1、可以針對某一層的指定類的指定方法進行操作,這裡就不寫了,大家可以自己實驗

配合Attribute就可以隻攔截相應的方法了。因為攔截器裡面是根據Attribute進行相應判斷的!!

builder.RegisterAssemblyTypes(assembly)

   .Where(type => typeof(IQCaching).IsAssignableFrom(type) && !type.GetTypeInfo().IsAbstract) .AsImplementedInterfaces()

   .InstancePerLifetimeScope()

   .EnableInterfaceInterceptors()

   .InterceptedBy(typeof(QCachingInterceptor));

2、時間問題,阻塞,浪費資源問題等

  定義切面有時候是友善,初次使用會很别扭,使用多了,可能會對性能有些許的影響,因為會大量動态生成代理類,性能損耗,是特别高的請求并發,比如萬級每秒,還是不建議生産環節推薦。是以說切面程式設計要深入的研究,不可随意使用,我說的也是九牛一毛,大家繼續加油吧!

3、靜态注入

基于Net的IL語言層級進行注入,性能損耗可以忽略不計,Net使用最多的Aop架構PostSharp(好像收費了;)采用的即是這種方式。

大家可以參考這個博文:https://www.cnblogs.com/mushroom/p/3932698.html

四、結語

  今天的講解就到了這裡了,通過這兩個小栗子,大家應該能對面向切面程式設計有一些朦胧的感覺了吧,感興趣的可以深入的研究,也歡迎一起讨論,剛剛在緩存中,我說到了緩存接口,就引入了下次的講解内容,Redis的高性能緩存架構,記憶體存儲的資料結構伺服器,可用作資料庫,高速緩存和消息隊列代理。下次再見咯~

五、CODE

https://github.com/anjoy8/Blog.Core https://gitee.com/laozhangIsPhi/Blog.Core

繼續閱讀