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