.NET Core 依赖注入改造(2)- 委托转换

.NET Core 依赖注入改造(1)- 命名服务
.NET Core 依赖注入改造(2)- 委托转换
.NET Core 依赖注入改造(3)- ILogger
.NET Core 依赖注入改造(4)- ActivatorUtilities
.NET Core 依赖注入改造(5)- Context

.NET Core 依赖注入改造(附1)- Autowired

一、

上一篇 写到了命名服务
演示了使用命名服务+委托的方式来获取服务,
但总觉得还有一些不不爽,
为什么委托就必须使完全一样的委托才能获取服务?
为什么不能直接注入方法?

var provider = new ServiceCollection()
        .AddSingleton<Func<object, string>>(o => JsonConvert.SerializeObject(o)) 
        .BuildServiceProvdier();
//........
delegate string ToJsonString(object obj);
var toJsonStriong = provider.GetNamedService<ToJsonString>("ToJsonString");
var method = typeof(JsonConvert).GetMethod("SerializeObject", new[] { typeof(object) });
var provider = new ServiceCollection()
        .AddNamedSingleton("ToJsonString", method) //注入方法
        .BuildServiceProvdier();
//........
delegate string ToJsonString(object obj);
var toJsonStriong = provider.GetNamedService<ToJsonString>("ToJsonString");

所以我准备继续改造一下

二、

经过上一次对源码的探索,整理了官方DI框架大致的执行逻辑


现在有2个需要解决的问题

  1. 做委托类型转换
    这个只能在最后一步GetService时,先获取原来注入的服务,然后根据新的Type进行类型转换;
    这里只要替换掉原来的 ServiceProvider 就可以了;
  2. 在不知道原类型的情况下无法获取原注入的服务(命名服务会简单一些)
    所以我可以将所有注入的委托服务都转换为MethodInfo类型注入,
    这个可以有两种方式干涉
    a. 重新实现 ServiceCollectionAdd 操作中替换
    b. 在编译BuildServiceProvdier之前做,循环所有注入的服务,并替换其中的委托
    a方案代价太大,而且在使用上也有限制,所以我选择了b方案

最终我使用了一个新的扩展方法BuildSupportDelegateServiceProvdier来代替BuildServiceProvdier

改造后的逻辑大概是这样的

三、

扩展方法 BuildSupportDelegateServiceProvdier

public static class DelegateServiceProvdierExtensions
{
    /// <summary>
    /// 构造支持委托转换的服务提供程序
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceProvider BuildSupportDelegateServiceProvdier(this IServiceCollection services)
    {
        var methodServices = new List<ServiceDescriptor>();
        // 循环所有服务
        foreach (var item in services)
        {
            if (typeof(Delegate).IsAssignableFrom(item.ServiceType))
            {
                // 针对委托类型的服务做一次MethodInfo处理
                if (item.ImplementationInstance is Delegate)
                {
                    methodServices.Add(new ServiceDescriptor(typeof(MethodInfo), ((Delegate)item.ImplementationInstance).Method));
                }
                else if (item.ImplementationFactory != null)
                {
                    methodServices.Add(new ServiceDescriptor(typeof(MethodInfo), p => ((Delegate)item.ImplementationFactory(p)).Method, item.Lifetime));
                }
            }
        }
        methodServices.ForEach(services.Add); //注入MethodInfo服务
        var provider = services.BuildServiceProvider();
        return new DelegateServiceProvdier(provider); // 返回装饰类
    }
}

DelegateServiceProvdier 装饰类

/// <summary>
/// 可创建委托服务的提供程序
/// </summary>
internal class DelegateServiceProvdier : IServiceProvider
{
    readonly IServiceProvider _provider;
    readonly ILogger _logger;
    readonly ConcurrentDictionary<Type, object> _services;

    /// <summary>
    /// 构造一个服务提供程序的代理
    /// </summary>
    /// <param name="provider">被代理的服务提供程序</param>
    public DelegateServiceProvdier(IServiceProvider provider)
    {
        _provider = provider ?? throw new ArgumentNullException(nameof(provider));
        _logger = _provider.GetService<ILoggerFactory>()?.CreateLogger<DelegateServiceProvdier>();
        _services = new ConcurrentDictionary<Type, object>(TypeComparer.Instance);
    }

