<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Util應用框架基礎(三) - 面向切面編程(AspectCore AOP)

      本節介紹Util應用框架對AspectCore AOP的使用.

      概述

      有些問題需要在系統中全局處理,比如記錄異常錯誤日志.

      如果在每個出現問題的地方進行處理,不僅費力,還可能產生大量冗余代碼,并打斷業務邏輯的編寫.

      這類跨多個業務模塊的非功能需求,被稱為橫切關注點.

      我們需要把橫切關注點集中管理起來.

      Asp.Net Core 提供的過濾器可以處理這類需求.

      過濾器有異常過濾器和操作過濾器等類型.

      異常過濾器可以全局處理異常.

      操作過濾器可以攔截控制器操作,在操作前和操作后執行特定代碼.

      過濾器很易用,但它必須配合控制器使用,所以只能解決部分問題.

      你不能將過濾器特性打在應用服務的方法上,那不會產生作用.

      我們需要引入一種類似 Asp.Net Core 過濾器的機制,在控制器范圍外處理橫切關注點.

      AOP框架

      AOP 是 Aspect Oriented Programming 的縮寫,即面向切面編程.

      AOP 框架提供了類似 Asp.Net Core 過濾器的功能,能夠攔截方法,在方法執行前后插入自定義代碼.

      .Net AOP框架有動態代理靜態織入兩種實現方式.

      動態代理 AOP 框架

      動態代理 AOP 框架在運行時動態創建代理類,從而為方法提供自定義代碼插入點.

      動態代理 AOP 框架有一些限制.

      • 要攔截的方法必須在接口中定義,或是虛方法.

      • 代理類過多,特別是啟用了參數攔截,會導致啟動性能下降.

      .Net 動態代理 AOP 框架有CastleAspectCore 等.

      Util應用框架使用 AspectCore ,選擇 AspectCore 是因為它更加易用.

      Util 對 AspectCore 僅簡單包裝.

      靜態織入 AOP 框架

      靜態織入 AOP 框架在編譯時修改.Net IL中間代碼.

      與動態代理AOP相比,靜態織入AOP框架有一些優勢.

      • 不必是虛方法.

      • 支持靜態方法.

      • 更高的啟動性能.

      但是成熟的 .Net 靜態織入 AOP 框架大多是收費的.

      Rougamo.Fody 是一個免費的靜態織入 AOP 框架,可以關注.

      基礎用法

      引用Nuget包

      Nuget包名: Util.Aop.AspectCore

      啟用Aop

      需要明確調用 AddAop 擴展方法啟用 AOP 服務.

      var builder = WebApplication.CreateBuilder( args );
      builder.AsBuild().AddAop();
      

      使用要點

      • 定義服務接口

        如果使用抽象基類,應將需要攔截的方法設置為虛方法.

      • 配置服務接口的依賴注入關系

        AspectCore AOP依賴Ioc對象容器,只有在對象容器中注冊的服務接口才能創建服務代理.

      • 將方法攔截器放在接口方法上.

        AspectCore AOP攔截器是一種.Net特性 Attribute,遵循 Attribute 使用約定.

        下面的例子將 CacheAttribute 方法攔截器添加到 ITestService 接口的 Test 方法上.

        注意: 應將攔截器放在接口方法上,而不是實現類上.

        按照約定, CacheAttribute 需要去掉 Attribute 后綴,并放到 [] 中.

        public interface ITestService : ISingletonDependency {        
            [Cache]
            List<string> Test( string value );
        }
        
      • 將參數攔截器放在接口方法參數上.

        AspectCore AOP 支持攔截特定參數.

        下面的例子在參數 value 上施加了 NotNullAttribute 參數攔截器.

          public interface ITestService : ISingletonDependency {
              void Test( [NotNull] string value );
          }
        

      Util內置攔截器

      Util應用框架使用 Asp.Net Core 過濾器處理全局異常,全局錯誤日志,授權等需求,僅定義少量 AOP 攔截器.

      Util應用框架定義了幾個參數攔截器,用于驗證.

      • NotNullAttribute

        • 驗證是否為 null,如果為 null 拋出 ArgumentNullException 異常.

        • 使用范例:

          public interface ITestService : ISingletonDependency {
              void Test( [NotNull] string value );
          }
        
      • NotEmptyAttribute

        • 使用 string.IsNullOrWhiteSpace 驗證是否為空字符串,如果為空則拋出 ArgumentNullException 異常.

        • 使用范例:

          public interface ITestService : ISingletonDependency {
              void Test( [NotEmpty] string value );
          }
        
      • ValidAttribute

        • 如果對象實現了 IValidation 驗證接口,則自動調用對象的 Validate 方法進行驗證.

          Util應用框架實體,值對象,DTO等基礎對象均已實現 IValidation 接口.

        • 使用范例:

          驗證單個對象.

          public interface ITestService : ISingletonDependency {
              void Test( [Valid] CustomerDto dto );
          }
        

        驗證對象集合.

          public interface ITestService : ISingletonDependency {
              void Test( [Valid] List<CustomerDto> dto );
          }
        

      Util應用框架為緩存定義了方法攔截器.

      • CacheAttribute

        • 使用范例:
          public interface ITestService : ISingletonDependency {
              [Cache]
              List<string> Test( string value );
          }
        

      禁止創建服務代理

      有些時候,你不希望為某些接口創建代理類.

      使用 Util.Aop.IgnoreAttribute 特性標記接口即可.

      下面演示了從 AspectCore AOP 排除工作單元接口.

      [Util.Aop.Ignore]
      public interface IUnitOfWork {
          Task<int> CommitAsync();
      }
      

      創建自定義攔截器

      除了內置的攔截器外,你可以根據需要創建自定義攔截器.

      創建方法攔截器

      繼承 Util.Aop.InterceptorBase 基類,重寫 Invoke 方法.

      下面以緩存攔截器為例講解創建方法攔截器的要點.

      • 緩存攔截器獲取 ICache 依賴服務并創建緩存鍵.

      • 通過緩存鍵和返回類型查找緩存是否存在.

      • 如果緩存已經存在,則設置返回值,不需要執行攔截的方法.

      • 如果緩存不存在,執行方法獲取返回值并設置緩存.

      Invoke 方法有兩個參數 AspectContextAspectDelegate.

      • AspectContext上下文提供了方法元數據信息和服務提供程序.

        • 使用 AspectContext 上下文獲取方法元數據.

          AspectContext 上下文提供了攔截方法相關的大量元數據信息.

          本例使用 context.ServiceMethod.ReturnType 獲取返回類型.

        • 使用 AspectContext 上下文獲取依賴的服務.

          AspectContext上下文提供了 ServiceProvider 服務提供器,可以使用它獲取依賴服務.

          本例需要獲取緩存操作接口 ICache ,使用 context.ServiceProvider.GetService<ICache>() 獲取依賴.

      • AspectDelegate表示攔截的方法.

        await next( context ); 執行攔截方法.

        如果需要在方法執行前插入自定義代碼,只需將代碼放在 await next( context ); 之前即可.

      /// <summary>
      /// 緩存攔截器
      /// </summary>
      public class CacheAttribute : InterceptorBase {
          /// <summary>
          /// 緩存鍵前綴
          /// </summary>
          public string CacheKeyPrefix { get; set; }
          /// <summary>
          /// 緩存過期間隔,單位:秒,默認值:36000
          /// </summary>
          public int Expiration { get; set; } = 36000;
      
          /// <summary>
          /// 執行
          /// </summary>
          public override async Task Invoke( AspectContext context, AspectDelegate next ) {
              var cache = GetCache( context );
              var returnType = GetReturnType( context );
              var key = CreateCacheKey( context );
              var value = await GetCacheValue( cache, returnType, key );
              if ( value != null ) {
                  SetReturnValue( context, returnType, value );
                  return;
              }
              await next( context );
              await SetCache( context, cache, key );
          }
      
          /// <summary>
          /// 獲取緩存服務
          /// </summary>
          protected virtual ICache GetCache( AspectContext context ) {
              return context.ServiceProvider.GetService<ICache>();
          }
      
          /// <summary>
          /// 獲取返回類型
          /// </summary>
          private Type GetReturnType( AspectContext context ) {
              return context.IsAsync() ? context.ServiceMethod.ReturnType.GetGenericArguments().First() : context.ServiceMethod.ReturnType;
          }
      
          /// <summary>
          /// 創建緩存鍵
          /// </summary>
          private string CreateCacheKey( AspectContext context ) {
              var keyGenerator = context.ServiceProvider.GetService<ICacheKeyGenerator>();
              return keyGenerator.CreateCacheKey( context.ServiceMethod, context.Parameters, CacheKeyPrefix );
          }
      
          /// <summary>
          /// 獲取緩存值
          /// </summary>
          private async Task<object> GetCacheValue( ICache cache, Type returnType, string key ) {
              return await cache.GetAsync( key, returnType );
          }
      
          /// <summary>
          /// 設置返回值
          /// </summary>
          private void SetReturnValue( AspectContext context, Type returnType, object value ) {
              if ( context.IsAsync() ) {
                  context.ReturnValue = typeof( Task ).GetMethods()
                      .First( p => p.Name == "FromResult" && p.ContainsGenericParameters )
                      .MakeGenericMethod( returnType ).Invoke( null, new[] { value } );
                  return;
              }
              context.ReturnValue = value;
          }
      
          /// <summary>
          /// 設置緩存
          /// </summary>
          private async Task SetCache( AspectContext context, ICache cache, string key ) {
              var options = new CacheOptions { Expiration = TimeSpan.FromSeconds( Expiration ) };
              var returnValue = context.IsAsync() ? await context.UnwrapAsyncReturnValue() : context.ReturnValue;
              await cache.SetAsync( key, returnValue, options );
          }
      }
      

      創建參數攔截器

      繼承 Util.Aop.ParameterInterceptorBase 基類,重寫 Invoke 方法.

      與方法攔截器類似, Invoke 也提供了兩個參數 ParameterAspectContext 和 ParameterAspectDelegate.

      ParameterAspectContext 上下文提供方法元數據.

      ParameterAspectDelegate 表示攔截的方法.

      下面演示了 [NotNull] 參數攔截器.

      在方法執行前判斷參數是否為 null,如果為 null 拋出異常,不會執行攔截方法.

      /// <summary>
      /// 驗證參數不能為null
      /// </summary>
      public class NotNullAttribute : ParameterInterceptorBase {
          /// <summary>
          /// 執行
          /// </summary>
          public override Task Invoke( ParameterAspectContext context, ParameterAspectDelegate next ) {
              if( context.Parameter.Value == null )
                  throw new ArgumentNullException( context.Parameter.Name );
              return next( context );
          }
      }
      

      性能優化

      AddAop 配置方法默認不帶參數,所有添加到 Ioc 容器的服務都會創建代理類,并啟用參數攔截器.

      AspectCore AOP 參數攔截器對啟動性能有很大的影響.

      默認配置適合規模較小的項目.

      當你在Ioc容器注冊了上千個甚至更多的服務時,啟動時間將顯著增長,因為啟動時需要創建大量的代理類.

      有幾個方法可以優化 AspectCore AOP 啟動性能.

      • 拆分項目

        對于微服務架構,單個項目包含的接口應該不會特別多.

        如果發現由于創建代理類導致啟動時間過長,可以拆分項目.

        但對于單體架構,不能通過拆分項目的方式解決.

      • 減少創建的代理類.

        Util定義了一個AOP標記接口 IAopProxy ,只有繼承了 IAopProxy 的接口才會創建代理類.

        要啟用 IAopProxy 標記接口,只需向 AddAop 傳遞 true .

          var builder = WebApplication.CreateBuilder( args );
          builder.AsBuild().AddAop( true );
        

        現在只有明確繼承自 IAopProxy 的接口才會創建代理類,代理類的數量將大幅減少.

        應用服務和領域服務接口默認繼承了 IAopProxy.

        如果你在其它構造塊使用了攔截器,比如倉儲,需要讓你的倉儲接口繼承 IAopProxy.

      • 禁用參數攔截器.

        如果啟用了 IAopProxy 標記接口,啟動性能依然未達到你的要求,可以禁用參數攔截器.

        AddAop 擴展方法支持傳入 Action<IAspectConfiguration> 參數,可以覆蓋默認設置.

        下面的例子禁用了參數攔截器,并為所有繼承了 IAopProxy 的接口創建代理.

          var builder = WebApplication.CreateBuilder( args );
          builder.AsBuild().AddAop( options => options.NonAspectPredicates.Add( t => !IsProxy( t.DeclaringType ) ) );
        
          /// <summary>
          /// 是否創建代理
          /// </summary>
          private static bool IsProxy( Type type ) {
              if ( type == null )
                  return false;
              var interfaces = type.GetInterfaces();
              if ( interfaces == null || interfaces.Length == 0 )
                  return false;
              foreach ( var item in interfaces ) {
                  if ( item == typeof( IAopProxy ) )
                      return true;
              }
              return false;
          }
        

      源碼解析

      AppBuilderExtensions

      擴展了 AddAop 配置方法.

      isEnableIAopProxy 參數用于啟用 IAopProxy 標記接口.

      Action<IAspectConfiguration> 參數用于覆蓋默認配置.

      /// <summary>
      /// Aop配置擴展
      /// </summary>
      public static class AppBuilderExtensions {
          /// <summary>
          /// 啟用AspectCore攔截器
          /// </summary>
          /// <param name="builder">應用生成器</param>
          public static IAppBuilder AddAop( this IAppBuilder builder ) {
              return builder.AddAop( false );
          }
      
          /// <summary>
          /// 啟用AspectCore攔截器
          /// </summary>
          /// <param name="builder">應用生成器</param>
          /// <param name="isEnableIAopProxy">是否啟用IAopProxy接口標記</param>
          public static IAppBuilder AddAop( this IAppBuilder builder,bool isEnableIAopProxy ) {
              return builder.AddAop( null, isEnableIAopProxy );
          }
      
          /// <summary>
          /// 啟用AspectCore攔截器
          /// </summary>
          /// <param name="builder">應用生成器</param>
          /// <param name="setupAction">AspectCore攔截器配置操作</param>
          public static IAppBuilder AddAop( this IAppBuilder builder, Action<IAspectConfiguration> setupAction ) {
              return builder.AddAop( setupAction, false );
          }
      
          /// <summary>
          /// 啟用AspectCore攔截器
          /// </summary>
          /// <param name="builder">應用生成器</param>
          /// <param name="setupAction">AspectCore攔截器配置操作</param>
          /// <param name="isEnableIAopProxy">是否啟用IAopProxy接口標記</param>
          private static IAppBuilder AddAop( this IAppBuilder builder, Action<IAspectConfiguration> setupAction, bool isEnableIAopProxy ) {
              builder.CheckNull( nameof( builder ) );
              builder.Host.UseServiceProviderFactory( new DynamicProxyServiceProviderFactory() );
              builder.Host.ConfigureServices( ( context, services ) => {
                  ConfigureDynamicProxy( services, setupAction, isEnableIAopProxy );
                  RegisterAspectScoped( services );
              } );
              return builder;
          }
      
          /// <summary>
          /// 配置攔截器
          /// </summary>
          private static void ConfigureDynamicProxy( IServiceCollection services, Action<IAspectConfiguration> setupAction, bool isEnableIAopProxy ) {
              services.ConfigureDynamicProxy( config => {
                  if ( setupAction == null ) {
                      config.NonAspectPredicates.Add( t => !IsProxy( t.DeclaringType, isEnableIAopProxy ) );
                      config.EnableParameterAspect();
                      return;
                  }
                  setupAction.Invoke( config );
              } );
          }
      
          /// <summary>
          /// 是否創建代理
          /// </summary>
          private static bool IsProxy( Type type, bool isEnableIAopProxy ) {
              if ( type == null )
                  return false;
              if ( isEnableIAopProxy == false ) {
                  if ( type.SafeString().Contains( "Xunit.DependencyInjection.ITestOutputHelperAccessor" ) )
                      return false;
                  return true;
              }
              var interfaces = type.GetInterfaces();
              if ( interfaces == null || interfaces.Length == 0 )
                  return false;
              foreach ( var item in interfaces ) {
                  if ( item == typeof( IAopProxy ) )
                      return true;
              }
              return false;
          }
      
          /// <summary>
          /// 注冊攔截器服務
          /// </summary>
          private static void RegisterAspectScoped( IServiceCollection services ) {
              services.AddScoped<IAspectScheduler, ScopeAspectScheduler>();
              services.AddScoped<IAspectBuilderFactory, ScopeAspectBuilderFactory>();
              services.AddScoped<IAspectContextFactory, ScopeAspectContextFactory>();
          }
      }
      

      Util.Aop.IAopProxy

      IAopProxy 是一個標記接口,繼承了它的接口才會創建代理類.

      /// <summary>
      /// Aop代理標記
      /// </summary>
      public interface IAopProxy {
      }
      

      Util.Aop.InterceptorBase

      InterceptorBase 是方法攔截器基類.

      它是一個簡單抽象層, 未來可能提供一些共享方法.

      /// <summary>
      /// 攔截器基類
      /// </summary>
      public abstract class InterceptorBase : AbstractInterceptorAttribute {
      }
      

      Util.Aop.ParameterInterceptorBase

      ParameterInterceptorBase 是參數攔截器基類.

      /// <summary>
      /// 參數攔截器基類
      /// </summary>
      public abstract class ParameterInterceptorBase : ParameterInterceptorAttribute {
      }
      

      Util.Aop.IgnoreAttribute

      [Util.Aop.Ignore] 用于禁止創建代理類.

      /// <summary>
      /// 忽略攔截
      /// </summary>
      public class IgnoreAttribute : NonAspectAttribute {
      }
      
      posted @ 2023-11-05 17:21  何鎮汐  閱讀(839)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 国产精品成人中文字幕 | 青草精品国产福利在线视频| 久久综合色一综合色88欧美| 日本九州不卡久久精品一区| 无码天堂亚洲国产AV| 99中文字幕精品国产| 国产自拍偷拍视频在线观看| 亚洲日韩性欧美中文字幕| 亚洲熟女精品一区二区| 亚洲欧美自偷自拍视频图片| 麻豆国产AV剧情偷闻女邻居内裤| 久久国产成人高清精品亚洲| 好先生在线观看免费播放| 国产精品亚洲二区在线看| 成在线人免费| 国产成人av一区二区三区不卡| 久久久国产一区二区三区四区小说| 中文字幕第一页国产精品| 亚洲色婷婷综合开心网| 亚洲欧美自偷自拍视频图片| 久热色精品在线观看视频| 中文字幕精品久久久久人妻红杏1| 亚洲国产成熟视频在线多多 | 国产精品成人免费视频网站京东| 国产精品自在自线免费观看| 日本高清视频在线www色| 国产激情一区二区三区午夜| 欧美变态另类zozo| 香蕉EEWW99国产精选免费 | 人妻在线无码一区二区三区| 国产一区二区不卡91| 日本熟妇浓毛| 人妻熟女一区无中文字幕| 探索| 亚洲欧美综合中文| 国产亚洲AV电影院之毛片| 亚洲丰满熟女一区二区蜜桃| 无套内谢少妇高清毛片| 亚洲精品无amm毛片| 伊人色综合久久天天| 亚洲成人av在线高清|