源码相关
MediaR是一个中介者库,里面实现了请求响应/发布订阅两种模式。
内部相当于一个容器,由源码可知,内部创建时注入了一个容器委托(从容器中获得对应的实现接口的实例)。
// 这里注入了一个委托,用于是从服务容器中获取对应项实例。 public Mediator(ServiceFactory serviceFactory) { _serviceFactory = serviceFactory; }
// 这一段有两个方法,一个是获得一个实例,一个是获得所有实现了这个接口的实例,这个是给发布订阅用的。
public delegate object ServiceFactory(Type serviceType); public static class ServiceFactoryExtensions { public static T GetInstance<T>(this ServiceFactory factory) => (T) factory(typeof(T)); public static IEnumerable<T> GetInstances<T>(this ServiceFactory factory) => (IEnumerable<T>) factory(typeof(IEnumerable<T>)); }
获得所有的订阅者后,将在Mediator的方法Publish依次调用,如果途中取消,可以使用CancellationToken进行标记。
protected virtual async Task PublishCore(IEnumerable<Func<INotification, CancellationToken, Task>> allHandlers, INotification notification, CancellationToken cancellationToken) { foreach (var handler in allHandlers) { await handler(notification, cancellationToken).ConfigureAwait(false); } }
发布订阅排序问题
问题:NOP中可以有顺序,那么问题来了,从上面看来,MediatR并没有给我们对订阅者排序的选择。那么我们如何才能对事件处理者排序呢?
目前有两种方式,第一种是修改MediatR的 源码,添加特性
namespace MediatR.Attributes { [AttributeUsage(AttributeTargets.Class)] public sealed class EventOrderAttribute : Attribute { /// <summary> /// 排序值。 /// </summary> public int Order { get; set; } } }
总所周知,依赖注入一个接口多个实现的时候,可以通过IEnumerable获得所有的实现。
因此,我们可以在获取势力的时候,即上面的方法ServiceFactoryExtensions.IEnumerable<T> GetInstances<T>(this ServiceFactory factory)中处理
如下:
public static class ServiceFactoryExtensions { public static T GetInstance<T>(this ServiceFactory factory) => (T)factory(typeof(T)); public static IEnumerable<T> GetInstances<T>(this ServiceFactory factory) { var instanceList = (IEnumerable<T>)factory(typeof(IEnumerable<T>));
// 反射进行排序。 if (instanceList.Any(o => o.GetType().GetCustomAttributes().Any(o => o.GetType().Equals(typeof(EventOrderAttribute))))) instanceList = instanceList .OrderBy(o => o.GetType().GetCustomAttribute<EventOrderAttribute>() == null ? 0 : o.GetType().GetCustomAttribute<EventOrderAttribute>().Order).ToList(); return instanceList; }
当然,以上方案是不推荐的,毕竟修改了核心源码,但我们依然可以利用容器的特性,安装一定顺序进行排序,然后依次注入,即可实现上面一样的效果。
首先依然保留原来的特性,这次我们把魔手伸向
MediatR.Extensions.Microsoft.DependencyInjection 这个项目中,一下只是为了快速参考,所以直接改这个,你也可以自己实现一个。
在这个项目中,主要的是ServiceRegistrar这个类,进行注册,里面是以此注入不同的接口。
关注:ServiceRegistrar.ConnectImplementationsToTypesClosing方法
private static void ConnectImplementationsToTypesClosing(Type openRequestInterface, IServiceCollection services, IEnumerable<Assembly> assembliesToScan, bool addIfAlreadyExists) { // 这个集合收集中保存的是处理者。 var concretions = new List<Type>(); // 这个集合保存的是处理者的接口。 var interfaces = new List<Type>(); foreach (var type in assembliesToScan.SelectMany(a => a.DefinedTypes).Where(t => !t.IsOpenGeneric())) { var interfaceTypes = type.FindInterfacesThatClose(openRequestInterface).ToArray(); if (!interfaceTypes.Any()) continue; if (type.IsConcrete()) { concretions.Add(type); } foreach (var interfaceType in interfaceTypes) { interfaces.Fill(interfaceType); } } // 这一部分是将接口与相应的实例者相匹配,然后注册到容器中。 foreach (var @interface in interfaces) { var exactMatches = concretions.Where(x => x.CanBeCastTo(@interface)).ToList(); if (addIfAlreadyExists) { foreach (var type in exactMatches) { services.AddTransient(@interface, type); } } else { if (exactMatches.Count > 1) { exactMatches.RemoveAll(m => !IsMatchingWithInterface(m, @interface)); } // 针对一个接口的注册,注入多个矗立着。 foreach (var type in exactMatches) { services.TryAddTransient(@interface, type); } } if (!@interface.IsOpenGeneric()) { AddConcretionsThatCouldBeClosed(@interface, concretions, services); } } }
那么我们也只需要按照这种形式,先排序在一次注入到容器中。
如下:
private static void ConnectImplementationsToTypesClosing(Type openRequestInterface, IServiceCollection services, IEnumerable<Assembly> assembliesToScan, bool addIfAlreadyExists) { // 这个集合收集中保存的是处理者。 var concretions = new List<Type>(); // 新增一个用于收集排序的集合。 var concretionsOrder = new List<Type>(); // 这个集合保存的是处理者的接口。 var interfaces = new List<Type>(); foreach (var type in assembliesToScan.SelectMany(a => a.DefinedTypes).Where(t => !t.IsOpenGeneric())) { var interfaceTypes = type.FindInterfacesThatClose(openRequestInterface).ToArray(); if (!interfaceTypes.Any()) continue; if (type.IsConcrete()) { if (type.GetCustomAttributes().Any(o => o.GetType().Equals(typeof(EventOrderAttribute)))) concretionsOrder.Add(type); else concretions.Add(type); } foreach (var interfaceType in interfaceTypes) { interfaces.Fill(interfaceType); } } // 排序后加入到服务中。 concretionsOrder = OrderService(concretionsOrder); AddService(concretions); AddService(concretionsOrder); // 排序服务。 List<Type> OrderService(List<Type> typeList) { if (typeList.Any()) return typeList.OrderBy(o => o.GetCustomAttribute<EventOrderAttribute>().Order).ToList(); else return typeList; } // 添加服务。 void AddService(List<Type> typeList) { // 这一部分是将接口与相应的实例者相匹配,然后注册到容器中。 foreach (var @interface in interfaces) { var exactMatches = typeList.Where(x => x.CanBeCastTo(@interface)).ToList(); if (addIfAlreadyExists) { foreach (var type in exactMatches) { services.AddTransient(@interface, type); } } else { if (exactMatches.Count > 1) { exactMatches.RemoveAll(m => !IsMatchingWithInterface(m, @interface)); } // 针对一个接口的注册,注入多个矗立着。 foreach (var type in exactMatches) { services.TryAddTransient(@interface, type); } } if (!@interface.IsOpenGeneric()) { AddConcretionsThatCouldBeClosed(@interface, typeList, services); } } } }
下面是ABC对应321.
[EventOrder(Order = 3)] public class AClHandler : INotificationHandler<EventData> { public Task Handle(EventData notification, CancellationToken cancellationToken) { Console.WriteLine(nameof(AClHandler)); return Task.CompletedTask; } } [EventOrder(Order = 2)] public class BClHandler : INotificationHandler<EventData> { public Task Handle(EventData notification, CancellationToken cancellationToken) { Console.WriteLine(nameof(BClHandler)); return Task.CompletedTask; } } [EventOrder(Order = 1)] public class CClHandler : INotificationHandler<EventData> { public Task Handle(EventData notification, CancellationToken cancellationToken) { Console.WriteLine(nameof(CClHandler)); return Task.CompletedTask; } }
看看结果:
如果去掉排序
第二种先排序再插入容器的方法,或许不适用所有的依赖注入容器,但是原生容器时没有问题的,Autofac应该也没有问题。