Abp vNext 源码分析 - 3. 依赖注入与拦截器

一、简要说明

ABP vNext 框架在使用依赖注入服务的时候,是直接使用的微软提供的 Microsoft.Extensions.DependencyInjection 包。这里与原来的 ABP 框架就不一样了,原来的 ABP 框架还需要抽象出来一个 IIocManager 用来管理整个 IoC 容器,现在则直接操作 IServiceCollectionIServiceProvider 进行组件的注册/解析。

这里需要注意的是,虽然现在的依赖注入服务是使用微软官方那一套库进行操作,但是 ABP vNext 还是为我们提供了组件自动注册、拦截器这些基础功能。

二、源码分析

2.1 组件自动注册

ABP vNext 仍然在其 Core 库为我们提供了三种接口,即 ISingletonDependencyITransientDependencyIScopedDependency 接口,方便我们的类型/组件自动注册,这三种接口分别对应了对象的 单例瞬时范围 生命周期。只要任何类型/接口实现了以上任意接口,ABP vNext 就会在系统启动时候,将这些对象注册到 IoC 容器当中。

那么究竟是在什么时候呢?回顾上一章的模块系统的文章,在模块系统调用模块的 ConfigureService() 的时候,就会有一个 services.AddAssembly(module.Type.Assembly) ,他会将模块的所属的程序集传入。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ModuleLoader : IModuleLoader
{
	// ... 其他代码
	protected virtual void ConfigureServices(List<IAbpModuleDescriptor> modules, IServiceCollection services)
	{
		// ... 其他代码
		//ConfigureServices
		foreach (var module in modules)
		{
			if (module.Instance is AbpModule abpModule)
			{
				// 是否跳过服务的自动注册,默认为 false。
				if (!abpModule.SkipAutoServiceRegistration)
				{
					services.AddAssembly(module.Type.Assembly);
				}
			}

			module.Instance.ConfigureServices(context);
		}
		// ... 其他代码
	}
	// ... 其他代码
}

看来核心就在于这个 AddAssembly() 扩展方法了,跳转到方法的内部,发现真正干事的是 IConventionalRegistrar 对象,暂且称之为规约注册器,而且我们可以拥有多个规约注册器,你可以自己实现自动注册规则。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public static IServiceCollection AddAssembly(this IServiceCollection services, Assembly assembly)
{
    // 获得所有规约注册器,然后调用规约注册器的 AddAssmbly 方法注册类型。
	foreach (var registrar in services.GetConventionalRegistrars())
	{
		registrar.AddAssembly(services, assembly);
	}

	return services;
}

该接口定义了三个方法,支持传入程序集、类型数组、具体类型,他们的默认实现都在抽象类 ConventionalRegistrarBase 当中。

1
2
3
4
5
6
7
8
public interface IConventionalRegistrar
{
    void AddAssembly(IServiceCollection services, Assembly assembly);

    void AddTypes(IServiceCollection services, params Type[] types);

    void AddType(IServiceCollection services, Type type);
}

抽象类当中的实现也非常简单,他们最终都是调用的 AddType() 方法来将类型注册到 IServiceCollection 当中的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public abstract class ConventionalRegistrarBase : IConventionalRegistrar
{
	public virtual void AddAssembly(IServiceCollection services, Assembly assembly)
	{
		// 获得程序集内的所有类型,过滤掉抽象类和泛型类型。
		var types = AssemblyHelper
			.GetAllTypes(assembly)
			.Where(
				type => type != null &&
						type.IsClass &&
						!type.IsAbstract &&
						!type.IsGenericType
			).ToArray();

		AddTypes(services, types);
	}

	public virtual void AddTypes(IServiceCollection services, params Type[] types)
	{
		foreach (var type in types)
		{
			AddType(services, type);
		}
	}

	public abstract void AddType(IServiceCollection services, Type type);
}

