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)">&lt;summary&gt;</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)">&lt;/summary&gt;</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)">&lt;summary&gt;</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)">&lt;/summary&gt;</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> &amp;&amp; method.GetCustomAttribute&lt;TransactionalAttribute&gt;() != <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> &amp;&amp; method.GetCustomAttribute&lt;TransactionalAttribute&gt;() != <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)">&lt;summary&gt;</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)">&lt;/summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;typeparam name="TService"&gt;&lt;/typeparam&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;typeparam name="TImplementation"&gt;&lt;/typeparam&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;param name="services"&gt;&lt;/param&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;param name="interceptorTypes"&gt;&lt;/param&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;returns&gt;&lt;/returns&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> IServiceCollection AddSbScoped&lt;TService, TImplementation&gt;(<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)">&lt;summary&gt;</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)">&lt;/summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;typeparam name="TService"&gt;&lt;/typeparam&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;typeparam name="TImplementation"&gt;&lt;/typeparam&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;param name="services"&gt;&lt;/param&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;param name="interceptorTypes"&gt;&lt;/param&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;returns&gt;&lt;/returns&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> IServiceCollection AddSbSingleton&lt;TService, TImplementation&gt;(<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&lt;AutowiredAttribute&gt;() != <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&lt;ValueAttribute&gt;() <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>&lt;IInterceptor&gt; interceptors =<span style="color: rgba(0, 0, 0, 1)"> interceptorTypes.ToList()
                .ConvertAll</span>&lt;IInterceptor&gt;(interceptorType =&gt; 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)">&lt;summary&gt;</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)">&lt;/summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;typeparam name="TService"&gt;&lt;/typeparam&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;param name="services"&gt;&lt;/param&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;param name="interceptorTypes"&gt;&lt;/param&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;returns&gt;&lt;/returns&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> IServiceCollection AddSbTransient&lt;TService&gt;(<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)">&lt;summary&gt;</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)">&lt;/summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;typeparam name="TService"&gt;&lt;/typeparam&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;param name="services"&gt;&lt;/param&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;param name="interceptorTypes"&gt;&lt;/param&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;returns&gt;&lt;/returns&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> IServiceCollection AddSbScoped&lt;TService&gt;(<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)">&lt;summary&gt;</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)">&lt;/summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;typeparam name="TService"&gt;&lt;/typeparam&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;param name="services"&gt;&lt;/param&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;param name="interceptorTypes"&gt;&lt;/param&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;returns&gt;&lt;/returns&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> IServiceCollection AddSbSingleton&lt;TService&gt;(<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>&lt;IInterceptor&gt; interceptors =<span style="color: rgba(0, 0, 0, 1)"> interceptorTypes.ToList()
                .ConvertAll</span>&lt;IInterceptor&gt;(interceptorType =&gt; 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&lt;AutowiredAttribute&gt;() != <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&lt;ValueAttribute&gt;() <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)">&lt;summary&gt;</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)">&lt;/summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;param name="builder"&gt;&lt;/param&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;returns&gt;&lt;/returns&gt;</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>&lt;ControllerFeature&gt;<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&lt;TypeInfo, Type&gt;((Func&lt;TypeInfo, Type&gt;)(c =&gt;<span style="color: rgba(0, 0, 0, 1)"> c.AsType())))
            builder.Services.TryAddTransient(type, type);
        builder.Services.Replace(ServiceDescriptor.Transient</span>&lt;IControllerActivator, SbControllerActivator&gt;<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>
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 </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&lt;AutowiredAttribute&gt;() != <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&lt;ValueAttribute&gt;() <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)">&lt;inheritdoc /&gt;</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>
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</span>&lt;Engine&gt;(<span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(TransactionalInterceptor));
        
        services.AddScoped</span>&lt;IUnitOfWork,UnitOfWork&gt;<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>&lt;ICar, Car&gt;(<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 的发扬光大尽自己的一份力。