Golang库插件注册加载机制的问题

目录
  • 注册
  • 加载
  • 总结

最近看到一个内部项目的插件加载机制,非常赞。当然这里说的插件并不是指的golang原生的可以在buildmode中加载指定so文件的那种加载机制。而是软件设计上的「插件」。如果你的软件是一个框架,或者一个平台性产品,想要提升扩展性,即可以让第三方进行第三方库开发,最终能像搭积木一样将这些库组装起来。那么就可能需要这种库加载机制。

我们的目标是什么?对第三方库进行某种库规范,只要按照这种库规范进行开发,这个库就可以被加载到框架中。

我们先定义一个插件的数据结构,这里肯定是需要使用接口来规范,这个可以根据你的项目自由发挥,比如我希望插件有一个Setup方法来在启动的时候加载即可。然后我就定义如下的Plugin结构。

type Plugin interface{
  Name() string
  Setup(config map[string]string) error
}

而在框架启动的时候,我启动了一个如下的全局变量:

var plugins map[string]Plugin

注册

有人可能会问,这里有了加载函数setup,但是为什么没有注册逻辑呢?

答案是注册的逻辑放在库的init函数中。

即框架还提供了一个注册函数。

// package plugin
Register(plugin Plugin)

这个register就是实现了将第三方plugin放到plugins全局变量中。

所以第三方的plugin库大致实现如下:

package MyPlugin