所以我们的重点就在于 AddType() 方法,ABP vNext 框架默认的规约注册器叫做 DefaultConventionalRegistrar,跳转到其定义可以发现在其内部,除了对三种生命周期接口处理之外,如果类型使用了 DependencyAttribute 特性,也会根据该特性的参数配置进行不同的注册逻辑。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public override void AddType(IServiceCollection services, Type type)
{
	// 判断类型是否标注了 DisableConventionalRegistration 特性,如果有标注,则跳过。
	if (IsConventionalRegistrationDisabled(type))
	{
		return;
	}

	// 获得 Dependency 特性,如果没有则返回 null。
	var dependencyAttribute = GetDependencyAttributeOrNull(type);
	// 优先使用 Dependency 特性所指定的生命周期,如果不存在则根据 type 实现的接口确定生命周期。
	var lifeTime = GetLifeTimeOrNull(type, dependencyAttribute);

	if (lifeTime == null)
	{
		return;
	}

	// 获得等待注册的类型定义,类型的定义优先使用 ExposeServices 特性指定的类型,如果没有则使用
	// 类型当中接口以 I 开始,后面为实现类型名称的接口。
	foreach (var serviceType in AutoRegistrationHelper.GetExposedServices(services, type))
	{
		var serviceDescriptor = ServiceDescriptor.Describe(serviceType, type, lifeTime.Value);

		if (dependencyAttribute?.ReplaceServices == true)
		{
			// 替换服务。
			services.Replace(serviceDescriptor);
		}
		else if (dependencyAttribute?.TryRegister == true)
		{
			// 注册服务。
			services.TryAdd(serviceDescriptor);
		}
		else
		{
			// 注册服务。
			services.Add(serviceDescriptor);
		}
	}
}

这里就是在 GetLifeTimeOrNull() 内部的 GetServiceLifetimeFromClassHierarcy() 方法确定了每个接口对应的生命周期。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
protected virtual ServiceLifetime? GetServiceLifetimeFromClassHierarcy(Type type)
{
	if (typeof(ITransientDependency).GetTypeInfo().IsAssignableFrom(type))
	{
		return ServiceLifetime.Transient;
	}

	if (typeof(ISingletonDependency).GetTypeInfo().IsAssignableFrom(type))
	{
		return ServiceLifetime.Singleton;
	}

	if (typeof(IScopedDependency).GetTypeInfo().IsAssignableFrom(type))
	{
		return ServiceLifetime.Scoped;
	}

	return null;
}

如果读者有用过 AutoFac 或者 Castle Windsor 这些依赖注入框架的话,就知道我们要注册一个类型,需要知道该类型的定义和实现。这里的 AutoRegistrationHelper 工具类就会为我们确定注册类型的类型定义,与其默认实现。

例如我有两个接口 IDemoTestIDemoTestTwo,和他们的默认实现 DemoTest ,我可以有以下几种方法来确定我的注册类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 指定了两个接口类型的实现都是 DemoTest,在注册的时候就会执行两次注册。
// 分别是 services.AddTransient<IDemoTest,DemoTest>();
// services.AddTransient<IDemoTestTwo,DemoTest>();
[ExposeServices(typeof(IDemoTest),typeof(IDemoTestTwo))]
public class DemoTest : IDemoTest,ITransientDependency
{
    
}

// 或者不显式指定,只需要接口定义符合约定即可。
// services.AddTransient<IDemoTest,DemoTest>();
public class DemoTest : IDemoTest,ITransientDependency
{

}

// 如果连注册接口都没有指定,那么就直接注入当前的实现类型。
// services.AddTransient<DemoTest>();
public class DemoTest : ITransientDependency
{

}

2.2 方法拦截器

2.2.1 ABP vNext 新的抽象层

在 ABP vNext 框架当中,将方法拦截器抽象了一层 IAbpInterceptor,但实际实现还是使用的 Castle.Core 所提供的动态代理功能,其定义在 Volo.Abp.Dependency.DynamicProxy 文件夹当中,如下图。

1555946779214

