前言

asp.net core 中,微软提供了一套默认的依赖注入实现,该实现对应的包为:microsoft.extensions.dependencyinjection,我们可以通过查看其对应的开源仓库看一下它的具体实现。基于该实现,我们不必显式创建我们的服务对象,可以将其统一注入到 serviceprovider 中进行集中维护,使用的时候直接在该对象中获取即可。让我们在编写业务逻辑时,不用太关注对象的创建和销毁。这也是为什么现在有些最佳实践中建议不要过多使用 new 的方式来获取对象。在本文中,我们将一起了解一下如何实现一个自己的 serviceprovider

自己动手,丰衣足食

为了方便区分,我这里自定义定义的类叫:servicelocator,其功能与官方的 serviceprovider 类似。

基本实现

首先,我们需要定义一个简单的服务发现接口,用于约束上层具体的实现,示例代码如下所示:

public interface iservicelocator
{
    void addservice<t>();
    t getservice<t>();
}

接着,我们定义一个继承上述接口的具体实现类,示例代码如下所示:

public class servicelocator : iservicelocator
{
    private readonly idictionary<object, object> services;

    public servicelocator()
    {
        services = new dictionary<object, object>();
    }

    public void addservice<t>()
    {
        services.tryadd(typeof(t), activator.createinstance<t>());
    }

    public t getservice<t>()
    {
        try
        {
            return (t)services[typeof(t)];
        }
        catch (keynotfoundexception)
        {
            throw new applicationexception("the requested service is not registered");
        }
    }
}

这样,我们就实现了一个最基本的服务发现类,通过该类,我们可以将我们的多个服务进行集中管理。这里我为了方便,模拟了 3 个服务类用于注册,示例代码如下所示:

public interface iservice
{
    void sayhello();
}
public class servicea : iservice
{
    public void sayhello()
    {
        console.writeline("hello,i'm from a");
    }
}
public class serviceb : iservice
{
    public void sayhello()
    {
        console.writeline("hello,i'm from b");
    }
}
public class servicec : iservice
{
    public void sayhello()
    {
        console.writeline("hello,i'm from c");
    }
}

使用方式就很简单了,如下所示:

class program
{
    static void main(string[] args)
    {
        iservicelocator locator = new servicelocator();
        locator.addservice<servicea>();
        locator.addservice<serviceb>();
        locator.addservice<servicec>();


        locator.getservice<servicea>().sayhello();
        locator.getservice<serviceb>().sayhello();
        locator.getservice<servicec>().sayhello();
    }
}

程序运行效果如下图所示:

程序看起来运行不错,结果也符合我们的预期。但是稍微有点工作经验的朋友就会发现上述的实现是有很多潜在问题的。对于 iservicelocator 的实例,我们一般会以单例模式来进行使用,这就会设计到线程安全的委托,所以我们的服务列表必须要是线程安全的。此外,如果我们需要注册的服务过多,通过上述方式来进行注册的话会加到系统开销,因为我们的服务一旦注册进去就会立刻被初始化,从而耗费不必要的系统内存,所以我们应该让其实例化推迟,在使用的时候才进行实例化操作。下面我们对上述问题一一进行改进。

单例模式

单例模式是一种最简单也是使用最频繁的设计模式,单例模式本身也有很多形式,感兴趣的可以查看我之前的博文:设计模式系列 – 单例模式,这里,我采用 线程安全 方式来修改我们的 servicelocator,此外,我们还需要将我们的服务集合类修改为线程安全类型。所以,整个修改完毕后,示例代码如下所示:

public class servicelocator : iservicelocator
{
    private static servicelocator _instance;

    private static readonly object _locker = new object();

    public static servicelocator getinstance()
    {
        if (_instance == null)
        {
            lock (_locker)
            {
                if (_instance == null)
                {
                    _instance = new servicelocator();
                }
            }
        }

        return _instance;
    }

    private readonly idictionary<object, object> services;

    private servicelocator()
    {
        services = new concurrentdictionary<object, object>();
    }

