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
- 判定递归stack是否有循环依赖
- 循环依赖条件1: 递归层级大于等于50
- 循环依赖条件2: 递归链上存在当前需要构造的对象
- 构造对象
- 将步骤3构造的对象push到递归stack中
- 当前对象构造完成pop递归stack
- 递归层级减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>();
下面是单例注入的执行步骤:
- 获取ServiceA的实例, 因为注入方式是单例, 所以会缓存ServiceA的对象到_sharedInstances字典中.
- 解析ServiceA的属性IServiceB, 尝试从缓存_sharedInstances字典中查找, 如果找到直接赋值给属性并返回.
- 如果没有找到, 再构造一个ServiceB的实例缓存到_sharedInstances字典中, 并赋值给属性并返回.
隔壁Java Spring如何解决循环依赖
核心思想相同, 都是需要缓存并未填充属性的单例对象, earlySingletonObjects 和 _sharedInstances 这两个集合对象的作用相同.
- singletonObject : 用于存放完全初始化好的bean, 从缓存中直接获取
- earlySingletonObjects: 存放原始的bean对象(尚未填充属性), 用于解决循环依赖
- 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" 转载请保留原文链接及作者。