2016-04-06 69 views
0

在我們的項目中,我們使用SimpleInjector並喜歡它! 我們想擴展添加到該服務的註冊後,將驗證登記符合下列線程安全要求的容器:如何在容器註冊中驗證線程安全

  1. 每個註冊單應自制[ThreadSafe的]
  2. 如果被註釋一個服務使用[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; 
} 
+0

我打了ExpressionBuild和ExpressionBuilding事件,並發現它涵蓋了正在參加內置的驗證過程中所有的註冊,所以它們可以被用來填補線程安全違法行爲的列表(或任何其他服務-implementat離子生活習慣習慣規則違反)在內置驗證過程結束時引發關於它們的例外。 – Eldar

回答

3

目前沒有辦法掛鉤到Verify或診斷。儘管有幾種方法可以從簡單注射器獲取大量信息來自行分析。例如,有一個獲取已知InstanceProducer實例的列表的方法Container.GetCurrentRegistrations。有一個InstanceProducer.GetKnownRelationships方法可以遞歸地遍歷對象圖(診斷服務廣泛使用此方法和KnownRelationship實例)。

然而,對於您的情況,您可能還會看到另一種方法,即覆蓋ILifestyleSelectionBehavior。通過自定義生活方式選擇行爲,您可以影響簡單注射器選擇的生活方式,而不會選擇明確的生活方式。默認情況下,Simple Injector會給你一個短暫的生活方式。在你的情況下,自定義的行爲將如下所示:

container.Options.LifestyleSelectionBehavior = 
    new ThreadSafeAsSingletonLifestyleSelectionBehavior(); 

什麼這樣可以在具有標有[ThreadSafe]是自動所有實現:

class ThreadSafeAsSingletonLifestyleSelectionBehavior : ILifestyleSelectionBehavior { 
    public Lifestyle SelectLifestyle(Type serviceType, Type implementationType) { 
     var sa = serviceType.GetCustomAttribute<ThreadSafeAttribute>(); 
     var ia = implementationType.GetCustomAttribute<ThreadSafeAttribute>(); 
     if (sa != null && ia == null) throw new InvalidOperationException(
      "If a service is annotated with [ThreadSafe] its implementation "+ 
      "type should also be registered with [ThreadSafe]."); 

     return ia == null ? Lifestyle.Transient : Lifestyle.Singleton; 
    } 
} 

如下您可以註冊自定義行爲註冊爲單例,並且強制實施在其服務類型的情況下具有[ThreadSafe]屬性。

這意味着,每次你沒有明確在註冊類型與生活方式,生活方式的選擇行爲踢例:

container.Register<IThreadSafeService, ThreadSafeImplementation>(); 

但是,請注意,這個註冊仍然會得到一個短暫的生活方式:

container.Register<IThreadSafeService, ThreadSafeImplementation>(Lifestyle.Transient);