    public void addservice<t>()
    {
        services.tryadd(typeof(t), activator.createinstance<t>());
    }

    public t getservice<t>()
    {
        try
        {
            return (t)services[typeof(t)];
        }
        catch (keynotfoundexception)
        {
            throw new applicationexception("the requested service is not registered");
        }
    }
}

延迟加载

要想让所有的注册的服务支持懒加载,我们需要引入一个新的集合,这个新的集合是用于存储我们相应的实例对象,在注册的时候我们只记录注册类型,在需要访问到相应的服务时,我们只需要在这个实例集合列表中访问,如果发现我们需要的服务还未被实例化,那我们再进行实例化,然后将该实例化对象存储起来并返回。对于用哪种数据结构来存,我们可以采用多种数据结构,我这里仍然采用字典来存储,示例代码如下所示:

public class servicelocator : iservicelocator
{
    private static servicelocator _instance;

    private static readonly object _locker = new object();

    public static servicelocator getinstance()
    {
        if (_instance == null)
        {
            lock (_locker)
            {
                if (_instance == null)
                {
                    _instance = new servicelocator();
                }
            }
        }
        return _instance;
    }

    private readonly idictionary<type, type> servicestype;
    private readonly idictionary<type, object> instantiatedservices;

    private servicelocator()
    {
        servicestype = new concurrentdictionary<type, type>();
        instantiatedservices = new concurrentdictionary<type, object>();
    }

    public void addservice<t>()
    {
        servicestype.add(typeof(t), typeof(t));
    }

    public t getservice<t>()
    {
        if (!instantiatedservices.containskey(typeof(t)))
        {
            try
            {
                constructorinfo constructor = servicestype[typeof(t)].getconstructor(new type[0]);
                debug.assert(constructor != null, "cannot find a suitable constructor for " + typeof(t));
                t service = (t)constructor.invoke(null);

                instantiatedservices.add(typeof(t), service);
            }
            catch (keynotfoundexception)
            {
                throw new applicationexception("the requested service is not registered");
            }
        }

        return (t)instantiatedservices[typeof(t)];
    }
}

自匹配构造

上面的所有改进都支持无参构造函数的服务,但是对于有参构造函数的服务注册,我们定义的 服务提供者就不满足的,因为上述的反射方式是不支持有参构造函数的。对于这种情况我们有两种解决办法。第一种是将服务的初始化放到最上层,然后 servicelocator 通过一个 fun 的方式来获取该示例,并存储起来,我们称之为 显示创建。第二种方式依然是通过反射方式,只是这个反射可能会复杂一下,我们称之为 隐式创建。我们分别对于这两个实现方式进行代码示例。

  • 显示构造
public interface iservicelocator
{
    void addservice<t>();

    //新增接口
    void addservice<t>(func<t> implementation);

    t getservice<t>();
}

public class servicelocator : iservicelocator
{
    private static servicelocator _instance;

    private static readonly object _locker = new object();

    public static servicelocator getinstance()
    {
        if (_instance == null)
        {
            lock (_locker)
            {
                if (_instance == null)
                {
                    _instance = new servicelocator();
                }
            }
        }
        return _instance;
    }

    private readonly idictionary<type, type> servicestype;
    private readonly idictionary<type, object> instantiatedservices;

    private servicelocator()
    {
        servicestype = new concurrentdictionary<type, type>();
        instantiatedservices = new concurrentdictionary<type, object>();
    }

    public void addservice<t>()
    {
        servicestype.add(typeof(t), typeof(t));
    }

    //新增接口对应的具体实现
    public void addservice<t>(func<t> implementation)
    {
        servicestype.add(typeof(t), typeof(t));
        var done = instantiatedservices.tryadd(typeof(t), implementation());
        debug.assert(done, "cannot add current service: " + typeof(t));
    }

