Dependency injection “安全地重新初始化”;“单一实例”;DI容器中的依赖关系

Dependency injection “安全地重新初始化”;“单一实例”;DI容器中的依赖关系,dependency-injection,autofac,Dependency Injection,Autofac,我在web应用程序中有一个广泛使用的缓存接口,其实现当前注册为SingleInstance 当前的缓存实现假定单线程初始化,但一旦初始化就不可变,因此可以跨多个线程安全地共享 但是,这意味着当前,如果基础值发生更改,则在应用程序重新启动之前,不会更新缓存。虽然很少更新基础值,但我们现在希望提供修改基础值的应用程序行为,然后通知缓存刷新 我可以修改缓存实现以使用锁定,或者利用一个.NET并发集合安全地更新缓存值 但是,我想知道autofac是否提供了一种功能,允许我在下一次请求时将已注册的实例更改

我在web应用程序中有一个广泛使用的缓存接口,其实现当前注册为
SingleInstance

当前的缓存实现假定单线程初始化,但一旦初始化就不可变,因此可以跨多个线程安全地共享

但是,这意味着当前,如果基础值发生更改,则在应用程序重新启动之前,不会更新缓存。虽然很少更新基础值,但我们现在希望提供修改基础值的应用程序行为,然后通知缓存刷新

我可以修改缓存实现以使用锁定,或者利用一个.NET并发集合安全地更新缓存值

但是,我想知道autofac是否提供了一种功能,允许我在下一次请求时将已注册的实例更改为新实例,这样就不需要修改缓存实现本身

因此理想的行为是,当我们修改底层值时,我们触发新缓存实例的创建。一旦实例完成初始化,所有正在进行的请求将继续使用旧缓存实例,任何新的http请求作用域都将解析为更新的实例

autofac是否提供了支持此方案的内置方式?

要更新注册为单个实例的组件,可以进行如下注册:

builder.RegisterType<ServiceProvider>().SingleInstance();
builder.Register(c => c.Resolve<ServiceProvider>().Service).As<IService>();
public class ServiceProvider
{
    public ServiceProvider()
    {
        this.Service = new Service();
    }

    public IService Service { get; set; }
}
要更新实例,您只需执行以下操作:

container.Resolve<ServiceProvider>().Service = newInstance; 
要更改全局
iSeries设备
实例,您必须解析名为
ServiceProvider的“根目录”

scope.ResolveNamed<ServiceProvider>("root").Service = newInstance; 

您永远无法安全地替换容器中的单例注册实例。一旦其他单例组件依赖于此,它们将只保留对旧实例的引用,而在容器中替换实例意味着某些组件(将在替换操作后创建)将引用新实例,而其他组件将继续引用旧实例。这几乎不会导致您喜欢的行为,并且极有可能导致错误

我的建议是,一旦应用程序运行,就不要尝试更改容器的注册。这将很快变得相当复杂,以监督情况是否正确以及线程是否安全。例如,如果在解析另一个线程的对象图时替换该实例,该怎么办?这可能意味着对象图同时包含对旧实例和新实例的引用

相反,请在应用程序级别解决此问题。首先,您需要两个API;一个用于读取缓存,另一个用于更新缓存。两者都可以使用相同的组件实现,不过:

//您实际需要的非常简化的版本
接口ICache{CacheObject Get();}
接口ICacheUpdater{void Set(CacheObject o);}
过于简单的实现可能如下所示:

密封类缓存:ICache,ICacheUpdater
{
私有静态缓存对象实例;
公共无效集(CacheObject o)=>instance=o;
public CacheObject Get()=>实例;
}
此实现可能会起作用,但如果在同一请求中多次检索缓存,则可以在同一请求中读取新旧值(因为不同的线程可以在两者之间调用
Set
)。这可能是个问题。在这种情况下,您可以将实现更改为以下内容:

密封类HttpCache:ICache,ICacheUpdater
{
私有静态只读对象密钥=typeof(HttpCache);
私有静态缓存对象实例;
私有静态IDictionary项=>HttpContext.Current.items;
公共无效集(CacheObject o)=>instance=o;
public CacheObject Get()=>(CacheObject)items[key]??(items[key]=实例);
}
在此实现中,缓存对象的额外引用存储在
HttpContext.Items
字典中。这确保在执行单个(web)请求期间,始终检索相同的实例


本例假设您正在运行一个web应用程序,但您可以很容易地想象一个不同应用程序类型的解决方案。

因此,如果我理解正确,这将适用于所有注册为
InstancePerRequest
的组件,这些组件依赖于
iSeries
。但是,如果我有任何依赖于
iSeries设备的
SingleInstance
组件,这些组件将永远不会得到更新的缓存实例-因此我需要小心确保我没有其他依赖于
iSeries设备的
SingleInstance
注册Nathan你说得对。但是在所有情况下,如果您有一个依赖于
iSeries设备的
SingleInstance
,您将无法更新它。此实例应依赖另一个服务(即:
IServiceProvider
或@steven answer
ICache
)返回更新的实例。仔细考虑一下,使用您的解决方案,
IService
不再是
单一实例
,因此,如果我碰巧有任何依赖于
iSeries设备的
SingleInstance
注册,autofac应该抛出
DependencyResolutionException
,所以如果我错过了一个,至少会收到警告。@Nathan:没错!您将有一个
DependencyResolutionException
,因为
IService
的解析将需要一个未命名的
ServiceProvider
,它将失败,因为没有“*从请求实例的作用域中看不到与“AutofacWebRequest”匹配的标记的作用域。*”。但我的答案与Autofac紧密相连,而@Steven answer可能更适合您的应用需要
scope.ResolveNamed<ServiceProvider>("root").Service = newInstance; 
scope.Resolve<ServiceProvider>().Service = newInstance;