ABP vNext 将拦截器和方法调用模型都进行了定义,其中 AbpInterceptor 则是 IAbpInterceptor 的默认抽象实现。在ProxyHelper 工具类当中,提供了从代理对象获取真实类型的方法。(PS: 通过 Castle.Core 代理后的对象与原有类型定义是不一致的。)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ABP vNext 当中的拦截器定义
public interface IAbpInterceptor
{
	// 同步方法拦截。
	void Intercept(IAbpMethodInvocation invocation);

	// 异步方法拦截。
	Task InterceptAsync(IAbpMethodInvocation invocation);
}

// ABP vNext 当中拦截器的默认抽象实现。
public abstract class AbpInterceptor : IAbpInterceptor
{
	public abstract void Intercept(IAbpMethodInvocation invocation);

	// 异步方法本质上还是调用同步方法,并返回一个已完成的 Task。
	public virtual Task InterceptAsync(IAbpMethodInvocation invocation)
	{
		Intercept(invocation);
		return Task.CompletedTask;
	}
}

至于 IAbpMethodInvocation 接口,则是封装了一个被拦截方法调用时的各种参数,例如被拦截方法的在调用时所传递的参数,返回值类型,方法定义等。而 ABP vNext 也为它建立了一个 CastleAbpMethodInvocationAdapter 适配器,实现了上述接口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public interface IAbpMethodInvocation
{
	object[] Arguments { get; }

	IReadOnlyDictionary<string, object> ArgumentsDictionary { get; }

	Type[] GenericArguments { get; }

	object TargetObject { get; }

	MethodInfo Method { get; }

	object ReturnValue { get; set; }

	void Proceed();

	Task ProceedAsync();
}

2.2.2 Castle.Core 动态代理的集成

ABP vNext 在实际使用的时候,还是通过 Castle.Core 提供的动态代理功能来实现拦截器,相关的代码存放在 Volo.Abp.Castle.Core 库和 Volo.Abp.Autofac 库当中。

首先我们来看 Castle.Core 库对接口 IAbpMethodInvocationIAbpInterceptor 的实现,在 CastleAbpInterceptorAdapter 中通过适配器来定义了一个标准的 Castle 拦截器,这个拦截器可以传入 ABP vNext 定义的 IAbpInterceptor 作为其泛型参数。

1
2
3
4
5
public class CastleAbpInterceptorAdapter<TInterceptor> : IInterceptor
	where TInterceptor : IAbpInterceptor
{
	
}

Castle 的拦截器也会有一个 Intercept() 方法,该方法将在被拦截方法执行的时候触发。在触发之后,会根据当前方法的定义进行不同的操作,这里异步方法和同步方法处理逻辑是不一样的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public void Intercept(IInvocation invocation)
{
	var proceedInfo = invocation.CaptureProceedInfo();

	var method = invocation.MethodInvocationTarget ?? invocation.Method;

    // 判断执行的方法是否是异步方法。
	if (method.IsAsync())
	{
		InterceptAsyncMethod(invocation, proceedInfo);
	}
	else
	{
		InterceptSyncMethod(invocation, proceedInfo);
	}
}

这里我们以异步方法为例,其内部又会根据方法的返回值是否是 Task 进行不同的操作,因为如果是泛型的 Task,说明该异步方法是有返回值的,所以处理逻辑也不一样。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
private void InterceptAsyncMethod(IInvocation invocation, IInvocationProceedInfo proceedInfo)
{
	if (invocation.Method.ReturnType == typeof(Task))
	{
		invocation.ReturnValue = MethodExecuteWithoutReturnValueAsync
			.Invoke(this, new object[] { invocation, proceedInfo });
	}
	else
	{
		invocation.ReturnValue = MethodExecuteWithReturnValueAsync
			.MakeGenericMethod(invocation.Method.ReturnType.GenericTypeArguments[0])
			.Invoke(this, new object[] {invocation, proceedInfo});
	}
}