    public t getservice<t>()
    {
        if (!instantiatedservices.containskey(typeof(t)))
        {
            try
            {
                constructorinfo constructor = servicestype[typeof(t)].getconstructor(new type[0]);
                debug.assert(constructor != null, "cannot find a suitable constructor for " + typeof(t));
                t service = (t)constructor.invoke(null);

                instantiatedservices.add(typeof(t), service);
            }
            catch (keynotfoundexception)
            {
                throw new applicationexception("the requested service is not registered");
            }
        }
        return (t)instantiatedservices[typeof(t)];
    }
}

-------------------------------------------------------------------------------------------

public interface iservice
{
    void sayhello();
}
public class servicea : iservice
{
    public void sayhello()
    {
        console.writeline("hello,i'm from a");
    }
}
public class serviceb : iservice
{
    public void sayhello()
    {
        console.writeline("hello,i'm from b");
    }
}
public class servicec : iservice
{
    public void sayhello()
    {
        console.writeline("hello,i'm from c");
    }
}

public class serviced : iservice
{
    private readonly iservice _service;

    public serviced(iservice service)
    {
        _service = service;
    }
    public void sayhello()
    {
        console.writeline("-------------");
        _service.sayhello();
        console.writeline("hello,i'm from d");
    }
}

-------------------------------------------------------------------------------------------

class program
{
    static void main(string[] args)
    {
        iservicelocator locator = servicelocator.getinstance();
        locator.addservice<servicea>();
        locator.addservice<serviceb>();
        locator.addservice<servicec>();

        locator.getservice<servicea>().sayhello();
        locator.getservice<serviceb>().sayhello();
        locator.getservice<servicec>().sayhello();

        locator.addservice(() => new serviced(locator.getservice<servicea>()));
        locator.getservice<serviced>().sayhello();
    }
}

程序输出如下图所示:

当我们需要注册的服务对应的有参构造函数中的参数不需要注册到 servicelocator,那我们可以采用这种方法进行服务注册,比较灵活。

  • 隐式构造
public class servicelocator : iservicelocator
{
    private static servicelocator _instance;

    private static readonly object _locker = new object();

    public static servicelocator getinstance()
    {
        if (_instance == null)
        {
            lock (_locker)
            {
                if (_instance == null)
                {
                    _instance = new servicelocator();
                }
            }
        }
        return _instance;
    }

    private readonly idictionary<type, type> servicestype;
    private readonly idictionary<type, object> instantiatedservices;

    private servicelocator()
    {
        servicestype = new concurrentdictionary<type, type>();
        instantiatedservices = new concurrentdictionary<type, object>();
    }

    public void addservice<t>()
    {
        servicestype.add(typeof(t), typeof(t));
    }
    public void addservice<t>(func<t> implementation)
    {
        servicestype.add(typeof(t), typeof(t));
        var done = instantiatedservices.tryadd(typeof(t), implementation());
        debug.assert(done, "cannot add current service: " + typeof(t));
    }

    public t getservice<t>()
    {
        var service = (t)getservice(typeof(t));
        if (service == null)
        {
            throw new applicationexception("the requested service is not registered");
        }
        return service;
    }

    /// <summary>
    /// 关键代码
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    private object getservice(type type)
    {
        if (!instantiatedservices.containskey(type))
        {
            try
            {
                constructorinfo constructor = servicestype[type].gettypeinfo().declaredconstructors
                                            .where(constructor => constructor.ispublic).firstordefault();
                parameterinfo[] ps = constructor.getparameters();

                list<object> parameters = new list<object>();
                for (int i = 0; i < ps.length; i++)
                {
                    parameterinfo item = ps[i];
                    bool done = instantiatedservices.trygetvalue(item.parametertype, out object parameter);
                    if (!done)
                    {
                        parameter = getservice(item.parametertype);
                    }
                    parameters.add(parameter);
                }

                object service = constructor.invoke(parameters.toarray());

                instantiatedservices.add(type, service);
            }
            catch (keynotfoundexception)
            {
                throw new applicationexception("the requested service is not registered");
            }
        }
        return instantiatedservices[type];
    }
}

-------------------------------------------------------------------------------------------

public interface iservice
{
    void sayhello();
}
public class servicea : iservice
{
    public void sayhello()
    {
        console.writeline("hello,i'm from a");
    }
}
public class serviced : iservice
{
    private readonly servicea _service;

