• 第15章 注销器实现原理与触发


    1 原理

    Redis数据库量:以延时操作为基础,把数据库中的数据缓存到Redis数据库中,以减少用户对数据库进行直接访问的频率,从而提高程序的渲染显示性能。在使用Redis数据库,为页面的渲染显示提供数据时,由于Redis数据库所缓存数据的延时性,即页面所渲染显示的数据是几分钟以前的,1个用户最新向数据库提交的数据,是不能立即被其他的用户看到的;因为。为了解决这个问题nopCommerce开发者为我们提供了触发注销器的解决方案,原理如下图所示:

    2 准备工作

    2.1 OptenTypeExtension

    ///

        /// 【继承于泛型扩展】

        ///

        ///

        /// 摘要:

        ///     该类中的方法用于验证1个指定继承目标类/接口,是否继承于1个指定的泛型类/接口。

        /// 说明:

        ///     由于“IsAssignableFrom”只能用于验证1个指定类/接口是否继承于1个指定的非泛型的类/接口,如果用于验证泛型的类/接口,则会一直返回:false,

        /// 所以定义了该扩展类,以实现对泛型的类/接口继承的验证操作。

        ///

        public static class OptenTypeExtension

        {

            /// name="openGeneric">1个被继承的指定泛型类/接口的类型实例。

            /// name="type">1个指定继承目标类/接口的类型实例。

            ///

            /// 【继承于泛型?】

            ///

            /// 摘要:

            ///     获取1个值false(不继承于泛型)/true(继承于泛型),该值指示1个指定类/接口是否继承于1个指定的泛型类/接口。

            ///  说明:

            ///     由于“IsAssignableFrom”只能用于验证1个指定类/接口是否继承于1个指定的非泛型的类/接口,如果用于验证泛型的类/接口,则会一直返回:false。

            ///

            ///

            /// 返回:

            ///     1个值false(不继承于泛型)/true(继承于泛型)。

            ///

            ///

            public static bool DoesTypeImplementOpenGeneric(this Type openGeneric, Type type)

            {

                try

                {

                    //获取1个被继承的指定泛型类/接口的类型实例。

                    var genericTypeDefinition = openGeneric.GetGenericTypeDefinition();

                    //依次对继承目标类/接口,中的所有被继承的类/接口的类型实例进行验证,只要所继承的目标类/接口中有1个是泛型的就返回:true。

                    foreach (var implementedInterface in type.FindInterfaces((objType, objCriteria) => true, null))

                    {

                        //如果被继承的类/接口的类型实例,不是泛型的,则继续验证继承目标类/接口中其它的被继承的类/接口的类型实例。

                        if (!implementedInterface.IsGenericType)

                            continue;

                        //如果被继承的类/接口的类型实例是泛型的,就立即返回:true,因为已经可以确认目标类/接口继承了指定泛型类/接口。

                        if (genericTypeDefinition.IsAssignableFrom(implementedInterface.GetGenericTypeDefinition()))

                            return true;

                    }

                    return false;

                }

                catch

                {

                    return false;

                }

            }

        }

    2.2 ServiceProviderExtension

      public static class ServiceProviderExtension

        {

            public static ServiceProvider ServiceProvider { get; set; }

        }

    3 注销器接口(IConsumer)及其具体实现类的定义实现

    3.1 IConsumer

    namespace Linkage.Events

    {

        /// name="T">泛型类型实例(1个指定类的类型实例)。

        ///

        /// 【注销器--接口】

        /// 摘要:

        ///    如果1指类型的实例发生改变,需要对定义在任意命名空间的该实例,进行更新,

        /// 那么触发器(EventPublisher)就会通过所有的(继承于Consumer接口的)具体实现类的实例调用该接口中的同名方法,来对定义在任意命名空间的该实例,进行更新。

        /// 说明:

        ///     nopCommerce开发者的定义该方法的主要意图是:在有数据的增/删/修操作发生时,通过触发器(EventPublisher),来对缓存在Redis数据库中的数据进行即时的更新,

        ///  从而使页面始在渲染显示时,始终能够以最新的缓存数据,呈现给其他的用户,

        ///

        public interface IConsumer<T>

        {

            #region 方法

            /// name="eventMessage">1个指定指定类(T)的实例。

            ///

            /// 【异步触发句柄】

            ///

            /// 摘要:

            ///     如果1指类型的实例发生改变,需要对定义在任意命名空间的该实例,进行更新,

            /// 那么触发器(EventPublisher)就会通过所有的(继承于Consumer接口的)具体实现类的实例调用该方法,来对定义在任意命名空间的该实例,进行更新。

            /// 说明:

            ///     nopCommerce开发者的定义该方法的主要意图是:在有数据的增/删/修操作发生时,通过触发器(EventPublisher),来对缓存在Redis数据库中的数据进行即时的更新,

            ///  从而使页面始在渲染显示时,始终能够以最新的缓存数据,呈现给其他的用户。

            ///

            ///

            Task HandleEventAsync(T eventMessage);

            #endregion

        }

    }

    3.2 Linkage.Models.DateTimeConsumerModel

    using Linkage.Events;

    namespace Linkage.Models

    {

        ///

        /// 【时间注销器--类】

        /// 摘要:

        ///    如果时间类型(DateTime)的实例发生改变时,需要对定义在Linkage.Models.DateTimeConsumerModel命名空间的时间类型(DateTime)的实例,进行更新,

        /// 那么触发器(EventPublisher)就会调用当前类中的“HandleEventAsync”方法,来对定义在Linkage.Models.DateTimeConsumerModel命名空间的时间类型(DateTime)的实例,进行更新。

        ///

        public class DateTimeConsumerModel : IConsumer

        {

            #region 属性

            ///

            /// 【时间】

            ///

            /// 摘要:

            ///     获取/设置触发器更新操作完成后的时间值(需要被更新的时间类型(DateTime)的实例)。

            ///

            ///

            public static DateTime DateTime { get; set; }

            #endregion

            #region 方法

            /// name="eventMessage">时间类型(DateTime)的实例。

            ///

            /// 【异步触发句柄】

            ///

            /// 摘要:

            ///     如果时间类型(DateTime)的实例发生改变,需要对定义在Linkage.Models.DateTimeConsumerModel 命名空间的时间类型(DateTime)的实例,进行更新,

            /// 那么触发器(EventPublisher)就会调用当前类中的“HandleEventAsync”方法,来对定义在Linkage.Models.DateTimeConsumerModel 命名空间的时间类型(DateTime)的实例,进行更新。

            ///

            ///

            public Task HandleEventAsync(DateTime eventMessage)

            {

                //把触发器更新操作完成后的时间值,存储到当前类的静态属性成员(需要被更新的时间类型(DateTime)的实例)中。

                DateTime = eventMessage;

                return Task.CompletedTask;

            }

            #endregion

        }

    }

    3.3 Linkage.Controllers.PrincipleController.DateTimeConsumer

    using Linkage.Events;

    using Linkage.Models;

    using Microsoft.AspNetCore.Mvc;

    namespace Linkage.Controllers

    {

        public class PrincipleController : Controller

        {

            public static DateTime _dateTime = DateTime.Now;

            private static bool _isUpdate = false;

            private readonly IEventPublisher _eventPublisher;

            public PrincipleController(IEventPublisher eventPublisher)

            {

                _eventPublisher = eventPublisher;

            }

            public async Task Index()

            {

                if (_isUpdate)

                {

                    await _eventPublisher.PublishAsync(_dateTime.AddYears(10));

                }

                else

                {

                    DateTimeConsumer.DateTime = _dateTime;

                    DateTimeConsumerModel.DateTime = _dateTime;

                }

                return View();

            }

            public IActionResult Upate()

            {

                _isUpdate = _isUpdate == false ? true : false;

                return RedirectToAction("Index");

            }

            ///

            /// 【时间注销器--类】

            /// 摘要:

            ///    如果时间类型(DateTime)的实例发生改变时,需要对定义在Linkage.Controllers.PrincipleController.DateTimeConsumer命名空间的时间类型(DateTime)的实例,进行更新,

            /// 那么触发器(EventPublisher)就会调用当前类中的“HandleEventAsync”方法,来对定义在Linkage.Controllers.PrincipleController.DateTimeConsumer命名空间的时间类型(DateTime)的实例,进行更新。

            ///

            public class DateTimeConsumer : IConsumer

            {

                #region 属性

                ///

                /// 【时间】

                ///

                /// 摘要:

                ///     获取/设置触发器更新操作完成后的时间值(需要被更新的时间类型(DateTime)的实例)。

                ///

                ///

                public static DateTime DateTime { get; set; }

                #endregion

                #region 方法

                /// name="eventMessage">时间类型(DateTime)的实例。

                ///

                /// 【异步触发句柄】

                ///

                /// 摘要:

                ///     如果时间类型(DateTime)的实例发生改变,需要对定义在Linkage.Controllers.PrincipleController.DateTimeConsumer命名空间的时间类型(DateTime)的实例,进行更新,

                /// 那么触发器(EventPublisher)就会调用当前类中的“HandleEventAsync”方法,来对定义在Linkage.Controllers.PrincipleController.DateTimeConsumer命名空间的时间类型(DateTime)的实例,进行更新。

                ///

                ///

                public Task HandleEventAsync(DateTime eventMessage)

                {

                    //把触发器更新操作完成后的时间值,存储到当前类的静态属性成员(需要被更新的时间类型(DateTime)的实例)中。

                    DateTime = eventMessage;

                    return Task.CompletedTask;

                }

                #endregion

            }

        }

    }

    4 触发器接口(IEventPublisher)及其具体实现类(EventPublisher)的定义实现

    4.1 IEventPublisher

    namespace Linkage.Events

    {

        ///

        /// 【触发器--接口】

        ///

        /// 摘要:

        ///     如果1指类型(“T”)的实例发生改变,需要对定义在任意命名空间同1类型(“T”)的实例,进行更新,

        /// 继承该接口的具体实现类中的同名方法就会通过所有的(继承于Consumer接口的)具体实现类的实例,来对定义在任意命名空间的同1类型(“T”)的实例,进行更新。

        /// 说明:

        ///     nopCommerce开发者的定义该方法的主要意图是:在有数据的增/删/修操作发生时,通过该方法,来对缓存在Redis数据库中的数据进行即时的更新,

        ///  从而使页面始在渲染显示时,始终能够以最新的缓存数据,呈现给其他的用户。

        ///

        public partial interface IEventPublisher

         {

            #region 方法

            /// name="TEvent">泛型类型实例(这里特指:IConsumer接口中的“T”(“TEvent”=“T”))。

            /// name="@event">IConsumer接口中“T”(“TEvent”=“T”)的实例

            ///

            /// 【异步触发】

            ///

            /// 摘要:

            ///     如果1指类型(“TEvent”=“T”)的实例发生改变,需要对定义在任意命名空间同1类型(“TEvent”=“T”)的实例,进行更新,

            /// 该方法就会通过所有的(继承于IConsumer接口的)具体实现类的实例,来对定义在任意命名空间的同1类型(“TEvent”=“T”)的实例,进行更新。

            /// 说明:

            ///     nopCommerce开发者的定义该方法的主要意图是:在有数据的增/删/修操作发生时,通过该方法,来对缓存在Redis数据库中的数据进行即时的更新,

            ///  从而使页面始在渲染显示时,始终能够以最新的缓存数据,呈现给其他的用户。

            ///

            ///

            Task PublishAsync<TEvent>(TEvent @event);

            #endregion

        }

    }

    4.2 EventPublisher

    using Linkage.Extensions;

    namespace Linkage.Events

    {

        ///

        /// 【触发器--类】

        ///

        /// 摘要:

        ///     如果1指类型(“T”)的实例发生改变,需要对定义在任意命名空间同1类型(“T”)的实例,进行更新,

        /// 继承该具体实现类中的方法就会通过所有的(继承于Consumer接口的)具体实现类的实例,来对定义在任意命名空间的同1类型(“T”)的实例,进行更新。

        /// 说明:

        ///     nopCommerce开发者的定义该方法的主要意图是:在有数据的增/删/修操作发生时,通过该方法,来对缓存在Redis数据库中的数据进行即时的更新,

        ///  从而使页面始在渲染显示时,始终能够以最新的缓存数据,呈现给其他的用户。

        ///

        public partial class EventPublisher : IEventPublisher

        {

            #region 方法

            /// name="TEvent">泛型类型实例(这里特指:IConsumer接口中的“T”(“TEvent”=“T”))。

            /// name="@event">Consumer接口中“T”(“TEvent”=“T”)的实例

            ///

            /// 【异步触发】

            ///

            /// 摘要:

            ///     如果1指类型(“TEvent”=“T”)的实例发生改变,需要对定义在任意命名空间同1类型(“TEvent”=“T”)的实例,进行更新,

            /// 该方法就会通过所有的(继承于IConsumer接口的)具体实现类的实例,来对定义在任意命名空间的同1类型(“T”)的实例,进行更新。

            /// 说明:

            ///     nopCommerce开发者的定义该方法的主要意图是:在有数据的增/删/修操作发生时,通过该方法,来对缓存在Redis数据库中的数据进行即时的更新,

            ///  从而使页面始在渲染显示时,始终能够以最新的缓存数据,呈现给其他的用户。

            ///

            ///

    s>

            public virtual async Task PublishAsync<TEvent>(TEvent @event)

            {

                //从依赖注入到.Net(Core)6框架内置容器中,获取所有继承于IConsumer接口具体实现类的实例。

                List> _eventConsumerList = ServiceProviderExtension.ServiceProvider.GetServices>().ToList();

                //对所有具体实现类的实例,依次执行“HandleEventAsync”方法,从而实现对定义在任意命名空间的同1类型(“TEvent”=“T”)的实例,进行更新操作

                foreach (var consumer in _eventConsumerList)

                {

                    try

                    {

                        //对1指定具体实现类中的同1类型(“TEvent”=“T”)的实例,进行更新操作。

                        await consumer.HandleEventAsync(@event);

                    }

                    catch (Exception exception)

                    {

                        Console.WriteLine(exception.Message);

                    }

                }

            }

            #endregion

        }

    }

    5 Program.cs

    //把继承于“IConsumer<>”泛型接口的所有具体实现类的实例,依赖注入到.Net(Core)6框架内置容器中。

    //获取所有继承于“IConsumer<>”泛型接口的所有具体实现类的类型实例。

    var _consumerList = AppDomain.CurrentDomain.GetAssemblies()

        .SelectMany(a => a.GetTypes().Where(t => typeof(IConsumer<>).DoesTypeImplementOpenGeneric(t)))

        .ToList();

    //通过所有具体实现类的类型实例,依次实例化所有具体实现类,并把这些实例,依赖注入到.Net(Core)6框架内置容器中。

    foreach (var consumer in _consumerList)

    {

        var interfaces = consumer.FindInterfaces((type, criteria) => type.IsGenericType && ((Type)criteria).IsAssignableFrom(type.GetGenericTypeDefinition()), typeof(IConsumer<>));

        foreach (var findInterface in interfaces)

        {

            builder.Services.AddTransient(findInterface, consumer);

        }

    }

    //把“ EventPublisher”类的实例,依赖注入到.Net(Core)6框架内置容器中。

    builder.Services.AddSingleton();

    //通过AddRazorRuntimeCompilation依赖注入中间件实现页面修改热加载(Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation)。

    builder.Services

        .AddControllersWithViews()

        .AddRazorRuntimeCompilation();

    //注意:从依赖注入到.Net(Core)6框架内置容器中,获取“ServiceProvider”实例,必须定义在最后,否则“ServiceProvider.GetServices”方法将不能获取类的实例,

    //因为类的实例还没有依赖注入到.Net(Core)6框架内置容器中。

    ServiceProviderExtension.ServiceProvider = builder.Services.BuildServiceProvider();

    6 Principle\Index.cshtml

    @{

        ViewData["Title"] = "注销器实现原理与触发";

    }

    @{

        <h1 class="text-success">当前时间(TestController):@Linkage.Controllers.PrincipleController._dateTimeh1>

        <h1 class="text-success">当前时间(Linkage.Models):@Linkage.Controllers.PrincipleController._dateTimeh1>

        if (Linkage.Controllers.PrincipleController.DateTimeConsumer.DateTime != Linkage.Controllers.PrincipleController._dateTime

            && Linkage.Models.DateTimeConsumerModel.DateTime != Linkage.Controllers.PrincipleController._dateTime)

        {

            <hr/>

            <h1 class="text-warning">当前时间(TestController):@Linkage.Controllers.PrincipleController.DateTimeConsumer.DateTimeh1>

            <h1 class="text-warning">当前时间(Linkage.Models):@Linkage.Models.DateTimeConsumerModel.DateTimeh1>

        }

    }

    <hr />

    @{

        if (Linkage.Controllers.PrincipleController.DateTimeConsumer.DateTime == Linkage.Controllers.PrincipleController._dateTime

            && Linkage.Models.DateTimeConsumerModel.DateTime == Linkage.Controllers.PrincipleController._dateTime)

        {

            <h1 class="text-success">更新时间:@Linkage.Controllers.PrincipleController._dateTimeh1>

        }

        else

        {

            <h1 class="text-danger">更新时间(TestController):@Linkage.Controllers.PrincipleController.DateTimeConsumer.DateTimeh1>

            <h1 class="text-danger">更新时间(Linkage.Models):@Linkage.Models.DateTimeConsumerModel.DateTimeh1>

        }

    }

    <hr />

    <a href="/Principle/Upate" class="btn btn-primary">更 新a>

    对以上功能更为具体实现和注释见:221027_15Linkage(注销器实现原理与触发)。

     

  • 相关阅读:
    python的opencv操作记录(十)——图像融合
    基于wxpython和wxformbuilder实现的简单2-8-10-16进制转换器,并用pyinstaller打包
    基于java的出租车预约网站
    聊天、会议、多媒体一体化:多平台支持的即时通讯系统 | 开源日报 No.44
    新型人工智能技术让机器人的识别能力大幅提升
    常量指针和指针常量
    CANoe-vTESTstudio之State Diagram编辑器(功能介绍)
    Centos7下zabbix安装与部署,设置中文(保姆级图文)【网络工程】
    uniAPP小程序 子组件使用watch不生效,H5正常,小程序不正常(其实是子组件model选项的问题)
    基于seata的分布式事务
  • 原文地址:https://blog.csdn.net/zhoujian_911/article/details/127556729