Dora.Interception,為.NET Core度身打造的AOP框架 [4]:與依賴注入框架的無縫整合
Dora.Interception最初的定位就是專門針對.NET Core的AOP框架,所以在整個迭代過程中我大部分是在做減法。對於.NET Core程式開發來說,依賴注入已經成為無處不在並且“深入骨髓”的東西,不論是在進行業務應用的開發,還是進行基礎元件的開發,依賴注入是實現“鬆耦合”最為理想的方式(沒有之一)。對於絕大部分AOP框架來說,它們最終都會體現為建立一個能夠攔截的“代理物件”來實現對方法呼叫的攔截,但是.NET Core中針對服務例項的提供完全由通過IServiceProvider介面表示的DI容器來接管,所以Dora.Interception必須將兩者無縫地整合在一起。與依賴注入框架的整合不僅僅體現在對可被攔截的代理物件的建立,同樣應用在了針對攔截器的定義和註冊上。
一、IInterceptable<T>
由於.NET Core總是採用IServiceProvider介面表示的DI容器來提供注入的依賴服務物件,現在我們得將原始的目標物件轉換成能夠被攔截代理物件,為此我們提供了一個泛型的服務介面IInterceptable<T>,它的Proxy屬性返回的就是這麼一個代理物件。
public interface IInterceptable<T> where T: class { T Proxy { get; } }
由於著了一個幫助我們提供可攔截代理的IInterceptable<T>服務,我們就可以在需要攔截目標型別的地方按照如下的方式注入該服務,並利用其Proxy屬性得到這個可被攔截的代理。
public class HomeController : Controller { private readonly ISystemClock _clock; public HomeController(IInterceptable<ISystemClock> clockAccessor) { _clock = clockAccessor.Proxy; Debug.Assert(typeof(SystemClock) != _clock.GetType()); } }
二、讓IServiceProvider直接代理物件
在被依賴型別的建構函式中注入IInterceptable<T>服務的程式設計方式總顯得有點彆扭,這要求所有具有AOP需求的元件都需要依賴Dora.Interception,這無疑是不現實的。我們最終需要解決的還是如何讓IServiceProvider直接提供可被攔截的代理物件,為此我對.NET Core依賴注入框架的原始碼作了一點很小的改動。這個經過簡單修改的IServiceProvider實現型別就是如下這個InterceptableServiceProvider 型別。至於具體修改了什麼,並不是一兩句話就能說清楚的,這涉及到整個依賴注入框架的設計,有興趣有檢視原始碼。
internal sealed class InterceptableServiceProvider : IServiceProvider, IDisposable, IServiceProviderEngineCallback { internal InterceptableServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options, IInterceptingProxyFactory interceptingProxyFactory); public void Dispose(); public object GetService(Type serviceType); void IServiceProviderEngineCallback.OnCreate(IServiceCallSite callSite); void IServiceProviderEngineCallback.OnResolve(Type serviceType, IServiceScope scope); }
我們在Startup型別的ConfigureServices方法中,呼叫IServiceCollection的擴充套件方法BuildInterceptableServiceProvider建立的就是這麼一個InterceptableServiceProvider 物件。
public class Startup { public IServiceProvider ConfigureServices(IServiceCollection services) { return services ... .BuildInterceptableServiceProvider(); } ... }
三、服務註冊
Dora.Interception所需的服務註冊都是通過呼叫IServiceCollection的擴充套件方法AddInterception來完成的,由於AddInterception會調整現有的服務註冊以支援上面介紹的IInterceptable<T>服務,所以AddInterception方法的呼叫需要放在所有服務註冊結束之後。建立InterceptableServiceProvider的BuildInterceptableServiceProvider方法內部會呼叫AddInterception方法,但是不會對現有的服務註冊作任何修改。
public static class ServiceCollectionExtensions { public static IServiceCollection AddInterception(this IServiceCollection services, Action<InterceptionBuilder> configure = null); public static IServiceProvider BuildInterceptableServiceProvider(this IServiceCollection services, Action<InterceptionBuilder> configure = null); public static IServiceProvider BuildInterceptableServiceProvider(this IServiceCollection services, bool validateScopes, Action<InterceptionBuilder> configure = null); }
AddInterception和BuildInterceptableServiceProvider方法均定義了一個Action<InterceptionBuilder>型別的引數,我們可以利用它對註冊的服務做進一步定製。比如如果我們需要實現自定義的攔截器註冊方式,只需要將自定義的IInterceptorProviderResolver物件新增到InterceptorProviderResolvers 屬性表示的集合中即可。
public class InterceptionBuilder { public InterceptionBuilder(IServiceCollection services); public InterceptorProviderResolverCollection InterceptorProviderResolvers { get; } public IServiceCollection Services { get; } }