    public serviced(servicea service)
    {
        _service = service;
    }
    public void sayhello()
    {
        console.writeline("-------------");
        _service.sayhello();
        console.writeline("hello,i'm from d");
    }
}

-------------------------------------------------------------------------------------------

class program
{
    static void main(string[] args)
    {
        iservicelocator locator = servicelocator.getinstance();
        locator.addservice<serviced>();
        locator.addservice<servicea>();
        locator.getservice<serviced>().sayhello();

        locator.getservice<servicea>().sayhello();
    }
}

程序输入如下图所示:

通过隐式构造的方式可以将我们待注册的服务依据其对应的构造函数参数类型来动态创建,这和 dotnetcore 中的 serviceprovider 的方式很相似,它不依赖于我们服务的注册顺序,都能正常的进行构造。

官方实现

上面我们通过自己手动实现了一个 servicelocator 大致明白了其中的实现思路,所以有必要看一下官方是如何实现的。

首先,在使用方式上,我们一般这么使用,示例代码如下所示:

var services= new servicecollection();

......
services.addsingleton(configuration.getsection(nameof(appsettings)).get<appsettings>());
......

serviceprovider serviceprovider = services.buildserviceprovider();

可以看到,最终我们是通过一个 serviceprovider 来获取我们的服务提供对象,该类对应官方的源码实现如下图所示:

通过源码我们不难看出,所有的服务对象都是注册进了一个 iserviceproviderengine 类型的对象,而该对象的具体类型又是根据 serviceprovideroptions 的方式来进行创建。这里,有两个类我们需要着重注意一下:

  • serviceprovider
  • callsitefactory

前者负责上层注入,后者负责底层构建,所以如果你想看一下官方是如何实例化这些注入对象的话可以看一下对应的实现代码。

总结

如果你看完了我上面所有的代码示例,回头想想,其实一点都不难,要是自己写的话,也是可以写出来的。但是在实际工作中,能够活学活用的人却很少,归根到底就是思维方式的问题。官方也是通过反射来实现的,只不过他的内部逻辑会更严谨一些,这就导致了他的实现会更复杂一些,这也是里所当然的事情。

题外话

click me(玻璃心请勿点击)

本来我不是太想说这件事情,但是想想还是有必要说一下。

  • 首先,我这人最烦 高级黑小粉红。在最开始的那几年,我天真地以为技术圈和其他行业比起来,要相对好一些,因为这个圈子里都是搞技术的朋友。可过了几年之后,我的这种想法确实被打脸了。所以我现在看到一些事,遇到一些人,多少感觉挺悲哀的。说实话,这几年,我遇到过很多 伪程序员,他们大多喜欢 拿来主义,从来不会自己 主动研究问题。看见别人做什么,自己就跟着做什么,遇到不会的,搞不定的要么甩锅,要么放过。如果他来 请教 你,你对他说了解决思路,然后让他自己花时间研究研究,他就不高兴了,会给你 贴标签。对此我想说的是,当你走出校门步入社会开始工作后,除了你父母外,没有任何人有责任和义务免费给你进行专门指导,学习是自己的事情,一切都是事在人为。也罢,我也懒得解释,你开心就好。
  • 其次,我个人觉得抄代码这件事情不丢人。自己经验不足,看看老前辈之前优秀的代码,拿来学习学习,理解并明白前辈这样设计的优缺点,并把它转化为自己的。当以后再遇到一些业务场景能够活学活用就可以了,这种行为我不反对,并且我个人一直都是这样的。但是有一点我接受不了,抄袭但不注明出处的行为,只要你的文章灵感有来源于其他地方,无论质量如何,请务必引入相关出处,否则这和剽窃没什么区别。我当时在博客园看到一篇首页文章的标题和内容后真不知道该说什么。这里我就不指出该文章的链接,还望好自为之。总之,希望每一个技术人不要把写博客这件事情玩变味了。

共勉!

相关参考

  • service locator pattern in c#
  • microsoft.extensions.dependencyinjection