    /// <summary>
    /// 获取指定类型的服务
    /// </summary>
    /// <param name="serviceType">服务类型</param>
    /// <returns></returns>
    public object GetService(Type serviceType)
    {
        if (typeof(IServiceProvider) == serviceType)
        {
            return this;
        }
        // 从 _provider 中获取服务
        var service = _provider.GetService(serviceType);


        if (service == null)
        {
            // 当常规方式没有获取到服务,且服务是委托类型时,尝试获取MethodInfo服务,并返回最后一个签名相同的MethodInfo并转换为指定类型的委托
            return typeof(Delegate).IsAssignableFrom(serviceType)
                    ? _services.GetOrAdd(serviceType, x => ConvertDelegate(x, _provider.GetServices<MethodInfo>()))
                    : null;
        }

        if (service is Delegate delegateService)
        {
            // 当获取的服务是委托,但与要求的类型不符时,尝试转换委托类型
            if (serviceType is IServiceProvider tp
                && tp.GetService(typeof(Type)) is Type delegateType
                && typeof(Delegate).IsAssignableFrom(delegateType)
                && !delegateType.IsInstanceOfType(service))
            {
                return _services.GetOrAdd(serviceType, x => ConvertDelegate(delegateType, new[] { delegateService.Method }));
            }
            return service;
        }

        if (service is IEnumerable enumerable && serviceType.IsGenericType && serviceType.GetGenericArguments().Length == 1)
        {
            // 当获取的服务是泛型集合时
            var type = serviceType.GetGenericArguments()[0];
            if (type is IServiceProvider tp && tp.GetService(typeof(Type)) is Type delegateType)
            {
                return _services.GetOrAdd(serviceType, x => ConvertDelegates(delegateType, enumerable));
            }
            else
            {
                return _services.GetOrAdd(serviceType, x => ConvertDelegates(type, _provider.GetServices<MethodInfo>()));
            }
        }
        return service;
    }

    /// <summary>
    /// 转换委托服务集合
    /// </summary>
    /// <param name="delegateType"></param>
    /// <param name="enumerable"></param>
    /// <returns></returns>
    private IEnumerable ConvertDelegates(Type delegateType, IEnumerable enumerable)
    {
        var newServices = new ArrayList();
        var delegateMethod = delegateType.GetMethod("Invoke");
        foreach (var item in enumerable)
        {
            if (delegateType.IsInstanceOfType(item))
            {
                newServices.Add(item);
                continue;
            }
            var method = (item as Delegate)?.Method ?? item as MethodInfo;
            if (CompareMethodSignature(delegateMethod, method))
            {
                newServices.Add(method.CreateDelegate(delegateType, null));
            }
        }
        return newServices.ToArray(delegateType);
    }

    /// <summary>
    /// 转换委托服务
    /// </summary>
    private object ConvertDelegate(Type delegateType, IEnumerable<MethodInfo> methods)
    {
        var delegateName = delegateType.Name;
        var delegateMethod = delegateType.GetMethod("Invoke");
        MethodInfo last = null;
        MethodInfo lastExact = null;
        foreach (var method in methods)
        {
            if (CompareMethodSignature(method, delegateMethod))
            {
                if (method.Name == delegateName)
                {
                    lastExact = method;
                }
                else if (lastExact == null)
                {
                    last = method;
                }
            }
        }
        try
        {
            return (lastExact ?? last).CreateDelegate(delegateType, null);
        }
        catch (Exception ex)
        {
            _logger?.LogError(ex, ex.Message);
            return null;
        }
    }


    /// <summary>
    /// 比较2个方法签名是否相同
    /// </summary>
    /// <param name="method1">方法1</param>
    /// <param name="method2">方法2</param>
    /// <returns></returns>
    private bool CompareMethodSignature(MethodInfo method1, MethodInfo method2)
    {
        if (method1 == null || method2 == null || method1.ReturnType != method2.ReturnType)
        {
            return false;
        }
        var p1 = method1.GetParameters();
        var p2 = method2.GetParameters();
        if (p1.Length != p2.Length)
        {
            return false;
        }
        for (var i = 0; i < p1.Length; i++)
        {
            if (p1[i].ParameterType != p2[i].ParameterType)
            {
                return false;
            }
        }
        return true;
    }
}

