net core天馬行空系列:原生DI+AOP實現spring boot註解式編程

  • 2019 年 10 月 3 日
  • 筆記

       寫過spring boot之後,那種無處不在的註解讓我非常喜歡,比如屬性注入@autowire,配置值注入@value,聲明式事物@Transactional等,都非常簡潔優雅,那麼我就在想,這些在net core里能實現么?經過一番摸索,終於實現並整理成此文。

       IOC方面,個人非常喜歡net core自帶的DI,因為他註冊服務簡潔優雅,3個生命周期通俗易懂,所以就沒使用autofac等其他容器,AOP方面,使用了業內鼎鼎大名的Castle.DynamicProxy(簡稱DP),所以要在nuget中添加Castle.Core的依賴包,這裡大家可能會有疑問,利用mvc的actionFilter不就可以實現了么,為什麼還要引用DP呢,因為呀,actionFilter只在controller層有效,普通類他就無能為力了,而DP無所不能。

1.定義註解和需要用到的類

屬性注入註解

  [AttributeUsage(AttributeTargets.Property)]      public class AutowiredAttribute : Attribute      {      }

配置值注入註解

   [AttributeUsage(AttributeTargets.Property)]      public class ValueAttribute : Attribute      {          public ValueAttribute(string value = "")          {              this.Value = value;          }            public string Value { get; }      }

聲明式事物註解

   [AttributeUsage(AttributeTargets.Method)]      public class TransactionalAttribute : Attribute      {      }

