在我們的項目中,我們使用SimpleInjector並喜歡它! 我們想擴展添加到該服務的註冊後,將驗證登記符合下列線程安全要求的容器:如何在容器註冊中驗證線程安全
- 每個註冊單應自制[ThreadSafe的]
- 如果被註釋一個服務使用[ThreadSafe]進行註釋,其實現類型也應該使用[ThreadSafe]進行註冊。
我們通過具有ThreadSafetyVerifyingContainer
,它包裝SimpleInjector的容器,它暴露了類似的註冊方法也跟蹤數據SimpleInjector不會被GetCurrentRegistrations()
如開放式泛型類型註冊提供現在驗證這一點。這種方法是有效的,但有其侷限性(就像在條件註冊中支持Func工廠的麻煩一樣),我們問自己是否有辦法掛鉤驗證過程,以便在容器上調用Verify
時注入我們的線程安全驗證。這裏是我們目前的代碼:
/// <summary>
/// A wrapper around C that exposes the subset of <see cref="Container"/> API required for registration at the moment,
/// that during <see cref="Verify"/> it verifies registrations using the underline container and additionally
/// verifies that current registrations conform to thread-safety expectations.
/// </summary>
public class ThreadSafetyVerifyingContainer
{
public ThreadSafetyVerifyingContainer()
{
_underlineContainer = new Container();
_openGenericExplicitRegistrations = new List<RegistrationDescription>();
_threadSafetyExternalAnnotations = new HashSet<Type>();
MarkWellKnownExternalThreadSafeTypes();
}
public ContainerOptions Options => _underlineContainer.Options;
public Container Verify(VerificationOption options = VerificationOption.VerifyAndDiagnose)
{
_underlineContainer.Verify(options);
VerifyThreadSafety();
return _underlineContainer;
}
// Marks a type as thread-safe as a replacement to applying <see cref="ThreadSafeAttribute"/> to a type.
// Should be applied to external types not defined in the application, since <see cref="ThreadSafeAttribute"/>
// exists also for documentation purposes to let developers know some types are expected to be thread safe.
public void MarkExternalTypeAsThreadSafe(Type externalType)
{
_threadSafetyExternalAnnotations.Add(externalType);
}
private void VerifyThreadSafety()
{
var allRegistrationsDescriptions = _underlineContainer
.GetCurrentRegistrations()
.Select(RegistrationDescription.FromInstanceProducer)
.Concat(_openGenericExplicitRegistrations)
.ToArray();
var invalidSingletonRegistrations = allRegistrationsDescriptions
.Where(registration =>
registration.Lifestyle == Lifestyle.Singleton &&
!IsImplementationTypeMarkedAsThreadSafe(registration))
.Select(registration =>
$"The type {registration.ImplementationType} is registered as singleton but isn't marked as thread-safe using {nameof(ThreadSafeAttribute)}");
var invalidThreadSafeServiceRegistrations = allRegistrationsDescriptions
.Where(registration =>
IsTypeMarkedAsThreadSafe(registration.ServiceType) &&
!IsImplementationTypeMarkedAsThreadSafe(registration))
.Select(registration =>
$"The type {registration.ImplementationType} isn't marked as thread-safe using {nameof(ThreadSafeAttribute)} and is registered as implementation of {registration.ServiceType} which is marked as thread-safe");
var violations = invalidSingletonRegistrations.Concat(invalidThreadSafeServiceRegistrations).ToArray();
if (violations.Length > 0)
{
string errorMessage =
$"The container has thread-safety violating registrations:{Environment.NewLine}{string.Join(Environment.NewLine, violations)}";
throw new ThreadSafetyViolationException(errorMessage);
}
}
private void MarkWellKnownExternalThreadSafeTypes()
{
MarkExternalTypeAsThreadSafe(typeof(Container)); // SimpleInjector's Container is registered as singleton and is thread safe, ignore it
MarkExternalTypeAsThreadSafe(typeof(IEnumerable<>)); // Collections are IEnumerable<> implementations registered by the container as singletons, ignore them
}
private bool IsImplementationTypeMarkedAsThreadSafe(RegistrationDescription registration)
{
return IsTypeMarkedAsThreadSafe(registration.ImplementationType);
}
private bool IsTypeMarkedAsThreadSafe(Type type)
{
return
type.GetCustomAttribute<ThreadSafeAttribute>() != null ||
_threadSafetyExternalAnnotations.Contains(type) ||
_threadSafetyExternalAnnotations.Any(threadSafeType => threadSafeType.IsGenericTypeDefinition && type.IsGenericOf(threadSafeType));
}
private readonly HashSet<Type> _threadSafetyExternalAnnotations;
#endregion
public void Register<TService, TImplementation>(Lifestyle lifestyle)
where TService : class where TImplementation : class, TService
{
_underlineContainer.Register<TService, TImplementation>(lifestyle);
}
public void Register(Type openGenericServiceType, Assembly assembly, Lifestyle lifestyle)
{
_underlineContainer.Register(openGenericServiceType, new [] { assembly }, lifestyle);
}
public void RegisterSingleton<TService, TImplementation>()
where TImplementation : class, TService
where TService : class
{
_underlineContainer.RegisterSingleton<TService, TImplementation>();
}
public void RegisterSingleton<TConcrete>()
where TConcrete : class
{
_underlineContainer.RegisterSingleton<TConcrete>();
}
public void RegisterSingleton<TService>(TService instance) where TService : class
{
_underlineContainer.RegisterSingleton(instance);
}
public void RegisterScoped<TService, TImplementation>()
where TImplementation : class, TService
where TService : class
{
_underlineContainer.Register<TService, TImplementation>(Lifestyle.Scoped);
}
public void RegisterTransient<TService, TImplementation>()
where TService : class where TImplementation : class, TService
{
_underlineContainer.Register<TService, TImplementation>();
}
public void RegisterTransient<TImplementation>()
where TImplementation : class
{
_underlineContainer.Register<TImplementation>();
}
public void RegisterTransient<TService>(Func<TService> instanceCreator)
where TService : class
{
_underlineContainer.Register(instanceCreator);
}
public void RegisterCollectionScoped<TService>(IEnumerable<Type> concreteTypes)
where TService : class
{
_underlineContainer.RegisterCollection<TService>(concreteTypes.Select(type => Lifestyle.Scoped.CreateRegistration(type, _underlineContainer)));
}
public void Register(Type serviceType, Type implementationType, Lifestyle lifestyle)
{
_underlineContainer.Register(serviceType, implementationType, lifestyle);
RecordIfExplicitOpenGenericRegistration(serviceType, implementationType, lifestyle);
}
public void RegisterConditional(Type serviceType, Type implementationType, Lifestyle lifestyle, Predicate<PredicateContext> predicate)
{
_underlineContainer.RegisterConditional(serviceType, implementationType, lifestyle, predicate);
RecordIfExplicitOpenGenericRegistration(serviceType, implementationType, lifestyle);
}
public void RegisterImplementationByConsumerContext(Type notGenericServiceType, Type openGenericImplementationType, Lifestyle lifestyle)
{
Guard.CheckNullArgument(notGenericServiceType, nameof(notGenericServiceType));
Guard.CheckNullArgument(openGenericImplementationType, nameof(openGenericImplementationType));
Guard.CheckArgument(notGenericServiceType.IsGenericType, nameof(notGenericServiceType), $"Type {notGenericServiceType} shouldn't be a generic type but it is");
Guard.CheckArgument(!openGenericImplementationType.ContainsGenericParameters, nameof(openGenericImplementationType), $"Type {openGenericImplementationType} isn't an open-generic type");
Guard.CheckArgument(openGenericImplementationType.GetGenericArguments().Length != 1, nameof(openGenericImplementationType), $"Type {openGenericImplementationType} is open-generic but has more than 1 generic parameter");
_underlineContainer.RegisterConditional(
notGenericServiceType,
context => openGenericImplementationType.MakeGenericType(context.Consumer.ImplementationType),
lifestyle,
context => true);
RecordIfExplicitOpenGenericRegistration(notGenericServiceType, openGenericImplementationType, lifestyle);
}
private void RecordIfExplicitOpenGenericRegistration(Type serviceType, Type implementationType, Lifestyle lifestyle)
{
if (implementationType.ContainsGenericParameters)
{
_openGenericExplicitRegistrations.Add(new RegistrationDescription(serviceType, implementationType, lifestyle));
}
}
private readonly List<RegistrationDescription> _openGenericExplicitRegistrations;
[DebuggerDisplay("{ServiceType.Name} -> {ImplementationType.Name} with {Lifestyle.Name} life style")]
private class RegistrationDescription
{
public RegistrationDescription(Type serviceType, Type implementationType, Lifestyle lifestyle)
{
ServiceType = serviceType;
ImplementationType = implementationType;
Lifestyle = lifestyle;
}
public static RegistrationDescription FromInstanceProducer(InstanceProducer instanceProducer)
{
return new RegistrationDescription(
instanceProducer.ServiceType,
instanceProducer.Registration.ImplementationType,
instanceProducer.Registration.Lifestyle);
}
public Type ServiceType { get; }
public Type ImplementationType { get; }
public Lifestyle Lifestyle { get; }
}
private readonly Container _underlineContainer;
}
我打了ExpressionBuild和ExpressionBuilding事件,並發現它涵蓋了正在參加內置的驗證過程中所有的註冊,所以它們可以被用來填補線程安全違法行爲的列表(或任何其他服務-implementat離子生活習慣習慣規則違反)在內置驗證過程結束時引發關於它們的例外。 – Eldar