四、

这里有一个坑,需要单开一节来说下

new ConcurrentDictionary<Type, object>(TypeComparer.Instance);

之前代码中的 TypeComparer.Instance 其实是一个自定义实现

原因是

在获取服务集合时(provider.GetServices<ToJsonString>),的一个特殊对象TypeBuilderInstantiation

public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType)
{
    var genericEnumerable = typeof(IEnumerable<>).MakeGenericType(serviceType);
    return (IEnumerable<object>)provider.GetRequiredService(genericEnumerable);
}

问题就出在 typeof(IEnumerable<>).MakeGenericType(serviceType); 这个操作上

仔细看t1t2的类型是不同的,关键点在于NamedType并非系统RuntimeType类型的对象


这里的代码,每次都会new一个新的TypeBuilderInstantiation,如果不重新实现IEqualityComparer<Type>,使用默认方式比较,即使2个属性完全相同的TypeBuilderInstantiation也无法得到相同的HashCode,将造成服务缓存无法正常工作

internal class TypeComparer : IEqualityComparer<Type>
{
    public static readonly TypeComparer Instance = new TypeComparer();
    private static readonly Type _runtimeType = typeof(int).GetType();
    public bool Equals(Type x, Type y)
    {
        if (x == null || y == null)
        {
            return x.Equals(y);
        }
        if ((x.GetType() != _runtimeType && x.IsGenericType) || (y.GetType() != _runtimeType && y.IsGenericType))
        {
            if (!Equals(x.GetGenericTypeDefinition(), y.GetGenericTypeDefinition()))
            {
                return false;
            }
            if (x.IsGenericTypeDefinition || y.IsGenericTypeDefinition)
            {
                return x.IsGenericTypeDefinition == y.IsGenericTypeDefinition;
            }
            var args1 = x.GetGenericArguments();
            var args2 = y.GetGenericArguments();
            if (args1.Length != args2.Length)
            {
                return false;
            }
            for (var i = 0; i < args1.Length; i++)
            {
                if (!Equals(args1[i], args2[i]))
                {
                    return false;
                }
            }
            return true;
        }
        return x.Equals(y);
    }

    public int GetHashCode(Type obj)
    {
        if (obj != null && obj.GetType() != _runtimeType && obj.IsGenericType)
        {
            var hashcode = obj.GetGenericTypeDefinition().GetHashCode();
            if (!obj.IsGenericTypeDefinition)
            {
                foreach (var item in obj.GetGenericArguments())
                {
                    hashcode ^= item.GetHashCode();
                }
            }
            return hashcode;
        }
        return obj?.GetHashCode() ?? 0;
    }
}

五、

最后项目结构是这样的



测试一下


据说,依赖注入时命名服务委托转换更配哦。。。

delegate string ToJsonString(object obj);
delegate string ToXmlString(object obj);
static void Main(string[] args)
{
    var provider = new ServiceCollection()
                         .AddNamedSingleton<Func<object, string>>("ToJsonString", o => JsonConvert.SerializeObject(o))
                         .AddNamedSingleton<Func<object, string>>("ToXmlString", o => o.ToXml().ToString())
                        .BuildSupportDelegateServiceProvdier();

    var x = new
    {
        id = 1,
        name = "blqw"
    };
    var toJsonStriong = provider.GetNamedService<ToJsonString>("ToJsonString");
    Console.WriteLine(toJsonStriong(x));
    var toXmlString = provider.GetNamedService<ToXmlString>("ToXmlString");
    Console.WriteLine(toXmlString(x));

    Business.Operation(provider);
}

六、

github:https://github.com/blqw/blqw.DI/tree/master/src/blqw.DelegateServiceProvdier
nuget:https://www.nuget.org/packages/blqw.DI.DelegateServiceProvdier

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容