1. 程式人生 > >net core天馬行空系列:原生DI+AOP實現spring boot註解式程式設計

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

       寫過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的發揚光大儘自己的一份力。

    &n