博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ASP.NET Core 注册单例方案
阅读量:4035 次
发布时间:2019-05-24

本文共 3977 字,大约阅读时间需要 13 分钟。

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

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

单例设计

public class Singleton
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
constructors = typeof(T).GetTypeInfo().DeclaredConstructors; ConstructorInfo ci = constructors.ToList()[0]; instance = ci.Invoke(new object[] { }) as T; } } } return instance; } } protected Singleton() { IEnumerable
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
{ 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
, 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
();}

这样就可以了吗? 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
(); services.AddScoped
();}

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
跟 Instance 的破事。我们已经完全隐藏了单例模式的实现,这是解决这个问题附带的收获。这个设计是不是彻底实现了 面向接口编程 的规范? 快夸我吧!

转载地址:http://ghzdi.baihongyu.com/

你可能感兴趣的文章
coursesa课程 Python 3 programming Dictionary methods 字典的方法
查看>>
Returning a value from a function
查看>>
coursesa课程 Python 3 programming Functions can call other functions 函数调用另一个函数
查看>>
coursesa课程 Python 3 programming The while Statement
查看>>
course_2_assessment_6
查看>>
coursesa课程 Python 3 programming course_2_assessment_7 多参数函数练习题
查看>>
coursesa课程 Python 3 programming course_2_assessment_8 sorted练习题
查看>>
visca接口转RS-232C接口线序
查看>>
在unity中建立最小的shader(Minimal Shader)
查看>>
1.3 Debugging of Shaders (调试着色器)
查看>>
关于phpcms中模块_tag.class.php中的pc_tag()方法的含义
查看>>
vsftp 配置具有匿名登录也有系统用户登录,系统用户有管理权限,匿名只有下载权限。
查看>>
linux安装usb wifi接收器
查看>>
用防火墙自动拦截攻击IP
查看>>
补充自动屏蔽攻击ip
查看>>
谷歌走了
查看>>
多线程使用随机函数需要注意的一点
查看>>
getpeername,getsockname
查看>>
让我做你的下一行Code
查看>>
浅析:setsockopt()改善程序的健壮性
查看>>