工作單元介面及實現類,用來實現事物操作,注入級別為scope,一次請求公用一個工作單元實例

    public interface IUnitOfWork : IDisposable      {          /// <summary>          /// 開啟事務          /// </summary>          void BeginTransaction();            /// <summary>          /// 提交          /// </summary>          void Commit();            /// <summary>          /// 事物回滾          /// </summary>          void RollBack();      }

   public class UnitOfWork : IUnitOfWork      {          public void BeginTransaction()          {              Console.WriteLine("開啟事務");          }            public void Commit()          {              Console.WriteLine("提交事務");          }            public void Dispose()          {              //throw new System.NotImplementedException();          }            public void RollBack()          {              Console.WriteLine("回滾事務");          }      }

攔截器

  /// <summary>      /// 事物攔截器      /// </summary>      public class TransactionalInterceptor : StandardInterceptor      {          private IUnitOfWork Uow { set; get; }            public TransactionalInterceptor(IUnitOfWork uow)          {              Uow = uow;          }            protected override void PreProceed(IInvocation invocation)          {              Console.WriteLine("{0}攔截前", invocation.Method.Name);                var method = invocation.TargetType.GetMethod(invocation.Method.Name);              if (method != null && method.GetCustomAttribute<TransactionalAttribute>() != null)              {                  Uow.BeginTransaction();              }          }            protected override void PerformProceed(IInvocation invocation)          {              invocation.Proceed();          }            protected override void PostProceed(IInvocation invocation)          {              Console.WriteLine("{0}攔截後, 返回值是{1}", invocation.Method.Name, invocation.ReturnValue);                var method = invocation.TargetType.GetMethod(invocation.Method.Name);              if (method != null && method.GetCustomAttribute<TransactionalAttribute>() != null)              {                  Uow.Commit();              }          }      }

用來測試注入效果的介面以及實現類,這裡我們定義一輛汽車,汽車擁有一個引擎(屬性注入),它能點火啟動,點火操作帶事物,這裡為了演示【介面-實現類】注入和【實現類】注入2種情況,引擎就沒添加介面,只有實現類,並且AOP攔截僅有【實現類】的類時,只能攔截虛方法,所以Start和Stop函數為虛函數。

   /// <summary>      /// 汽車引擎      /// </summary>      public class Engine      {          [Value("HelpNumber")]          public string HelpNumber { set; get; }            public virtual void Start()          {              Console.WriteLine("發動機啟動");              Stop();          }            public virtual void Stop()          {              Console.WriteLine("發動機熄火,撥打求救電話" + HelpNumber);          }      }

   public interface ICar      {          Engine Engine { set; get; }            void Fire();      }

    public class Car : ICar      {          [Autowired]          public Engine Engine { set; get; }            [Value("oilNo")]          public int OilNo { set; get; }            [Transactional]          public void Fire()          {              Console.WriteLine("加滿" + OilNo + "號汽油,點火");              Engine.Start();          }      }

控制器HomeController

 public class HomeController : Controller      {          [Autowired]          public ICar Car{ set; get; }            [Value("description")]          public string Description { set; get; }            public IActionResult Index()          {              var car = Car;                Console.WriteLine(Description);                Car.Fire();                return View();          }      }

 修改appsettings.json,添加一些測試鍵值對,(如果測試時發現輸出的中文亂碼,把appsettings.json保存為utf8格式即可),具體程式碼如下,

{    "Logging": {      "LogLevel": {        "Default": "Warning"      }    },    "AllowedHosts": "*",    "oilNo": 95,    "HelpNumber": "110",    "description": "我要開始飆車了"  }

2.效果圖

從上圖可以看到,正常注入,正常開啟攔截器和事務,正確讀取配置值。

從上圖可以看到,我們的控制器,ICar和Engine全部都是動態代理類,注入正常。

3.核心程式碼

第一部分,添加一個擴展類,名叫SummerBootExtentions.cs,程式碼如下

public static class SummerBootExtentions      {          /// <summary>          /// 瞬時          /// </summary>          /// <typeparam name="TService"></typeparam>          /// <typeparam name="TImplementation"></typeparam>          /// <param name="services"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbTransient<TService, TImplementation>(this IServiceCollection services, params Type[] interceptorTypes)          {              return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Transient, interceptorTypes);          }            /// <summary>          /// 請求級別          /// </summary>          /// <typeparam name="TService"></typeparam>          /// <typeparam name="TImplementation"></typeparam>          /// <param name="services"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbScoped<TService, TImplementation>(this IServiceCollection services, params Type[] interceptorTypes)          {              return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Scoped, interceptorTypes);          }            /// <summary>          /// 單例          /// </summary>          /// <typeparam name="TService"></typeparam>          /// <typeparam name="TImplementation"></typeparam>          /// <param name="services"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbSingleton<TService, TImplementation>(this IServiceCollection services, params Type[] interceptorTypes)          {              return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Singleton, interceptorTypes);          }            public static IServiceCollection AddSbService(this IServiceCollection services, Type serviceType, Type implementationType,              ServiceLifetime lifetime, params Type[] interceptorTypes)          {              services.Add(new ServiceDescriptor(implementationType, implementationType, lifetime));                object Factory(IServiceProvider provider)              {                  var target = provider.GetService(implementationType);                  var properties = implementationType.GetTypeInfo().DeclaredProperties;                    foreach (PropertyInfo info in properties)                  {                      //屬性注入                      if (info.GetCustomAttribute<AutowiredAttribute>() != null)                      {                          var propertyType = info.PropertyType;                          var impl = provider.GetService(propertyType);                          if (impl != null)                          {                              info.SetValue(target, impl);                          }                      }                        //配置值注入                      if (info.GetCustomAttribute<ValueAttribute>() is ValueAttribute valueAttribute)                      {                          var value = valueAttribute.Value;                          if (provider.GetService(typeof(IConfiguration)) is IConfiguration configService)                          {                              var pathValue = configService.GetSection(value).Value;                              if (pathValue != null)                              {                                  var pathV = Convert.ChangeType(pathValue, info.PropertyType);                                  info.SetValue(target, pathV);                              }                          }                        }                  }                    List<IInterceptor> interceptors = interceptorTypes.ToList()                      .ConvertAll<IInterceptor>(interceptorType => provider.GetService(interceptorType) as IInterceptor);                    var proxy = new ProxyGenerator().CreateInterfaceProxyWithTarget(serviceType, target, interceptors.ToArray());                    return proxy;              };                var serviceDescriptor = new ServiceDescriptor(serviceType, Factory, lifetime);              services.Add(serviceDescriptor);                return services;          }            /// <summary>          /// 瞬時          /// </summary>          /// <typeparam name="TService"></typeparam>          /// <param name="services"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbTransient<TService>(this IServiceCollection services, params Type[] interceptorTypes)          {              return services.AddSbService(typeof(TService), ServiceLifetime.Transient, interceptorTypes);          }            /// <summary>          /// 請求          /// </summary>          /// <typeparam name="TService"></typeparam>          /// <param name="services"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbScoped<TService>(this IServiceCollection services, params Type[] interceptorTypes)          {              return services.AddSbService(typeof(TService), ServiceLifetime.Scoped, interceptorTypes);          }            /// <summary>          /// 單例          /// </summary>          /// <typeparam name="TService"></typeparam>          /// <param name="services"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbSingleton<TService>(this IServiceCollection services, params Type[] interceptorTypes)          {              return services.AddSbService(typeof(TService), ServiceLifetime.Singleton, interceptorTypes);          }            public static IServiceCollection AddSbService(this IServiceCollection services, Type serviceType,              ServiceLifetime lifetime, params Type[] interceptorTypes)          {              if (services == null)                  throw new ArgumentNullException(nameof(services));              if (serviceType == (Type)null)                  throw new ArgumentNullException(nameof(serviceType));                object Factory(IServiceProvider provider)              {                  List<IInterceptor> interceptors = interceptorTypes.ToList()                      .ConvertAll<IInterceptor>(interceptorType => provider.GetService(interceptorType) as IInterceptor);                      var proxy = new ProxyGenerator().CreateClassProxy(serviceType, interceptors.ToArray());                    var properties = serviceType.GetTypeInfo().DeclaredProperties;                    foreach (PropertyInfo info in properties)                  {                      //屬性注入                      if (info.GetCustomAttribute<AutowiredAttribute>() != null)                      {                          var propertyType = info.PropertyType;                          var impl = provider.GetService(propertyType);                          if (impl != null)                          {                              info.SetValue(proxy, impl);                          }                      }                        //配置值注入                      if (info.GetCustomAttribute<ValueAttribute>() is ValueAttribute valueAttribute)                      {                          var value = valueAttribute.Value;                          if (provider.GetService(typeof(IConfiguration)) is IConfiguration configService)                          {                              var pathValue = configService.GetSection(value).Value;                              if (pathValue != null)                              {                                  var pathV = Convert.ChangeType(pathValue, info.PropertyType);                                  info.SetValue(proxy, pathV);                              }                          }                      }                  }                    return proxy;              };                var serviceDescriptor = new ServiceDescriptor(serviceType, Factory, lifetime);              services.Add(serviceDescriptor);                return services;          }            /// <summary>          /// 添加summer boot擴展          /// </summary>          /// <param name="builder"></param>          /// <returns></returns>          public static IMvcBuilder AddSB(this IMvcBuilder builder)          {              if (builder == null)                  throw new ArgumentNullException(nameof(builder));              ControllerFeature feature = new ControllerFeature();              builder.PartManager.PopulateFeature<ControllerFeature>(feature);              foreach (Type type in feature.Controllers.Select<TypeInfo, Type>((Func<TypeInfo, Type>)(c => c.AsType())))                  builder.Services.TryAddTransient(type, type);              builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, SbControllerActivator>());                return builder;          }      }

View Code

第二部分,添加一個自定義控制器激活類,用以替換掉mvc自帶的激活類,這個類命名為SbControllerActivator.cs,程式碼如下

    public class SbControllerActivator : IControllerActivator      {          /// <inheritdoc />          public object Create(ControllerContext actionContext)          {              if (actionContext == null)                  throw new ArgumentNullException(nameof(actionContext));                Type serviceType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType();                var target = actionContext.HttpContext.RequestServices.GetRequiredService(serviceType);                var properties = serviceType.GetTypeInfo().DeclaredProperties;              var proxy = new ProxyGenerator().CreateClassProxyWithTarget(serviceType, target);                foreach (PropertyInfo info in properties)              {                  //屬性注入                  if (info.GetCustomAttribute<AutowiredAttribute>() != null)                  {                      var propertyType = info.PropertyType;                      var impl = actionContext.HttpContext.RequestServices.GetService(propertyType);                      if (impl != null)                      {                          info.SetValue(proxy, impl);                      }                  }                    //配置值注入                  if (info.GetCustomAttribute<ValueAttribute>() is ValueAttribute valueAttribute)                  {                      var value = valueAttribute.Value;                      if (actionContext.HttpContext.RequestServices.GetService(typeof(IConfiguration)) is IConfiguration configService)                      {                          var pathValue = configService.GetSection(value).Value;                          if (pathValue != null)                          {                              var pathV = Convert.ChangeType(pathValue, info.PropertyType);                              info.SetValue(proxy, pathV);                          }                      }                    }              }                return proxy;          }            /// <inheritdoc />          public virtual void Release(ControllerContext context, object controller)          {          }      }

View Code

第三部分,在Startup.cs中,修改ConfigureServices方法如下

 public void ConfigureServices(IServiceCollection services)          {              services.Configure<CookiePolicyOptions>(options =>              {                  // This lambda determines whether user consent for non-essential cookies is needed for a given request.                  options.CheckConsentNeeded = context => true;                  options.MinimumSameSitePolicy = SameSiteMode.None;              });                  services.AddMvc()                  .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)                  .AddSB();                services.AddSbScoped<Engine>(typeof(TransactionalInterceptor));                services.AddScoped<IUnitOfWork,UnitOfWork>();              services.AddScoped(typeof(TransactionalInterceptor));                services.AddSbScoped<ICar, Car>(typeof(TransactionalInterceptor));            }

       從上面程式碼我們可以看到,在addMvc後加上了我們替換默認控制器的AddSB方法,接管了控制器的生成。AddSbScoped<Engine>和AddSbScoped<ICar, Car>這種添加依賴注入的方式也保持了net core自帶DI的原滋原味,簡潔優雅,並且實現了動態代理,只需要在參數里添加攔截器,就能實時攔截,這裡參數為params Type[],可以添加N個攔截器。

4.寫在最後

   在部落格園潛水了好幾年,見證了net core從1.0到快出3.0,這也是第一次嘗試著寫部落格,為net core的發揚光大盡自己的一份力。