进一步解析在返回类型为 Task 时,它所调用的方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private async Task ExecuteWithoutReturnValueAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo)
{
    // 注意这里,该用法在之前的 C# 多线程学习笔记文章有说过,作用是出让当前核心给其他线程。
	await Task.Yield();

    // 调用真实的拦截器,根据传入的方法调用模型去拦截真实的方法。
	await _abpInterceptor.InterceptAsync(
		new CastleAbpMethodInvocationAdapter(invocation, proceedInfo)
	);
}

从上述代码可以得知,ABP vNext 的拦截器动作现在被包裹在一个 Castle 拦截器内部进行的。

1555980916284

Snipaste_2019-04-23_10-37-32

那么,我们的 Castle.Core 拦截器在什么时候与类型进行绑定的呢,每个拦截器又是如何与特性的类型进行注册的呢?这里我以审计日志拦截器为例,看一下它在系统当中是如何注册,并被使用的。

审计日志相关的代码存放在 Volo.Abp.Auditing 库中,我们找到 AuditingInterceptor 类型,查看其定义可以看到它也是继承自 AbpInterceptor 抽象基类。

1
2
3
4
public class AuditingInterceptor : AbpInterceptor, ITransientDependency
{
	
}

接着我们根据名字找到了拦截器的注册工具类 AuditingInterceptorRegistrar,在类型的定义当中 ShouldIntercept()ShouldAuditTypeByDefault() 根据传入的 Type 类型,根据特定的逻辑决定是否为该类型关联审计日志拦截器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private static bool ShouldIntercept(Type type)
{
	if (ShouldAuditTypeByDefault(type))
	{
		return true;
	}

    // 如果类型的任意方法启用了 Auditied 特性,则应用拦截器。
	if (type.GetMethods().Any(m => m.IsDefined(typeof(AuditedAttribute), true)))
	{
		return true;
	}

	return false;
}

public static bool ShouldAuditTypeByDefault(Type type)
{
    // 判断类型是否使用了 Audited 特性,使用了则应用审计日志拦截器。
	if (type.IsDefined(typeof(AuditedAttribute), true))
	{
		return true;
	}

    // 判断类型是否使用了 DisableAuditing 特性,使用了则不关联拦截器。
	if (type.IsDefined(typeof(DisableAuditingAttribute), true))
	{
		return false;
	}

    // 如果类型实现了 IAuditingEnabled 接口,则启用拦截器。
	if (typeof(IAuditingEnabled).IsAssignableFrom(type))
	{
		return true;
	}

	return false;
}

我们这里需要关注的是 RegisterIfNeeded() 方法,它在审计日志模块的预加载方法就被添加到了一个 ServiceRegistrationActionList 集合当中,这个集合会在后面 AutoFac 进行类型注册的时候被使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static void RegisterIfNeeded(IOnServiceRegistredContext context)
{
    // 如果类型允许被审计日志拦截器所拦截,则在类型关联的拦截器上下文当中添加审计日志拦截器。
	if (ShouldIntercept(context.ImplementationType))
	{
		context.Interceptors.TryAdd<AuditingInterceptor>();
	}
}
public override void PreConfigureServices(ServiceConfigurationContext context)
{
    // 将这个 Action 加入 List。
	context.Services.OnRegistred(AuditingInterceptorRegistrar.RegisterIfNeeded);
}

继续查看 OnRegistred() 的代码,得到如下的定义,可以看到最后的 Action 会被添加到一个 ServiceRegistrationActionList 访问器中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public static void OnRegistred(this IServiceCollection services, Action<IOnServiceRegistredContext> registrationAction)
{
	GetOrCreateRegistrationActionList(services).Add(registrationAction);
}

public static ServiceRegistrationActionList GetRegistrationActionList(this IServiceCollection services)
{
	return GetOrCreateRegistrationActionList(services);
}

