处理完成之后,我们需要在 RegisterAssemblyByConvention() 方法的内部真正地执行拦截器与代理类的生成工作,逻辑很简单,遍历之前的 _waitRegisterInterceptor 字典,依次使用 ProxyUtils 与 DryIoc 进行代理类的生成与绑定。
public class IocManager : IIocManager { // ... 其他代码 /// <summary> /// 使用已经存在的规约注册器来注册整个程序集内的所有类型。 /// </summary> /// <param>等待注册的程序集</param> /// <param>附加的配置项参数</param> public void RegisterAssemblyByConvention(Assembly assembly, ConventionalRegistrationConfig config) { var context = new ConventionalRegistrationContext(assembly, this, config); foreach (var registerer in _conventionalRegistrars) { registerer.RegisterAssembly(context); } if (config.InstallInstallers) { this.Install(assembly); } // 这里使用 TPL 并行库的原因是因为存在大量仓储类型与应用服务需要注册,应最大限度利用 CPU 来进行操作 Parallel.ForEach(_waitRegisterInterceptor, keyValue => { var proxyBuilder = new DefaultProxyBuilder(); Type proxyType; if (keyValue.Key.IsInterface) proxyType = proxyBuilder.CreateInterfaceProxyTypeWithTargetInterface(keyValue.Key, ArrayTools.Empty<Type>(), ProxyGenerationOptions.Default); else if (keyValue.Key.IsClass()) proxyType = proxyBuilder.CreateClassProxyTypeWithTarget(keyValue.Key,ArrayTools.Empty<Type>(),ProxyGenerationOptions.Default); else throw new ArgumentException($"类型 {keyValue.Value} 不支持进行拦截器服务集成。"); var decoratorSetup = Setup.DecoratorWith(useDecorateeReuse: true); // 使用 ProxyBuilder 创建好的代理类替换原有类型的实现 IocContainer.Register(keyValue.Key,proxyType, made: Made.Of(type=>type.GetConstructors().SingleOrDefault(c=>c.GetParameters().Length != 0), Parameters.Of.Type<IInterceptor[]>(request => { var objects = new List<object>(); foreach (var interceptor in keyValue.Value) { objects.Add(request.Container.Resolve(interceptor)); } return objects.Cast<IInterceptor>().ToArray(); }), PropertiesAndFields.Auto), setup: decoratorSetup); }); _waitRegisterInterceptor.Clear(); } // ... 其他代码 }这样的话,在调用控制器或者应用服务方法的时候能够正确的获取到真实的代理类型。
图:
可以看到拦截器不像原来那样是多个层级的情况,而是直接注入到代理类当中。
通过 invocation 参数,我们也可以直接获取到被代理对象的真实类型。
2. 问题 2 2.1 现象与原因问题 2 则是由于 DryIoc 的 Adapter 针对于 Scoped 生命周期对象的处理不同而引起的,比较典型的情况就是在 Startup 类当中使用 IServiceCollection.AddDbContxt<TDbContext>() 方法注入了一个 DbContext 类型,因为其方法内部默认是使用 ServiceLifeTime.Scoped 周期来进行注入的。
public static IServiceCollection AddDbContext<TContextService, TContextImplementation>( [NotNull] this IServiceCollection serviceCollection, [CanBeNull] Action<DbContextOptionsBuilder> optionsAction = null, ServiceLifetime contextLifetime = ServiceLifetime.Scoped, ServiceLifetime optionsLifetime = ServiceLifetime.Scoped) where TContextImplementation : DbContext, TContextService => AddDbContext<TContextService, TContextImplementation>( serviceCollection, optionsAction == null ? (Action<IServiceProvider, DbContextOptionsBuilder>)null : (p, b) => optionsAction.Invoke(b), contextLifetime, optionsLifetime);按照正常的逻辑,一个 Scoped 对象的生命周期应该是与一个请求一致的,当请求结束之后该对象被释放,而且在该请求的生命周期范围内,通过 Ioc 容器解析出来的 Scoped 对象应该是同一个。如果有新的请求,则会创建一个新的 Scoped 对象。
但是使用 DryIoc 替换了原有 Abp 容器之后,现在如果在一个控制器方法当中解析一个 Scoped 周期的对象,不论是几次请求获得的都是同一个对象。因为这种现象的存在,在 Abp 的 UnitOfWorkBase 当中完成一次数据库查询操作之后,会调用 DbContext 的 Dispose() 方法释放掉 DbContext。这样的话,在第二次请求因为获取的是同一个 DbContext,这样的话就会抛出对象已经被关闭的异常信息。
除了开发人员自己注入的 Scoped 对象,在 Abp 的 Zero 模块内部重写了 Microsoft.Identity 相关组件,而这些组件也是通过 IServiceCollection.AddScoped() 方法与 IServiceCollection.TryAddScoped() 进行注入的。
public static AbpIdentityBuilder AddAbpIdentity<TTenant, TUser, TRole>(this IServiceCollection services, Action<IdentityOptions> setupAction) where TTenant : AbpTenant<TUser> where TRole : AbpRole<TUser>, new() where TUser : AbpUser<TUser> { services.AddSingleton<IAbpZeroEntityTypes>(new AbpZeroEntityTypes { Tenant = typeof(TTenant), Role = typeof(TRole), User = typeof(TUser) }); //AbpTenantManager services.TryAddScoped<AbpTenantManager<TTenant, TUser>>(); //AbpEditionManager services.TryAddScoped<AbpEditionManager>(); //AbpRoleManager services.TryAddScoped<AbpRoleManager<TRole, TUser>>(); services.TryAddScoped(typeof(RoleManager<TRole>), provider => provider.GetService(typeof(AbpRoleManager<TRole, TUser>))); //AbpUserManager services.TryAddScoped<AbpUserManager<TRole, TUser>>(); services.TryAddScoped(typeof(UserManager<TUser>), provider => provider.GetService(typeof(AbpUserManager<TRole, TUser>))); //SignInManager services.TryAddScoped<AbpSignInManager<TTenant, TRole, TUser>>(); services.TryAddScoped(typeof(SignInManager<TUser>), provider => provider.GetService(typeof(AbpSignInManager<TTenant, TRole, TUser>))); // ... 其他注入代码 return new AbpIdentityBuilder(services.AddIdentity<TUser, TRole>(setupAction), typeof(TTenant)); }