type MyPlugin struct{
}
func (m *MyPlugin) Setup(config map[string]string) error {
	// TODO
func (m *MyPlugin) Name() string {
	return "myPlugin"
func init() {
	plugin.Register(&MyPlugin)

这样注册的逻辑就变成了,如果你要加载一个插件,那么你在main.go中直接以 _ import的形式引入即可。

package main

_ import "github.com/foo/myplugin"
func main() {
}

整体的感觉,这样子插件的注册就被“隐藏”到import中了。

加载

注册的逻辑其实看起来也平平无奇,但是加载的逻辑就考验细节了。

首先插件的加载其实有两点需要考虑:

  • 配置
  • 依赖

配置指的是插件一定是有某种配置的,这些配置以配置文件yaml中plugins.myplugin的路径存在。

plugins:
	myplugin:
		foo: bar

其实我对这种实现持保留意见。配置文件以一个文件中配置项的形式存在,好像不如以配置文件的形式存在,即以config/plugins/myplugin.yaml 的文件。

这样不会出现一个大配置文件的问题。毕竟每个配置文件本身就是一门DSL语言。如果你将配置文件的逻辑变复杂,一定会有很多附带的bug是由于配置文件错误导致的。

第二个说的是依赖。插件A依赖与插件B,那么这里就有加载函数Setup的先后顺序了。这种先后顺序如果纯依赖用户的“经验”,将某个插件的Setup调用放在某个插件的Setup调用之前,是非常痛苦的。(虽然一定是有办法可以做到)。更好的办法是依赖于框架自身的加载机制来进行加载。

首先我们在plugin包中定义一个接口:

type Depend interface{
	DependOn() []string
}

如果我的插件依赖一个名字为 “fooPlugin” 的插件,那么我的插件 MyPlugin就会实现这个接口。

package MyPlugin

type MyPlugin struct{
}
func (m *MyPlugin) Setup(config map[string]string) error {
	// TODO
func (m *MyPlugin) Name() string {
	return "myPlugin"
func init() {
	plugin.Register(&MyPlugin)
func (m *MyPlugin) DependOn() []string {
	return []string{"fooPlugin"}

在最终加载所有插件的时候,我们并不是简单地将所有插件调用Setup,而是使用一个channel,将所有插件放在channel中,然后一个个调用Setup,遇到有Depend其他插件的,且依赖插件还未被加载,则将当前插件放在队列最后(重新塞入channel)。

var setupStatus map[string]bool

// 获取所有注册插件
func loadPlugins() (plugin chan Plugin, setupStatus map[string]bool) {
	// 这里定义一个长度为10的队列
	var sortPlugin = make(chan Plugin, 10)
	var setupStatus = make[string]bool

	// 所有的插件
	for name, plugin := range plugins {
		sortPlugin <- plugin
		setupStatus[name] = false
	}
	return sortPlugin, setupStatus
}
// 加载所有插件
func SetupPlugins(pluginChan chan Plugin, setupStatus map[string]bool) error {
	num := len(pluginChan)
	for num > 0 {
		plugin <- pluginChan

		canSetup := true
		if deps, ok := p.(Depend); ok {
			depends := deps.DependOn()
			for _, dependName := range depends{
				if _, setuped := setupStatus[dependName]; !setup {
						// 有未加载的插件
						canSetup = false
						break
				}
			}
		}
		// 如果这个插件能被setup
		if canSetup {
			plugin.Setup(xxx)
			setupStatus[p.Name()] = true
		} else {
			// 如果插件不能被setup, 这个plugin就塞入到最后一个队列
			pluginChan <- plugin
	return nil
} 

上面这段代码最精妙的就是使用了一个有buffer的channel作为一个队列,消费队列一方SetupPlugins,除了消费队列,也有可能生产数据到队列,这样就保证了队列中所有plugin都是被按照标记的依赖被顺序加载的。

总结

这种插件的注册和加载机制是非常优雅的。注册方面,巧妙使用隐式import来做插件的注册。而加载方面,巧妙使用有buffer的channel作为加载队列。

到此这篇关于Golang库插件注册加载机制的文章就介绍到这了,更多相关Golang插件机制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 解决vscode中golang插件依赖安装失败问题

    vscode中安装ms-vscode.go插件后可以开启对go语言的支持,ms-vscode.go插件需要依赖一些工具,安装完成后提示 gocode go-outline go-symbols guru gorename gocode-gomod goreturns golint Installing github.com/ramya-rao-a/go-outline FAILED 由于网络原因,一些依赖工具无法正常安装,需要进行手动安装. 以下为手动安装的工具的步骤: 在%GOPATH%\sr

  • 解决 Golang VS Code 插件下载安装失败的问题

    最近开始学Go语言,但是在使用VS Code 编写Go的时候出现了插件无法下载的问题.最初我的解决办法也是从github下载再安装,但是我并不喜欢这种做法,因为我要在多台pc上使用VS Code编写Go,所以我觉要重复多次很麻烦,而且插件的安装也非常麻烦,我曾经一度想放弃学习Go语言,没错因为安装插件很麻烦.在经过我多次的查找最终找到了http://goproxy.cn/,这是Go的国内镜像,使用了这个就可以结局Go插件无法下载的问题. (http://goproxy.cn/ 文档简介,方便以后

  • Golang库插件注册加载机制的问题

    目录 注册 加载 总结 最近看到一个内部项目的插件加载机制,非常赞.当然这里说的插件并不是指的golang原生的可以在buildmode中加载指定so文件的那种加载机制.而是软件设计上的「插件」.如果你的软件是一个框架,或者一个平台性产品,想要提升扩展性,即可以让第三方进行第三方库开发,最终能像搭积木一样将这些库组装起来.那么就可能需要这种库加载机制. 我们的目标是什么?对第三方库进行某种库规范,只要按照这种库规范进行开发,这个库就可以被加载到框架中. 我们先定义一个插件的数据结构,这里肯定是需

  • PHP的autoload自动加载机制使用说明

    在PHP开发过程中,如果希望从外部引入一个class,通常会使用include和require方法,去把定义这个class的文件包含进来,但是这样可能会使得在引用文件的新脚本中,存在大量的include或require方法调用,如果一时疏忽遗漏则会产生错误,使得代码难以维护. 自PHP5后,引入了__autoload这个拦截器方法,可以自动对class文件进行包含引用,通常我们会这么写: 复制代码 代码如下: function __autoload($className) { include_o

  • PHP autoload与spl_autoload自动加载机制的深入理解

    PHP autoload机制详解(1) autoload机制概述在使用PHP的OO模式开发系统时,通常大家习惯上将每个类的实现都存放在一个单独的文件里,这样会很容易实现对类进行复用,同时将来维护时也很便利.这 也是OO设计的基本思想之一.在PHP5之前,如果需要使用一个类,只需要直接使用include/require将其包含进来即可.下面是一个实际的例 子: 复制代码 代码如下: /* Person.class.php */<?phpclass Person {var $name, $age;f

  • thinkPHP5.0框架自动加载机制分析

    本文实例讲述了thinkPHP5.0框架自动加载机制.分享给大家供大家参考,具体如下: 概述 ThinkPHP5.0 真正实现了按需加载,所有类库采用自动加载机制,并且支持类库映射和composer类库的自动加载. 自动加载的实现由think\Loader类库完成,自动加载规范符合PHP的PSR-4. 自动加载 由于新版ThinkPHP完全采用了命名空间的特性,因此只需要给类库正确定义所在的命名空间,而命名空间的路径与类库文件的目录一致,那么就可以实现类的自动加载. 类库的自动加载检测顺序如下:

  • PHP进阶学习之类的自动加载机制原理分析

    本文实例讲述了PHP类的自动加载机制.分享给大家供大家参考,具体如下: 前言 我们在常见的PHP的主流框架中通常写好一个类只需写好相应的命名空间或直接实例化类就可以实现类的使用.而不需要使用原生的方式把类文件一个个用require.include引入包含进来,这归功于PHP的类自动加载机制,也是本文讨论的要点. 一.概念 在PHP代码中,不需要显式地使用文件路径将类库文件包含进来,便可使用该文件中定义的类库,这种技术称作自动加载. 在使用类或者定义了命名空间的类时,只需要直接实例化使用,PHP机

  • Angular懒加载机制刷新后无法回退的快速解决方法

    今天在项目中遇到一个很奇怪的问题,使用oclazyload懒加载angular的模块,刷新页面后,单击回退按钮无法返回上一个页面.估计是使用懒加载机制销毁了angular内部的state关联,导致无法回到上一个state(单击回退按钮 ui-routre的 $stateChangeStart 事件都不会触发),当然这只是猜测,由于事件关系也没有去深入的探究源码. angular懒加载机制刷新后无法回退的解决方案 : 通过查看angular(ionic)的源码发现$browser这个服务上有个on

  • Java实现配置加载机制

    前言 现如今几乎大多数Java应用,例如我们耳熟能详的tomcat, struts2, netty...等等数都数不过来的软件, 要满足通用性,都会提供配置文件供使用者定制功能. 甚至有一些例如Netty这样的网络框架,几乎完全就是由配置驱动,这样的软件我们也通常称之为"微内核架构"的软件. 你把它配置成什么,它就是什么. It is what you configure it to be. 最常见的配置文件格式是XML, Properties等等文件. 本文探讨加载配置中最通用也是最

  • Java 配置加载机制详解及实例

    前言 现如今几乎大多数Java应用,例如我们耳熟能详的tomcat, struts2, netty-等等数都数不过来的软件,要满足通用性,都会提供配置文件供使用者定制功能. 甚至有一些例如Netty这样的网络框架,几乎完全就是由配置驱动,这样的软件我们也通常称之为"微内核架构"的软件.你把它配置成什么,它就是什么. It is what you configure it to be. 最常见的配置文件格式是XML, Properties等等文件. 本文探讨加载配置中最通用也是最常见的场

  • 说说PHP的autoLoad自动加载机制

    __autoload的使用方法1: 最经常使用的就是这种方法,根据类名,找出类文件,然后require_one 复制代码 代码如下: function __autoload($class_name) { $path = str_replace('_', '/', $class_name); require_once $path . '.php'; } // 这里会自动加载Http/File/Interface.php 文件 $a = new Http_File_Interface(); 这种方法

  • PHP面向对象自动加载机制原理与用法分析

    本文实例讲述了PHP面向对象自动加载机制原理与用法.分享给大家供大家参考,具体如下: 在学习PHP的面向对象的时候,会知道很多"语法糖",也就是魔术方法.有一个加自动加载的魔术方法,叫:__autoload(); 先看一段代码 <?php function __autoload($classname) { $filename = "./". $classname .".php"; include_once($filename); } new

随机推荐