private static ServiceRegistrationActionList GetOrCreateRegistrationActionList(IServiceCollection services)
{
	var actionList = services.GetSingletonInstanceOrNull<IObjectAccessor<ServiceRegistrationActionList>>()?.Value;
	if (actionList == null)
	{
		actionList = new ServiceRegistrationActionList();
		services.AddObjectAccessor(actionList);
	}

	return actionList;
}

AutoFac 在执行注册操作的时候,会调用 AutofacRegistration 静态类的 Register 方法,该方法会遍历整个 IServiceCollection 集合。在将类型注册到 AutoFac 的 IoC 容器中的时候,在它的内部会调用 AbpRegistrationBuilderExtensions 提供的扩展方法为具体的类型添加过滤器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static void Register(
		ContainerBuilder builder,
		IServiceCollection services)
{
	var moduleContainer = services.GetSingletonInstance<IModuleContainer>();
	// 获取之前添加的上下文集合,即审计日志拦截器在预加载方法添加的 Action 集合。
	var registrationActionList = services.GetRegistrationActionList();

	foreach (var service in services)
	{
		if (service.ImplementationType != null)
		{
			var serviceTypeInfo = service.ServiceType.GetTypeInfo();
			if (serviceTypeInfo.IsGenericTypeDefinition)
			{
				builder
					.RegisterGeneric(service.ImplementationType)
					.As(service.ServiceType)
					.ConfigureLifecycle(service.Lifetime)
					// 这里是重点,传入了 Action 集合,调用了扩展方法。
					.ConfigureAbpConventions(moduleContainer, registrationActionList);
			}
			// ... 注释了其他代码。
		}
		// ... 注释了其他代码。
	}
}

下面是扩展方法所定义的相关代码,注意阅读注释。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> ConfigureAbpConventions<TLimit, TActivatorData, TRegistrationStyle>(
		this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> registrationBuilder, 
		IModuleContainer moduleContainer, 
		ServiceRegistrationActionList registrationActionList)
	where TActivatorData : ReflectionActivatorData
{
	// ... 注释了其他代码。
	registrationBuilder = registrationBuilder.InvokeRegistrationActions(registrationActionList, serviceType, implementationType);
	// ... 注释了其他代码。
}

private static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> InvokeRegistrationActions<TLimit, TActivatorData, TRegistrationStyle>(this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> registrationBuilder, ServiceRegistrationActionList registrationActionList, Type serviceType, Type implementationType) 
	where TActivatorData : ReflectionActivatorData
{
	// 构造上下文,以便去调用之前传入的 Action。
	var serviceRegistredArgs = new OnServiceRegistredContext(serviceType, implementationType);

	foreach (var registrationAction in registrationActionList)
	{
		// 以审计日志拦截器为例,这里会调用在预加载方法传入的 AuditingInterceptorRegistrar.RegisterIfNeeded 方法。
		registrationAction.Invoke(serviceRegistredArgs);
	}

	// 这里的 Interceptors 实际上就是 AuditingInterceptorRegistrar.RegisterIfNeeded 内部添加的拦截器哦。
	if (serviceRegistredArgs.Interceptors.Any())
	{
		registrationBuilder = registrationBuilder.AddInterceptors(
			serviceType,
			serviceRegistredArgs.Interceptors
		);
	}

	return registrationBuilder;
}

private static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> AddInterceptors<TLimit, TActivatorData, TRegistrationStyle>(
	this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> registrationBuilder, 
	Type serviceType,
	IEnumerable<Type> interceptors)
	where TActivatorData : ReflectionActivatorData
{
	// ... 注释了其他代码。

	foreach (var interceptor in interceptors)
	{
		// 构造真实的拦截器,并与类型集成。
		registrationBuilder.InterceptedBy(
			typeof(CastleAbpInterceptorAdapter<>).MakeGenericType(interceptor)
		);
	}

	return registrationBuilder;
}

2.3 对象访问器

在第一章节的时候,我们就遇到过 IObjectAccessor<T> 接口,基本上是针对该接口所提供的 Value 属性进行操作,下面就是该接口的定义和它的默认实现 ObjectAccessor<T>,十分简单,就一个泛型的 Value。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public interface IObjectAccessor<out T>
{
    [CanBeNull]
    T Value { get; }
}

