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; }</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Value { <span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)">; } }</span></pre>
声明式事物注解
[AttributeUsage(AttributeTargets.Method)] public class TransactionalAttribute : Attribute { }
工作单元接口及实现类,用来实现事物操作,注入级别为 scope,一次请求公用一个工作单元实例
public interface IUnitOfWork : IDisposable { /// <summary> /// 开启事务 /// </summary> void BeginTransaction();</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span> <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 提交 </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Commit(); </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span> <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 事物回滚 </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> RollBack(); }</span></pre>
public class UnitOfWork : IUnitOfWork { public void BeginTransaction() { Console.WriteLine("开启事务");}</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Commit() { Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">提交事务</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">); } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Dispose() { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">throw new System.NotImplementedException();</span>
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> RollBack() { Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">回滚事务</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">); } }</span></pre>
拦截器
/// <summary> /// 事物拦截器 /// </summary> public class TransactionalInterceptor : StandardInterceptor { private IUnitOfWork Uow { set; get; }</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> TransactionalInterceptor(IUnitOfWork uow) { Uow </span>=<span style="color: rgba(0, 0, 0, 1)"> uow; } </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> PreProceed(IInvocation invocation) { Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{0}拦截前</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, invocation.Method.Name); </span><span style="color: rgba(0, 0, 255, 1)">var</span> method =<span style="color: rgba(0, 0, 0, 1)"> invocation.TargetType.GetMethod(invocation.Method.Name); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (method != <span style="color: rgba(0, 0, 255, 1)">null</span> && method.GetCustomAttribute<TransactionalAttribute>() != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { Uow.BeginTransaction(); } } </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> PerformProceed(IInvocation invocation) { invocation.Proceed(); } </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> PostProceed(IInvocation invocation) { Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{0}拦截后, 返回值是{1}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, invocation.Method.Name, invocation.ReturnValue); </span><span style="color: rgba(0, 0, 255, 1)">var</span> method =<span style="color: rgba(0, 0, 0, 1)"> invocation.TargetType.GetMethod(invocation.Method.Name); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (method != <span style="color: rgba(0, 0, 255, 1)">null</span> && method.GetCustomAttribute<TransactionalAttribute>() != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { Uow.Commit(); } } }</span></pre>
用来测试注入效果的接口以及实现类,这里我们定义一辆汽车,汽车拥有一个引擎(属性注入),它能点火启动,点火操作带事物,这里为了演示【接口 - 实现类】注入和【实现类】注入 2 种情况,引擎就没添加接口,只有实现类,并且 AOP 拦截仅有【实现类】的类时,只能拦截虚方法,所以 Start 和 Stop 函数为虚函数。
/// <summary> /// 汽车引擎 /// </summary> public class Engine { [Value("HelpNumber")] public string HelpNumber { set; get; }</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Start() { Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">发动机启动</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">); Stop(); } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Stop() { Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">发动机熄火,拨打求救电话</span><span style="color: rgba(128, 0, 0, 1)">"</span> +<span style="color: rgba(0, 0, 0, 1)"> HelpNumber); } }</span></pre>
public interface ICar { Engine Engine { set; get; }</span><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Fire(); }</span></pre>
public class Car : ICar {[Autowired] public Engine Engine { set; get; }[Value(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">oilNo</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)] </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> OilNo { <span style="color: rgba(0, 0, 255, 1)">set</span>; <span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)">; } [Transactional] </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Fire() { Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">加满</span><span style="color: rgba(128, 0, 0, 1)">"</span> + OilNo + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">号汽油,点火</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">); Engine.Start(); } }</span></pre>
控制器 HomeController
public class HomeController : Controller {[Autowired] public ICar Car{ set; get; }[Value(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">description</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)] </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Description { <span style="color: rgba(0, 0, 255, 1)">set</span>; <span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> IActionResult Index() { </span><span style="color: rgba(0, 0, 255, 1)">var</span> car =<span style="color: rgba(0, 0, 0, 1)"> Car; Console.WriteLine(Description); Car.Fire(); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> View(); } }</span></pre>
修改 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);}</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span> <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 请求级别 </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><typeparam name="TService"></typeparam></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><typeparam name="TImplementation"></typeparam></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="services"></param></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="interceptorTypes"></param></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><returns></returns></span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> IServiceCollection AddSbScoped<TService, TImplementation>(<span style="color: rgba(0, 0, 255, 1)">this</span> IServiceCollection services, <span style="color: rgba(0, 0, 255, 1)">params</span><span style="color: rgba(0, 0, 0, 1)"> Type[] interceptorTypes) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> services.AddSbService(<span style="color: rgba(0, 0, 255, 1)">typeof</span>(TService), <span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(TImplementation), ServiceLifetime.Scoped, interceptorTypes); } </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span> <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 单例 </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><typeparam name="TService"></typeparam></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><typeparam name="TImplementation"></typeparam></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="services"></param></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="interceptorTypes"></param></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><returns></returns></span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> IServiceCollection AddSbSingleton<TService, TImplementation>(<span style="color: rgba(0, 0, 255, 1)">this</span> IServiceCollection services, <span style="color: rgba(0, 0, 255, 1)">params</span><span style="color: rgba(0, 0, 0, 1)"> Type[] interceptorTypes) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> services.AddSbService(<span style="color: rgba(0, 0, 255, 1)">typeof</span>(TService), <span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(TImplementation), ServiceLifetime.Singleton, interceptorTypes); } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> IServiceCollection AddSbService(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)"> IServiceCollection services, Type serviceType, Type implementationType, ServiceLifetime lifetime, </span><span style="color: rgba(0, 0, 255, 1)">params</span><span style="color: rgba(0, 0, 0, 1)"> Type[] interceptorTypes) { services.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ServiceDescriptor(implementationType, implementationType, lifetime)); </span><span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> Factory(IServiceProvider provider) { </span><span style="color: rgba(0, 0, 255, 1)">var</span> target =<span style="color: rgba(0, 0, 0, 1)"> provider.GetService(implementationType); </span><span style="color: rgba(0, 0, 255, 1)">var</span> properties =<span style="color: rgba(0, 0, 0, 1)"> implementationType.GetTypeInfo().DeclaredProperties; </span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (PropertyInfo info <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> properties) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">属性注入</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (info.GetCustomAttribute<AutowiredAttribute>() != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">var</span> propertyType =<span style="color: rgba(0, 0, 0, 1)"> info.PropertyType; </span><span style="color: rgba(0, 0, 255, 1)">var</span> impl =<span style="color: rgba(0, 0, 0, 1)"> provider.GetService(propertyType); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (impl != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { info.SetValue(target, impl); } } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">配置值注入</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (info.GetCustomAttribute<ValueAttribute>() <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> ValueAttribute valueAttribute) { </span><span style="color: rgba(0, 0, 255, 1)">var</span> value =<span style="color: rgba(0, 0, 0, 1)"> valueAttribute.Value; </span><span style="color: rgba(0, 0, 255, 1)">if</span> (provider.GetService(<span style="color: rgba(0, 0, 255, 1)">typeof</span>(IConfiguration)) <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> IConfiguration configService) { </span><span style="color: rgba(0, 0, 255, 1)">var</span> pathValue =<span style="color: rgba(0, 0, 0, 1)"> configService.GetSection(value).Value; </span><span style="color: rgba(0, 0, 255, 1)">if</span> (pathValue != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">var</span> pathV =<span style="color: rgba(0, 0, 0, 1)"> Convert.ChangeType(pathValue, info.PropertyType); info.SetValue(target, pathV); } } } } List</span><IInterceptor> interceptors =<span style="color: rgba(0, 0, 0, 1)"> interceptorTypes.ToList() .ConvertAll</span><IInterceptor>(interceptorType => provider.GetService(interceptorType) <span style="color: rgba(0, 0, 255, 1)">as</span><span style="color: rgba(0, 0, 0, 1)"> IInterceptor); </span><span style="color: rgba(0, 0, 255, 1)">var</span> proxy = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ProxyGenerator().CreateInterfaceProxyWithTarget(serviceType, target, interceptors.ToArray()); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> proxy; }; </span><span style="color: rgba(0, 0, 255, 1)">var</span> serviceDescriptor = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ServiceDescriptor(serviceType, Factory, lifetime); services.Add(serviceDescriptor); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> services; } </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span> <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 瞬时 </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><typeparam name="TService"></typeparam></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="services"></param></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="interceptorTypes"></param></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><returns></returns></span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> IServiceCollection AddSbTransient<TService>(<span style="color: rgba(0, 0, 255, 1)">this</span> IServiceCollection services, <span style="color: rgba(0, 0, 255, 1)">params</span><span style="color: rgba(0, 0, 0, 1)"> Type[] interceptorTypes) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> services.AddSbService(<span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(TService), ServiceLifetime.Transient, interceptorTypes); } </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span> <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 请求 </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><typeparam name="TService"></typeparam></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="services"></param></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="interceptorTypes"></param></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><returns></returns></span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> IServiceCollection AddSbScoped<TService>(<span style="color: rgba(0, 0, 255, 1)">this</span> IServiceCollection services, <span style="color: rgba(0, 0, 255, 1)">params</span><span style="color: rgba(0, 0, 0, 1)"> Type[] interceptorTypes) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> services.AddSbService(<span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(TService), ServiceLifetime.Scoped, interceptorTypes); } </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span> <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 单例 </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><typeparam name="TService"></typeparam></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="services"></param></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="interceptorTypes"></param></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><returns></returns></span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> IServiceCollection AddSbSingleton<TService>(<span style="color: rgba(0, 0, 255, 1)">this</span> IServiceCollection services, <span style="color: rgba(0, 0, 255, 1)">params</span><span style="color: rgba(0, 0, 0, 1)"> Type[] interceptorTypes) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> services.AddSbService(<span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(TService), ServiceLifetime.Singleton, interceptorTypes); } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> IServiceCollection AddSbService(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)"> IServiceCollection services, Type serviceType, ServiceLifetime lifetime, </span><span style="color: rgba(0, 0, 255, 1)">params</span><span style="color: rgba(0, 0, 0, 1)"> Type[] interceptorTypes) { </span><span style="color: rgba(0, 0, 255, 1)">if</span> (services == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ArgumentNullException(nameof(services)); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (serviceType == (Type)<span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ArgumentNullException(nameof(serviceType)); </span><span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> Factory(IServiceProvider provider) { List</span><IInterceptor> interceptors =<span style="color: rgba(0, 0, 0, 1)"> interceptorTypes.ToList() .ConvertAll</span><IInterceptor>(interceptorType => provider.GetService(interceptorType) <span style="color: rgba(0, 0, 255, 1)">as</span><span style="color: rgba(0, 0, 0, 1)"> IInterceptor); </span><span style="color: rgba(0, 0, 255, 1)">var</span> proxy = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ProxyGenerator().CreateClassProxy(serviceType, interceptors.ToArray()); </span><span style="color: rgba(0, 0, 255, 1)">var</span> properties =<span style="color: rgba(0, 0, 0, 1)"> serviceType.GetTypeInfo().DeclaredProperties; </span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (PropertyInfo info <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> properties) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">属性注入</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (info.GetCustomAttribute<AutowiredAttribute>() != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">var</span> propertyType =<span style="color: rgba(0, 0, 0, 1)"> info.PropertyType; </span><span style="color: rgba(0, 0, 255, 1)">var</span> impl =<span style="color: rgba(0, 0, 0, 1)"> provider.GetService(propertyType); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (impl != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { info.SetValue(proxy, impl); } } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">配置值注入</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (info.GetCustomAttribute<ValueAttribute>() <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> ValueAttribute valueAttribute) { </span><span style="color: rgba(0, 0, 255, 1)">var</span> value =<span style="color: rgba(0, 0, 0, 1)"> valueAttribute.Value; </span><span style="color: rgba(0, 0, 255, 1)">if</span> (provider.GetService(<span style="color: rgba(0, 0, 255, 1)">typeof</span>(IConfiguration)) <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> IConfiguration configService) { </span><span style="color: rgba(0, 0, 255, 1)">var</span> pathValue =<span style="color: rgba(0, 0, 0, 1)"> configService.GetSection(value).Value; </span><span style="color: rgba(0, 0, 255, 1)">if</span> (pathValue != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">var</span> pathV =<span style="color: rgba(0, 0, 0, 1)"> Convert.ChangeType(pathValue, info.PropertyType); info.SetValue(proxy, pathV); } } } } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> proxy; }; </span><span style="color: rgba(0, 0, 255, 1)">var</span> serviceDescriptor = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ServiceDescriptor(serviceType, Factory, lifetime); services.Add(serviceDescriptor); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> services; } </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span> <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 添加summer boot扩展 </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="builder"></param></span> <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><returns></returns></span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> IMvcBuilder AddSB(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)"> IMvcBuilder builder) { </span><span style="color: rgba(0, 0, 255, 1)">if</span> (builder == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ArgumentNullException(nameof(builder)); ControllerFeature feature </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ControllerFeature(); builder.PartManager.PopulateFeature</span><ControllerFeature><span style="color: rgba(0, 0, 0, 1)">(feature); </span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (Type type <span style="color: rgba(0, 0, 255, 1)">in</span> feature.Controllers.Select<TypeInfo, Type>((Func<TypeInfo, Type>)(c =><span style="color: rgba(0, 0, 0, 1)"> c.AsType()))) builder.Services.TryAddTransient(type, type); builder.Services.Replace(ServiceDescriptor.Transient</span><IControllerActivator, SbControllerActivator><span style="color: rgba(0, 0, 0, 1)">()); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> builder; } }</span></pre>
第二部分,添加一个自定义控制器激活类,用以替换掉 mvc 自带的激活类,这个类命名为 SbControllerActivator.cs,代码如下
public class SbControllerActivator : IControllerActivator { /// <inheritdoc /> public object Create(ControllerContext actionContext) { if (actionContext == null) throw new ArgumentNullException(nameof(actionContext));Type serviceType </span>=<span style="color: rgba(0, 0, 0, 1)"> actionContext.ActionDescriptor.ControllerTypeInfo.AsType(); </span><span style="color: rgba(0, 0, 255, 1)">var</span> target =<span style="color: rgba(0, 0, 0, 1)"> actionContext.HttpContext.RequestServices.GetRequiredService(serviceType); </span><span style="color: rgba(0, 0, 255, 1)">var</span> properties =<span style="color: rgba(0, 0, 0, 1)"> serviceType.GetTypeInfo().DeclaredProperties; </span><span style="color: rgba(0, 0, 255, 1)">var</span> proxy = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ProxyGenerator().CreateClassProxyWithTarget(serviceType, target); </span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (PropertyInfo info <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> properties) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">属性注入</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (info.GetCustomAttribute<AutowiredAttribute>() != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">var</span> propertyType =<span style="color: rgba(0, 0, 0, 1)"> info.PropertyType; </span><span style="color: rgba(0, 0, 255, 1)">var</span> impl =<span style="color: rgba(0, 0, 0, 1)"> actionContext.HttpContext.RequestServices.GetService(propertyType); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (impl != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { info.SetValue(proxy, impl); } } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">配置值注入</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (info.GetCustomAttribute<ValueAttribute>() <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> ValueAttribute valueAttribute) { </span><span style="color: rgba(0, 0, 255, 1)">var</span> value =<span style="color: rgba(0, 0, 0, 1)"> valueAttribute.Value; </span><span style="color: rgba(0, 0, 255, 1)">if</span> (actionContext.HttpContext.RequestServices.GetService(<span style="color: rgba(0, 0, 255, 1)">typeof</span>(IConfiguration)) <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> IConfiguration configService) { </span><span style="color: rgba(0, 0, 255, 1)">var</span> pathValue =<span style="color: rgba(0, 0, 0, 1)"> configService.GetSection(value).Value; </span><span style="color: rgba(0, 0, 255, 1)">if</span> (pathValue != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">var</span> pathV =<span style="color: rgba(0, 0, 0, 1)"> Convert.ChangeType(pathValue, info.PropertyType); info.SetValue(proxy, pathV); } } } } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> proxy; } </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><inheritdoc /></span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Release(ControllerContext context, <span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> controller) { } }</span></pre>
第三部分,在 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</span><Engine>(<span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(TransactionalInterceptor)); services.AddScoped</span><IUnitOfWork,UnitOfWork><span style="color: rgba(0, 0, 0, 1)">(); services.AddScoped(</span><span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(TransactionalInterceptor)); services.AddSbScoped</span><ICar, Car>(<span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(TransactionalInterceptor)); }</span></pre>
从上面代码我们可以看到,在 addMvc 后加上了我们替换默认控制器的 AddSB 方法,接管了控制器的生成。AddSbScoped<Engine> 和 AddSbScoped<ICar, Car> 这种添加依赖注入的方式也保持了 net core 自带 DI 的原滋原味,简洁优雅,并且实现了动态代理,只需要在参数里添加拦截器,就能实时拦截,这里参数为 params Type[], 可以添加 N 个拦截器。
4. 写在最后
在博客园潜水了好几年,见证了 net core 从 1.0 到快出 3.0,这也是第一次尝试着写博客,为 net core 的发扬光大尽自己的一份力。