ASP.NET Core 注册单例方案

一个单例是没有公共构造函数的,只能通过静态的 Instance 属性获取,这是单例的标准初衷,一个单例是不想让别人调用它的构造函数的。但是 aspnetcore 中提供的 AddSingleton<TService, TImplementation>() ,只提供了类型,而无法注入对象实例,单实例对象还是要框架深层构造的,这实际上并不是安全的做法。 

如果使用了标准的单例设计方法,则无法由框架直接生成单实例,这就需要使用点小技巧了。 

单例设计

public class Singleton<T> where T : class
{protected static T instance;protected static object locker = new object();public static T Instance{get{if (instance == null){lock (locker){if (instance == null) //防止线程重入{IEnumerable<ConstructorInfo> constructors = typeof(T).GetTypeInfo().DeclaredConstructors;ConstructorInfo ci = constructors.ToList()[0];instance = ci.Invoke(new object[] { }) as T;}}}return instance;}}protected Singleton(){IEnumerable<ConstructorInfo> constructors = typeof(T).GetTypeInfo().DeclaredConstructors;constructors.ActionForeach(c => {if (c.IsPublic){throw new InvalidOperationException("禁止从public构造函数中实例化!");}});}}

以上要点有三个:

1 使用次 if 判断和 locker 保护,防止线程重入时构造多个实例,确保唯一性;

2 在基类中,使用反射调用子类的构造函数完成实例化;

3 基类的构造函数是受保护的,它会检查,禁止子类的公共构造函数调用。

第3条隐藏了一个知识点:子类在初始化实例时,默认会调用基类的构造函数。

因为以上三条机制确保了单例的唯一性,所以反射只会在第一次使用时调用,对性能的影响可以忽略不计。

 

单例的使用

使用起来非常简单

public class Root:Singleton<Root>
{protected Root() { }public void DoSomething()
{Console.WriteLine("I feel very happy, cus I'm unique.");     } }

假设有一个类,叫做Root,由于业务需要,它必须要以单例实现,顾名思义,根只能有一个。

如果不重写 protected 构造函数,则可能发生以下情况:

Root root = new Root(); 

这是被禁止的,万一忘记写了 protected Root(){} 这一行,就会抛出异常,可见在  Singleton<T> 中进行判断,是十分有必要的。

在业务需要的地方,就可以用通常使用的单例模式来调用 :

Root.Instance.DoSomething();

至此,单例模式完成。

一个真实的业务场景

假设主模块是 Main.dll, 它是一个 aspnetcore 工程, 它调用了 A.dll 作为它的类库。由于某种原因,Root 类必须要在 Main 工程中实现,而不能放到 A 工程中。但是A工程要用到 Root 的方法。如果让 A 工程来引用  Main 工程,这就是反向引用了,这会形成循环引用,是不被允许的。

所以我们可以把 Root 的方法抽象出接口来,注册到 aspnet 框架中,我们可以这样做:

在 A.csproj 中,暴露接口给自己调用:

public interface IRoot
{void DoSomething();
}

在 Main.csproj 中实现这个接口:

class Root:Singleton<Root>, IRoot
{protected Root() { }public void DoSomething()
{Console.WriteLine("I feel very happy, cus I'm unique.");}
}

然后就是注册了,在 Main.csproj 工程的 Startup.cs 文件的  ConfigureServices 方法中进行注册:

public void ConfigureServices(IServiceCollection services)
{services.AddControllersWithViews();services.AddSession();//...其他代码services.AddSingleton<IRoot, Root>();
}

这样就可以了吗? no no no , 这样肯定是不行的,因为前面我们设计的单例,是这样使用的: Root.Instance.DoSomething();  而不是 Root root = new Root(); 

这会导致注入失败,因为框架注册要求 Root 有一个公共无参构造函数,况且它并不知道Root有个静态属性 Instance ,而且只能通过 Instance 来访问。

单例注入方案

下面说到正题了,既然不能直接注册单例,我们可以使用一个中间接口来注入,这个中间接口提供了单例的访问对象,而且它拥有一个没有写出来的默认公共构造方法,它的构造函数,与 Root 类的构造函数,毫无关系,所以可以由框架创建。

在 A.csproj 工程中定义:

public interface IRootProvider
{IRoot Root { get; }
}

在 Main.csproj 中实现:

public class RootProvider : IRootProvider
{public IRoot Root { get => SomeNamespace.Root.Instance; }
}

然后再注册:

public void ConfigureServices(IServiceCollection services)
{services.AddControllersWithViews();services.AddSession();//...其他代码services.AddScoped<IRootProvider, RootProvider>();services.AddScoped<ISomeService, SomeService>();
}

look, 我们已经不需要使用 AddSingleton 来注入了,因为  RootProvider 不必是单实例的。 

在 A.csproj  中愉快地使用:

public class SomeService:ISomeService
{private IRoot root;public SomeService(IRootProvider rootProvider){this.root = rootProvider.Root;}public void SomeBusiness(){this.root.DoSomething();}
}
SomeService 是在主模块中注入的服务,在主模块中构造,构造的前提是要有一个 IRootProvider 的实例,同时  IRootProvider 要在它的前一行注册,这个很重要。 
到这里,本文就结束了,但我还是想啰嗦一下:在A中使用的 IRoot root 一点也看不出单例的痕迹,因为 IRoot 只是一个业务接口;同时 IRootProvider 也只提供了 IRoot 的 get 方法, 所以对于 A 模块的开发者,完全不必知道 Root 的存在,更不必知道什么 Singleton<Root> 跟 Instance 的破事。我们已经完全隐藏了单例模式的实现,这是解决这个问题附带的收获。这个设计是不是彻底实现了 面向接口编程 的规范? 快夸我吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/308090.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PHP做二次开发:ThinkCMF门户应用安装

使用工具&#xff1a;phpEnv 具体步骤&#xff1a; 1.获取门户应用portal源码 2.安装portal代码 3.执行portal数据库文件 4.安装并启用前台模板 5.导入后台管理菜单 第一步&#xff1a;获取门户应用portal源码 1.打开官方网站http://kancloud.cn/thinkcmf/faq/1005840&#xf…

vector的逆序输出(神奇的vector)

一&#xff1a;直接上代码&#xff08;逆序输出&#xff09; #include<bits/stdc.h> using namespace std; int main(){vector<int> v;for(int i 0; i < 5; i){v.push_back(i); }reverse(v.begin(),v.end());for(int i 0; i < 5; i){cout << v[i] &…

初识ABP vNext(6):vue+ABP实现国际化

点击上方蓝字"小黑在哪里"关注我吧语言选项语言切换注意前言上一篇介绍了ABP扩展实体&#xff0c;并且在前端部分新增了身份认证管理和租户管理的菜单&#xff0c;在实现这两个功能模块前&#xff0c;先来解决一下界面文字国际化的问题。开始国际化&#xff08;简称 …

『软件工程1』详解软件是什么

软件基本概念一、什么是产品二、软件的双重角色三、软件的涵义及特征四、软件应用五、软件危机六、软件神话一、什么是产品 1、从用户的角度 产品实际上就是信息&#xff0c;以某种方式使得用户世界更加美好 2、从软件工程师的角度 产品实际上就是软件 二、软件的双重角色 1…

200. 岛屿数量025(BFS详解)

二&#xff1a;思路 1.这里我们使用的是BFS(广度优先搜索遍历) 2.当我们遇到一个岛屿&#xff08;‘1’&#xff09;的时候&#xff0c;我们就对其的左右四边进行广度遍历 并且标记已经访问过的结点。 3.那么我们每次遇到一个1开始广度遍历那就证明我们发现了一个岛 三:上码 …

进击吧! Blazor 第一期

Blazor 是一个 Web UI 框架&#xff0c;可通过 WebAssembly 在任意浏览器中运行 .Net 。Blazor 旨在简化快速的单页面 .Net 浏览器应用的构建过程&#xff0c;它虽然使用了诸如 CSS 和 HTML 之类的 Web 技术&#xff0c;但它使用 C&#xff03;语言和 Razor 语法代替 JavaScrip…

『软件工程2』详解软件工程和软件过程模型

文章目录一、软件工程的定义1、Fritz Bauer在NATO上给出的定义2、Barry Boehm3、IEEE在软件工程术语汇编中的定义二、软件工程的层次1、软件工程三个要素2、软件工程的层次——图解3、软件工程的层次——逐一分析三、软件过程的三个阶段1、定义阶段——“做什么”2、开发阶段—…

利用vector实现一对一(pair<int,int>)

一&#xff1a;前言 我们知道有一对一的STL容器有map容器&#xff0c;但是map容器中的按键值排序和不允许由重复的元素&#xff0c;现在&#xff0c;我们可以利用 vector<pair<int,int> >来实现一对一&#xff0c;但其没有排序可以允许有重复的元素 二&#xff1…

没有docker,谈什么微服务架构?

新的互联网技术时代已经来临了&#xff0c;容器、Kubernetes、DevOps、微服务、云原生代表着技术前进的方向&#xff0c;.NET Core微服务Docker&#xff0c;亦是当下最优解决方案(低调点&#xff0c;几乎没有之一)&#xff01;有点自夸&#xff1f;作为专注于.NET领域十多年的老…

jsp中为什么在跳转500页面的时候 图片加载不出来

一&#xff1a;问题描述 我们在自定义500错误页面的时候&#xff0c;在当前的错误页面是可以加载出来图片的&#xff0c;但在其他页面跳转500错误页面的时候&#xff0c;获取不到错误页面的图片 二&#xff1a;问题解决&#xff08;图片加载不出来&#xff0c;多半是路径出了…

『软件工程3』你应该知道的三种原型实现模型:抛弃式、演化式、增量式

三种原型实现模型一、抛弃式原型开发二、演化式原型开发三、增量式原型开发一、抛弃式原型开发 1、定义&#xff1a;验证和澄清系统的需求描述&#xff0c;重新构造系统。 2、流程图 3、典型例子 开发者与客户进行沟通交流&#xff0c;之后获取到客户的需求&#xff0c;于是…

『软件工程4』一文了解软件项目管理中的4P

软件项目管理中的4P一、项目管理的重要性和定义1、重要性&#xff08;两个阶段&#xff09;2、软件项目管理的定义二、管理四要素4P1、管理的四要素(4P)2、软件项目中影响最终结果的要素3、项目管理关心的问题三、项目参与者类型(people)四、项目小组结构(people)1、项目的三种…

8-1 回溯法实验报告 (15 分)(思路+详解)

一&#xff1a;题目 给定k个正整数&#xff0c;用算术运算符&#xff0c;-&#xff0c;&#xff0c;/ 将这k个正整数连接起来&#xff0c;是最终的得数恰为m。 如果有多组满足要求的表达式&#xff0c;只要输出一组&#xff0c;每一步算式用分号隔开。 如果无法得到m&#xff…

TypeScript 4.0 发布

喜欢就关注我们吧&#xff01;整体看来&#xff0c;此版本在兼容性方面没有特别大的变化。因为 TypeScript 团队表示新版本继续使用与过去版本相似的版本控制模型&#xff0c;可将 4.0 视作 3.9 的延续升级版本。而且他们也一直在追求不牺牲主要灵活性的情况下&#xff0c;提供…

『软件工程5』详解软件项目管理之软件的度量

软件项目管理——软件的度量一、度量的目的1、引例2、度量的目的3、度量的作用二、测量、度量和指标区别1、引例2、测量、度量和指标的区别3、思考题三、过程度量和项目度量1、过程2、项目四、度量的方式1、物理世界中的测量2、软件测量五、面向规模的度量1、定义2、有用度量的…

7-2 旅行售货员 (10 分)(TSP问题思路加详解)

一题目 某售货员要到若干城市去推销商品&#xff0c;已知各城市之间的路程(或旅费)。他要选定一条从驻地出发&#xff0c;经过每个城市一遍&#xff0c;最后回到驻地的路线&#xff0c;使总的路程&#xff08;或总旅费&#xff09;最小。 输入格式: 第一行为城市数n 下面n行…

从零开始内建你的安全测试流程

一、 安全测试的意义安全问题&#xff0c;没发生的时候我们可以侥幸&#xff0c;一旦发生生产安全问题&#xff0c;对很多公司来说可能就是黑天鹅事件了。平台的安全&#xff0c;是我们测试中不可舍弃的一环&#xff0c;而且需要长期持续的关注。二、 从哪里入手很多公司没有专…

『软件测试1』你需要了解的软件测试基础知识

软件测试基础一、 软件缺陷的概述1、什么是软件缺陷2、软件缺陷的类型3、软件缺陷的案例4、软件缺陷的产生原因5、软件缺陷的分类6、软件缺陷的处理流程7、多学一招&#xff1a;缺陷报告&#xff08;由测试人员完成&#xff09;8、常见软件缺陷管理工具9、修复软件缺陷的成本二…

直连路由和静态路由(实验)

一:概念 1:直连路由概念 根据 路由 器学习路由信息、生成并维护 路由表 的方法包括直连路由(Direct)、 静态路由 (Static)和 动态路由 (Dynamic)。直连路由&#xff1a;路由器接口所连接的子网的路由方式称为直连路由&#xff1b; 非直连路由&#xff1a;通过路由协议从别的路…

使用 GB28181.Solution + ZLMediaKit + MediaServerUI 进行摄像头推流和播放

使用 GB28181.Solution ZLMediaKit MediaServerUI 进行摄像头推流和播放独立观察员 2020 年 8 月 25 日一、摄像机 GB28181 配置打开 国标 28181 配置页面&#xff0c;勾上 “接入使能”&#xff1a;打钩的是重要的配置信息&#xff0c;主要就是 SIP 服务器的信息和摄像头自己…