public class ObjectAccessor<T> : IObjectAccessor<T>
{
	public T Value { get; set; }

	public ObjectAccessor()
	{
		
	}

	public ObjectAccessor([CanBeNull] T obj)
	{
		Value = obj;
	}
}

仅仅看上述的代码,是看不出什么名堂的,接着我们来到它的扩展方法定义 ServiceCollectionObjectAccessorExtensions

可以看到其核心的代码在于 ObjectAccessor<T> AddObjectAccessor<T>(this IServiceCollection services, ObjectAccessor<T> accessor) 这个重载方法。它首先判断某个特定泛型的对象访问器是否被注册,如果被注册直接抛出异常,没有则继续。

最后呢通过一个小技巧,将某个特定类型的对象访问器作为单例注册到 IoC 容器的头部,方便快速检索。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static ObjectAccessor<T> AddObjectAccessor<T>(this IServiceCollection services, ObjectAccessor<T> accessor)
{
	if (services.Any(s => s.ServiceType == typeof(ObjectAccessor<T>)))
	{
		throw new Exception("An object accessor is registered before for type: " + typeof(T).AssemblyQualifiedName);
	}

	//Add to the beginning for fast retrieve
	services.Insert(0, ServiceDescriptor.Singleton(typeof(ObjectAccessor<T>), accessor));
	services.Insert(0, ServiceDescriptor.Singleton(typeof(IObjectAccessor<T>), accessor));

	return accessor;
}

使用的时候,从第一章就有见到,这里的对象访问器可以传入一个类型。这个时候其 Value 就是空的,但并不影响该类型的解析,只需要在真正使用之前将其 Value 值赋值为实例对象即可。

只是目前来看,该类型的作用并不是十分明显,更多的时候是一个占位类型而已,你可以在任意时间替换某个类型的对象访问器内部的 Value 值。

2.4 服务的范围工厂

我们知道在依赖注入框架当中,有一种特别的生命周期叫做 Scoped 周期,这个周期在我之前的相关文章有讲过,它是一个比较特别的生命周期。

简单来说,Scoped 对象的生命周期只有在某个范围内是单例存在的,例如以下伪代码,用户会请求 ScopedTest() 接口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class HomeController()
{
    public Task ScopedTest()
    {
        using(var scope = ScopedFactory.CreateScope<TestApp>())
        {
            scope.ChildContainer.Resolve<TestApp>.Name = "111";
            scope.ChildContainer.Resolve<TestController>();
        }
    }
}

public class TestController()
{
    public TestController(TestApp app)
    {
        Console.WritleLine(app.Name);
    }
}

最后在 TestController 中,控制台会输出 111 作为结果,在 HomeController 中 ScopedTest() 语句块结束的时候,obj 对象会被释放,在后续的请求当中,TestApp 都是作为一个 Scoped 对象生存的。

所以流程可以分为以下几步:

  1. 通过 ScopeFactory 创建一个 Scope 范围。
  2. 通过 Scope 范围内的子容器,解析对象。
  3. 子容器在解析时,如果解析出来的类型是 Scope 生命周期,则在整个 Scope 存活期间,它都是单例的
  4. Scope 范围释放,会调用销毁内部的子容器,并销毁掉所有解析出来的对象。

Volo.Abp.Autofac 库当中,定义了使用 AutoFac 封装的范围工厂与服务范围类型的定义,他们将会作为默认的 IServiceScopeFactory 实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
internal class AutofacServiceScopeFactory : IServiceScopeFactory
{
	private readonly ILifetimeScope _lifetimeScope;

	public AutofacServiceScopeFactory(ILifetimeScope lifetimeScope)
	{
		this._lifetimeScope = lifetimeScope;
	}

	public IServiceScope CreateScope()
	{
		return new AutofacServiceScope(this._lifetimeScope.BeginLifetimeScope());
	}
}

