ioc循环依赖的发现与解决

前言

最近在重构代码, 为框架添加Autofac作为ioc容器, 在处理业务层时, 遇到不同Service相互依赖的问题, 俗称循环依赖. 后来经过讨论解决了这个问题, 这里记录一下我们的探索过程.

场景介绍

A服务依赖于B服务作为构造函数的参数, B服务依赖于A服务作为构造函数的参数, demo代码如下:

    public class ServiceA : IServiceA
    {
        private readonly IServiceB _serviceB;

        public ServiceA(IServiceB serviceB)
        {
            _serviceB = serviceB;
        }
    }

    public interface IServiceA {}

    public class ServiceB : IServiceB
    {
        private readonly IServiceA _serviceA;

        public ServiceB(IServiceA serviceA)
        {
            _serviceA = serviceA;
        }
    }

    public interface IServiceB {}

第一次讨论

因为.net中通常使用构造函数注入, 如果出现循环依赖, 那么第一步考虑的就是拆分服务A或者服务B, 将互调部分拆分出一个服务AB, 专门处理互调业务. demo代码:

    public class ServiceAB : IServiceAB
    {
        private readonly IServiceA _serviceA;
        private readonly IServiceB _serviceB;
        public ServiceB(IServiceA serviceA, IServiceB serviceB)
        {
            _serviceA = serviceA;
            _serviceB = serviceB;
        }
    }
    public interface IServiceAB {}

优点: 每个服务的作用比较独立和内聚, 也很简单的解决了循环依赖的问题.
缺点: 在实际操作中拆分的规则和粒度很难把握, 会增加一些重复代码.

讨论后的探索

因为Spring中使用常用注解[AutoWired]来进行注入, 且使用的是字段注入, 不会存在循环依赖的问题, 所以探索在Autofac中是否可以使用属性注入来解决这个问题, 尝试后发现可行, demo代码:

    public class ServiceA : IServiceA
    {
        [AutofacInject]
        public IServiceB ServiceBImp {get; set;}
    }

    public class ServiceB : IServiceB
    {
        [AutofacInject]
        public IServiceA ServiceAImp {get; set;}
    }

    //注入方式, Autofac 版本4.6.2
    builder.RegisterType<ServiceB>().As<IServiceB>().SingleInstance().PropertiesAutowired(new PropertySelector(), true);
    builder.RegisterType<ServiceA>().As<IServiceA>().SingleInstance().PropertiesAutowired(new PropertySelector(), true);

    /// <summary>
    /// Autofac 是否自动注入特性 
    /// </summary>
    public class AutofacInjectAttribute : Attribute
    {
        public bool Inject { get; set; } = true; 

        public AutofacInjectAttribute(bool inject = true)
        {
            Inject = inject;
        }
    }

    public class PropertySelector : IPropertySelector
    {
        public bool InjectProperty(PropertyInfo propertyInfo, object instance)
        {
            var autofacInjectAttr = propertyInfo.GetCustomAttributes().FirstOrDefault(p => p.GetType() == typeof(AutofacInjectAttribute)) as AutofacInjectAttribute;
            if (autofacInjectAttr == null)
            {
                return false;
            }
            return autofacInjectAttr.Inject;
        }
    }

到此为止, 需求已经能完全满足我们的重构, 现在还留下了2个问题需要解决, ioc容器如何发现的循环依赖和属性注入如何解决循环依赖?

ioc容器如何发现循环依赖

在Autofac源码中, ResolveOperation.GetOrCreateInstance 方法中找到了答案, 下面是 GetOrCreateInstance的执行过程

  1. 当前递归的层级加1
  2. 判定递归stack是否有循环依赖
    1. 循环依赖条件1: 递归层级大于等于50
    2. 循环依赖条件2: 递归链上存在当前需要构造的对象
  3. 构造对象
  4. 将步骤3构造的对象push到递归stack中
  5. 当前对象构造完成pop递归stack
  6. 递归层级减1
    public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest request)
    {
        if (_ended) throw new ObjectDisposedException(ResolveOperationResources.TemporaryContextDisposed, innerException: null);
        ++_callDepth;
        if (_activationStack.Count > 0)
            CircularDependencyDetector.CheckForCircularDependency(request.Registration, _activationStack, _callDepth);
        var activation = new InstanceLookup(this, currentOperationScope, request);
        _activationStack.Push(activation);
        var handler = InstanceLookupBeginning;
        handler?.Invoke(this, new InstanceLookupBeginningEventArgs(activation));
        try
        {
            var instance = activation.Execute();
            _successfulActivations.Add(activation);
            return instance;
        }
        finally
        { 
            _activationStack.Pop();
            if (_activationStack.Count == 0)
            {
                CompleteActivations();
            }
            --_callDepth;
        }
    }

    public static void CheckForCircularDependency(IComponentRegistration registration, Stack<InstanceLookup> activationStack, int callDepth)
    {
        if (registration == null) throw new ArgumentNullException(nameof(registration));
        if (callDepth > MaxResolveDepth)
            throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, CircularDependencyDetectorResources.MaxDepthExceeded, registration));
        // Checks for circular dependency
        foreach (var a in activationStack)
        {
            if (a.ComponentRegistration == registration)
            {
                throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, CircularDependencyDetectorResources.CircularDependency, CreateDependencyGraphTo(registration, activationStack)));
            }
        }
    }

属性注入如何解决循环依赖

属性注入的解决依赖的前提: 1. 被循环依赖的属性比如是单例注入的; 2. 允许循环依赖

    var serviceA = container.Resolve<ServiceA>(); 

下面是单例注入的执行步骤:

  1. 获取ServiceA的实例, 因为注入方式是单例, 所以会缓存ServiceA的对象到_sharedInstances字典中.
  2. 解析ServiceA的属性IServiceB, 尝试从缓存_sharedInstances字典中查找, 如果找到直接赋值给属性并返回.
  3. 如果没有找到, 再构造一个ServiceB的实例缓存到_sharedInstances字典中, 并赋值给属性并返回.

隔壁Java Spring如何解决循环依赖

核心思想相同, 都是需要缓存并未填充属性的单例对象, earlySingletonObjects 和 _sharedInstances 这两个集合对象的作用相同.

  1. singletonObject : 用于存放完全初始化好的bean, 从缓存中直接获取
  2. earlySingletonObjects: 存放原始的bean对象(尚未填充属性), 用于解决循环依赖
  3. singletonFactories: 存放bean工厂对象, 用于解决循环依赖

简单的循环依赖IOC处理Demo

    static void Main(string[] args)
    {
        TinyIoc.Register<IServiceA, ServiceA>();
        TinyIoc.Register<IServiceB, ServiceB>();        
        var serviceA = TinyIoc.GetByProp<IServiceA>();
    }

    class TinyIoc
    {
        private static Dictionary<Type, Type> _typeImplDic = new Dictionary<Type, Type>();
        private static Dictionary<Type, object> _instanceTypeDic = new Dictionary<Type, object>(); // singletonObject
        private static Dictionary<Type, object> _earlyInstanceTypeDic = new Dictionary<Type, object>(); // earlySingletonObjects

        public static object GetByProp(Type typeofT)
        {
            if (_instanceTypeDic.ContainsKey(typeofT)) 
            {
                return _instanceTypeDic[typeofT];
            }
            if (_earlyInstanceTypeDic.ContainsKey(typeofT))
            {
                return _earlyInstanceTypeDic[typeofT];
            }
            var typeImp = _typeImplDic[typeofT];
            var impObj = Activator.CreateInstance(typeImp); //singletonFactories 
            _earlyInstanceTypeDic.Add(typeofT, impObj);
            var propes = typeImp.GetProperties();
            foreach (var item in propes)
            {
                item.SetValue(impObj, GetByProp(item.PropertyType));
            }
            _instanceTypeDic.Add(typeofT, typeImp);
            return impObj;
        }

        public static T GetByProp<T>()
        {
            var typeofT = typeof(T);
            return (T)GetByProp(typeofT);
        }

        public static void Register<IT, T>()
        {
            _typeImplDic.Add(typeof(IT), typeof(T));
        } 
    } 

总结

学习使我快落

代码托管地址


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 zhao4xi@126.com

文章标题:ioc循环依赖的发现与解决

文章字数:1.3k

本文作者:Zhaoxi

发布时间:2019-09-16, 10:30:51

最后更新:2019-09-18, 21:14:07

原始链接:http://zhao4xi.github.io/2019/09/16/ioc循环依赖的发现与解决/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录