这里可以看到,在构建这个工厂的时候,会注入一个 ILifetimScope,这个东西就是 AutoFac 提供的 子容器。在 CreateScope() 方法内部,我们通过构造一个 Scope 作为具体的范围解析对象,并将子容器传入到它的内部。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
internal class AutofacServiceScope : IServiceScope
{
	private readonly ILifetimeScope _lifetimeScope;

	public AutofacServiceScope(ILifetimeScope lifetimeScope)
	{
		// 构造子容器。
		this._lifetimeScope = lifetimeScope;
		this.ServiceProvider = this._lifetimeScope.Resolve<IServiceProvider>();
	}

	public IServiceProvider ServiceProvider { get; }

	public void Dispose()
	{
		// 范围释放的时候,释放子容器。
		this._lifetimeScope.Dispose();
	}
}

那么是在什么时候,我们的范围工厂会被调用来构造一个 IServiceScope 对象呢?就是在 ASP.NET Core 每次请求的时候,它在获得其内部的 RequestServices 时,就会通过 IServiceProvidersFeature 来创建一个 Scope 范围。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public IServiceProvider RequestServices
{
	get
	{
		if (!_requestServicesSet)
		{
			_context.Response.RegisterForDispose(this);
			// 通过工厂,创建一个范围解析对象,这里就是 AutofacServiceScopeFactory。
			_scope = _scopeFactory.CreateScope();
			_requestServices = _scope.ServiceProvider;
			_requestServicesSet = true;
		}
		return _requestServices;
	}

	set
	{
		_requestServices = value;
		_requestServicesSet = true;
	}
}

所以,我们在每次请求的时候,针对于 Scope 声明周期的对象,默认的话都是在整个请求处理期间,都是单例的,除非显式使用 using 语句块声明作用域。

而在 ABP vNext 中给我们提供了两个 Scoped Factory,分别是 HttpContextServiceScopeFactoryDefaultServiceScopeFactory ,它们都继承自 IHybridServiceScopeFactory 接口。

这个 IHybridServiceScopeFactory 接口只是一个空的接口,并继承自 Microsoft Dependency Inject 提供的 IServiceScopeFactory 工厂接口。

但在实际注入的时候,并不会替换掉默认的 IServiceScopeFactory 实现。因为在 IHybridServiceScopeFactory 的默认两个实现的定义上,他们都显式得通过 ExposeServices 特性说明了自己是哪些类型的默认实现,且一般使用的时候,都是通过注入 IHybridServiceScopeFactory 并结合 using 语句块来操作。

例如在 Volo.Abp.Data 库的 DataSeeder 类型中,有如下用法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public async Task SeedAsync(DataSeedContext context)
{
	using (var scope = ServiceScopeFactory.CreateScope())
	{
		foreach (var contributorType in Options.Contributors)
		{
			var contributor = (IDataSeedContributor) scope
				.ServiceProvider
				.GetRequiredService(contributorType);

			await contributor.SeedAsync(context);
		}
	}
}

只是这两个实现有什么不同呢?通过两个类型的名字就可以看出来,一个是给 ASP.NET Core MVC 程序使用的,另一个则是默认的范围工厂,下面我们从代码层面上来比较一下两者之间的差别。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
[ExposeServices(
	typeof(IHybridServiceScopeFactory), 
	typeof(DefaultServiceScopeFactory)
	)]
public class DefaultServiceScopeFactory : IHybridServiceScopeFactory, ITransientDependency
{
	// 直接注入封装的 AutofacServiceScopeFactory。
	protected IServiceScopeFactory Factory { get; }

	public DefaultServiceScopeFactory(IServiceScopeFactory factory)
	{
		Factory = factory;
	}

	public IServiceScope CreateScope()
	{
		// 通过 AutofacServiceScopeFactory 创建一个 scope。
		return Factory.CreateScope();
	}
}

HttpContextServiceScopeFactory 是放在 AspNetCore 模块下的,从他的 Dependency 特性可以看出来,他会替换掉默认的 DefaultServiceScopeFactory 实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
[ExposeServices(
	typeof(IHybridServiceScopeFactory),
	typeof(HttpContextServiceScopeFactory)
	)]
[Dependency(ReplaceServices = true)]
public class HttpContextServiceScopeFactory : IHybridServiceScopeFactory, ITransientDependency
{
	protected IHttpContextAccessor HttpContextAccessor { get; }

	// AutoFacServiceScopeFactory
	protected IServiceScopeFactory ServiceScopeFactory { get; }

	public HttpContextServiceScopeFactory(
		IHttpContextAccessor httpContextAccessor, 
		IServiceScopeFactory serviceScopeFactory)
	{
		HttpContextAccessor = httpContextAccessor;
		ServiceScopeFactory = serviceScopeFactory;
	}

	public virtual IServiceScope CreateScope()
	{
		// 假如 HTTP 上下文为空,直接使用 AutoFacScopeFactory 创建一个范围。
		var httpContext = HttpContextAccessor.HttpContext;
		if (httpContext == null)
		{
			return ServiceScopeFactory.CreateScope();
		}

		// 使用 HttpContext 的 RequestServices 构建一个 Scope。
		return new NonDisposedHttpContextServiceScope(httpContext.RequestServices);
	}

	protected class NonDisposedHttpContextServiceScope : IServiceScope
	{
		public IServiceProvider ServiceProvider { get; }

		public NonDisposedHttpContextServiceScope(IServiceProvider serviceProvider)
		{
			ServiceProvider = serviceProvider;
		}

		public void Dispose()
		{
			
		}
	}
}

可以看到,后者如果在 HttpContext 不为 null 的时候,是使用的 HttpContext.RequestServices 作为这个 Scope 的解析器。

RequestServices, on the other hand, is a scoped container created from the root on each request.

翻译成中文的意思就是,它是在每个请求的的时候创建的独立范围容器,其实就是开头所说的子容器。

2.5 类型注册完成的动作

其实这个玩意儿应该放在 2.2 节之前讲,只是在写完之后我才看到相关类型是放在依赖注入相关的文件夹当中,这里还请各位读者理解一下。

早期在 Castle Windsor 当中,类型在注册完成的时候会有一个注册完成的事件,用户可以挂载该事件来进行一些特殊的处理,比如说为类型添加动态代理。在 ABP vNext 当中因为支持多种不同的依赖注入框架,所以就没有类似的事件来做处理。

ABP vNext 则封装了一个 ServiceRegistrationActionList 类型,该类型用于存储在类型注册完成之后,用户可以执行的操作,可以看到它就是一个 Action 集合,用于存放一系列回调方法。

1
2
3
4
public class ServiceRegistrationActionList : List<Action<IOnServiceRegistredContext>>
{
	
}

由 2.2 节得知,这个玩意儿是在每一个类型注册完成之后,都会被遍历调用其中的 Action 动作。在调用的时候,会将当前注册完成的类型封装成一个 IOnServiceRegistredContext 对象,传递给具体的委托,这样委托就能够知道当前调用的类型,也就能够将拦截器放在其 Interceptors 属性当中了。

1
2
3
4
5
6
public interface IOnServiceRegistredContext
{
	ITypeList<IAbpInterceptor> Interceptors { get; }

	Type ImplementationType { get; }
}

三、总结

ABP vNext 框架针对于依赖注入这块的工作也进行了大量的精简,就代码量来说,比原有 ABP 框架减少了差不多一半左右,而且整个逻辑也比原来更加简洁易懂。

开发人员在使用的时候,其实最多的是关注如何注入自己想要的类型。通过了解 ABP vNext 底层的代码, 方便我们清楚拦截器和依赖注入框架的具体过程,这样在后面扩展功能的时候才能够做到心中有数。

Built with Hugo
主题 StackJimmy 设计