插件化技术调研

插件化技术调研

Time: 2018.12.04

调研背景

最近实验室的项目有插件化的需求,所以让我对插件化相关技术进行调研。

这篇文章对调研报告稍加修改,作为自己对这次调研的记录。

背景与问题描述
如果你的软件是传统销售模式,即将一套软件完整卖给不同客户。

那么不同客户对系统的需求会有所不同,通常会有一定的定制化需求,例如增加一些新功能、完善原有功能、修改皮肤样式等。

因此面对不同客户时,需要在原有系统的基础上进行不同程度的修改和二次开发。

而随着客户的增多,将导致系统在版本、功能、需求等方面的管理变得愈加繁杂,其中也可能存在重复开发、功能无法重用、开发工作量过大等问题。

因此需要寻求一种解决方案使得系统的开发更加灵活,即实现可定制化的目标,实现以更少的开发工作量应对变化的需求。

目标
实现可定制化的目标,实现以更少的开发工作量应对变化的需求。

实现路径
主要实现路径:系统插件化

附录:对可定制化的理解

  • 从客户角度来看,实现最终对外的系统服务可定制化,有众多方案。因为对于客户,系统的内部实现是一个黑箱,只要对外的服务可供选择和定制即可。
  • 从系统开发的角度来看,实现系统服务的可定制化的目的就是为了以更少的开发工作量应对变化的需求,本质上需要完成如下任务:
    • 对系统进行合理切分,且应当保证切分出来的各部分耦合度足够低,例如模块化、组件化、插件化
    • 提供各个部分之间交互、通信的机制

从上面的思考来看,实现可定制化也可有其它路径:

  • 微服务
  • EJB
  • 模块化

当然关键在于系统的 上是否合理和优雅。其实思想就是只要系统的功能模块划分的足够合理,耦合度足够低,就已经完成可定制化的一半了,之后再利用动态加载等技术可很容易完成可定制化。如果划分的这一步不够合理,那加上插件化框架其实也就是多加了个壳而已。

插件化技术基础

什么是插件?

Plug-in (computing) From Wikipedia
In computing, a plug-in (or plugin, add-in, addin, add-on, addon, or extension) is a software component that adds a specific feature to an existing computer program. When a program supports plug-ins, it enables customization.
译:即插件是一种软件组件,该组件主要用来在原有系统上添加一些扩展功能。插件将使得系统具有定制化的能力。

当一个软件系统开发完成后,可能需要添加一些额外的新功能(有时这些新功能也被称为扩展),我们通常希望能够在不影响原有系统的条件下完成新功能的添加,实现这一目标的并是所谓的插件化,新增加的功能模块就叫插件。

插件化有利于降低模块间的耦合度,有利于各模块和项目的维护更新。

典型的插件例如 Chrome、Firefox 浏览器插件、Eclipse 插件、Vscode 等各种 IDE 插件,各种软件提供的主题或皮肤也是一种插件。

关于组件、插件与控件,以及组件化、插件化、模块化这些概念可参见本文末尾的附录。

插件化实现技术

不论是自己设计实现系统的插件化还是直接应用现成的插件化框架,都会涉及到一些基本的基础技术。下面对这些技术做一下基本介绍和整理。

动态加载

插件化系统的特点之一就是实现各个插件的可插拔,系统可灵活的动态拆分与组合。要实现这一特点就离不开动态加载的概念。

Dynamic loading From Wikipedia
Dynamic loading is a mechanism by which a computer program can, at run time, load a library (or other binary) into memory, retrieve the addresses of functions and variables contained in the library, execute those functions or access those variables, and unload the library from memory.
译:动态加载是一种让系统在运行时加载并使用其他软件的机制。

动态加载在不同系统、平台有不同的具体实现,如果你的系统主要运行在 Java 平台之上,那么就要着重关注 Java 的动态加载。

Java 的动态加载主要依靠 ClassLoader。

Java 源码编译成字节码 class 文件,class 文件是 JVM 平台上的基本程序单位,而 ClassLoader 的作用就是加载 class 文件。Java 类加载的完整生命周期包括加载、验证、准备(连接)、解析、初始化、使用、卸载。

并且 Java 的 ClassLoader 在加载类时采用的是双亲委派机制(实现类加载的共享和隔离),如下图所示:

类加载器双亲委派模型_1.png

Java 具有四种类型的 ClassLoader:

  • Bootstrap ClassLoader:加载 jre/lib/rt.jar 或 Xbootclassoath 选项指定的 jar 包。
  • Extension ClassLoader:加载 jre/lib/*.jar 或 -Djava.ext.dirs 指定目录下的 jar 包。
  • Application ClassLoader:加载 classpath 或 Djava.class.path 所指定目录下的类和jar包。
  • Custom ClassLoader:用户自定义的 ClassLoader

但是在具体实现中有可能为特定需求(如顶层 ClassLoader 调用底层 ClassLoader 负责的类)破坏双亲委派模型。

反射

既然已经实现类的动态加载,那么为了访问以及使用动态加载类中的属性、方法就必须应用到反射技术,所以反射也是实现插件化过程中会频繁涉及到的技术之一。

反射技术可讲的内容很多,自己在实验室读研期间做的几个核心项目都与反射息息相关,所以估计以后会专门写一篇详细介绍反射的文章。

而文本主要集中在插件化技术,所以反射在这里就不做过多介绍了。

这里只稍微提及一下反射最重要的核心思想和关键:获取类的元信息。

Java 中类的元信息编译到了 class 文件中,运行时加载至内存模型的元空间区域,所以 Java 本身可以很容易的支持反射技术。

而 Python 这样的动态语言就更容易记录和使用类的元信息,所以反射的实现也很自然。

而 c/c++ 因为其编译结果只有变量、函数地址偏移、函数关系,所以只能在运行时构建函数名称等元信息与地址之间的映射关系,以此实现反射,而 c/c++ 在语言层面并未帮我们做这一工作,所以需要程序自己实现,例如 ProtoBuf 的反射实现。

动态代理

在实现插件化的过程中,有时需要拿到系统或平台层面方法的控制权,从而获得更为强大的能力。典型的就是在 android 平台上实现插件化,有时候需要 hook 部分系统方法才能实现对资源的动态加载。

在 Java 平台上,常用的动态代理方式有:

  • JDK 动态代理:初始创建速度相对较快,创建代理后的运行速度相对较慢
  • CGLib 动态代理:初始创建速度相对较慢,创建代理后的运行速度相对较快

其他相关技术

除动态加载之外,还有其他一些调用组件的方法,同样可应用于插件化:

  • 静态链接:在编译链接,直接将需要的其他模块的执行代码拷贝到调用处。
  • 动态链接:在编译时只记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中,然后程序在运行到指定的代码时,去共享执行内存中已经加载的动态库可执行代码。例如 Windows 的 dll 文件,Linux 下的 so 文件,动态链接也提供了系统动态扩展功能、可插拔的插件化能力。

除上述技术之外,实现插件化过程中还需要考虑插件之间的通信(通过反射拿到引用、面向服务)、插件的描述和管理、插件的状态和生命周期等

插件化框架

除了上面介绍的实现插件化的基础技术,要完整的实现插件化甚至搭建一个插件化框架,还需要做很多关于插件管理、插件状态、插件生命周期定义等工作。其实也就是实现一个框架所要考虑和设计的种种细节。

借助上面介绍的动态加载、反射等技术,再做好业务系统的模块划分,那么完全可以自己动手实现系统插件化了。

但是实际情况是业界已经存在很多比较成熟和实用的框架甚至是规范了,下面是对一些框架的调研结果。

插件化框架列表:

  • OSGI
    • Felix
    • Equinox
    • Makewave Knopflerfish
    • Spring DM
    • Gemini
  • 其他插件化框架
    • Hudson(for Jenkins)
    • JSPF
    • pf4j

OSGI

OSGI - Open Services Gateway initiative

OSGI From Wikipedia
The OSGi specification describes a modular system and a service platform for the Java programming language that implements a complete and dynamic component model, something that does not exist in standalone Java/VM environments.

即 OSGI 是 Java 的动态模块化系统的规范。

OSGi 将使开发者能够构建动态化、模块化的 Java 系统,系统的每个模块将可以像插件一样实现“可插拔”、“即插即用”。

OSGI 框架如下图所示:

layering-osgi.png

从图中可知,OSGI 框架从概念上可以划分为以下几个层次:

  • Bundles
    开发者开发的 OSGi 组件,即 OSGI 解析单位,其中包含 Class 文件、资源文件、元数据(MANIFEST.MF)
  • Services
    Services 层以 publish-find-bind (发布-绑定-查询)的机制动态连接 bundles。主要涉及模块之间的交互和通信。
  • Life-Cycle
    提供安装、启动、停止、更新、卸载 bundles 等关于生命周期的 API
  • Modules
    定义 bundle 如何导入和导出代码的层,即涉及到包及共享的代码
  • Security
    安全层,提供安全机制
  • Execution Environment
    定义特定平台下哪些方法和类是可用的

bundle
Bundle 是 OSGi 中的基本组件,其表现形式仍然为 Java 概念中传统的 Jar 包,其中包含了代码、资源文件和元数据。
通过 META-INF 目录下的 MANIFEST.MF 文件记录 bundle 的元数据描述信息。
例如:

 Bundle-Name: Hello World
 Bundle-SymbolicName: org.wikipedia.helloworld
 Bundle-Description: A Hello World bundle
 Bundle-ManifestVersion: 2
 Bundle-Version: 1.0.0
 Bundle-Activator: org.wikipedia.Activator  // 激活包后所要调用的类名
 Export-Package: org.wikipedia.helloworld;version="1.0.0" // 导出的包
 Import-Package: org.osgi.framework;version="1.3.0" // 导入的包

OSGI 为每个 bundle 创建独立的 ClassLoader,这样使得各个 bundle 的类尽可能相互隔离(包名 + 类名 + ClassLoader 才能唯一确定一个类),从而降低 bundle 之间的耦合度。

Services
主要涉及模块之间的交互和通信。
bundle 之间的交互可通过以下几种方式:

  • 面向服务式: 实现服务注册中心提供 bundle 的注册、查询、注销等服务
  • Event 服务: 采用发布-订阅的方式调用所需要的 bundle

OSGI 框架

OSGI 框架即可视为实现 OSGI 规范的容器,如同 Web 开发中实现了 J2EE 规范的 Web 容器。

下面介绍几款 OSGI 框架。

felix
felix 官方网站
felix 是由 Apache 基金组织开发的面向社区的 OSGi 框架实现,提供了标准的服务以及 OSGi 相关的服务实现。

Felix 项目比较成熟且在很多其他项目中得到应用,提供服务全面,涵盖了全部的 OSGi 4.2 标准。社区活跃,文档丰富。框架设计紧凑,上手简单。

Equinox
Equinox 官方网站-1
Equinox 官方网站-2
Equinox 是 Eclipse 所使用的 OSGi 框架,Eclipse 使用这个框架实现了它强大的插件系统,目前是 Eclipse 项目下的子项目。

Equinox 与 Eclipse 结合紧密,Eclipse 内置大量插件、功能、操作等,且配备专门的 Debug 工具,可非常方便的创建、管理、开发、调试以及部署 Equinox 项目。

虽然 Equinox 功能也算全面,但是对比 Felix 相对较少。

其他 OSGI 框架

  • Makewave Knopflerfish:OSGi的先行者,但是后续发展和维护不足,整体不如 Felix、Equinox 等主要框架
  • Spring DM: Spring 旗下的 OSGi 框架,最大特点就是结合了 Spring 框架。可以和 Spring 家族的各个框架结合的更好。但是 Spring DM 已经在 2009 年底转换成 Eclipse 下的 Gemini 项目
  • Gemini: Eclipse 下的另一个 OSGI 子项目,相比于其它几个框架,Gemini 有专门针对 Web 的子项目(Gemini Web),但是该项目最近更新是在 2012 年,应该已经停止更新和维护了。

其他插件化框架

  • JSPF: Java Simple Plugin Framework,一款十分轻量级、简单的插件框架,但是最新版本发布是在 2011 年五月,基本已经停止更新和维护。
  • pf4j: Plugin Framework for Java,开源轻量级插件化框架。源码可访问 GitHub 仓库,支持 Spring 以及 web 应用。

插件化方案

综上,可总结以下几种主要的、可选的插件化方案。

一、自定义插件化框架

即利用【插件化技术基础】一节中提到的实现插件化的基础技术如动态加载、反射、动态代理等基础自己动手编写符合我们项目的插件化框架。在开发过程中,应该需要解决和设计好以下几个方面:

  • 插件状态、依赖关系等静态描述
  • 插件生命周期管理,包括加载、运行、卸载的等
  • 插件之间的通信,可通过服务注册中心、事件发布与订阅模式、反射获取引用等方式
  • Web 应用插件化还要着重资源文件的管理

具体设计还可参考现有的插件化系统,详见本文附录部分的【现有插件化系统架构参考】一节。

这一方案的优点是可以根据项目系统的实际情况开发简单、完全可控、灵活、定制化、满足需求的插件化框架。而缺点就是具有一定的开发工作量且最终开发效果无法预计,预估至少需要 3 个月以上的开发、调试、整合等。

二、使用 Felix 框架

借助于 Felix 框架,可以很方便的开发插件化系统(当然重点和主要的工作量在于系统模块化的设计)。

更为详细的使用教程可参见如下文章:
Felix Documentation
Java Web应用集成OSGI
OSGi简介与基于OSGi框架(Felix)的简单应用实现

三、使用 Equinox 框架

与 Felix 类似,具体使用教程可参见如下文章:
Writing a bundle-based server application
OSGi as a Web Application Server
使用 Equinox 框架进行 OSGi 环境下的 Web 开发
使用 Equinox 开发 OSGi 应用程序

对上述方案总结如下表所示:

方案 规范 活跃度 工作量
自定义插件化框架 自定义 预估 6 个月以上
使用 Felix 框架 OSGi 最高 预估 3 个月
使用 Equinox 框架 OSGi 预估 3 个月

附录

调研过程中的额外知识点整理

组件、插件与控件

  • 组件:组件(Component)是是一个含义很大的概念,一般是指软件系统的一部分,承担了特定的职责,可以独立于整个系统进行开发和测试,一个良好设计的组件应该可以在不同的软件系统中被使用(可复用)。例如 V8 引擎是 Chrome 浏览器的一部分,负责运行 javascript 代码,这里 V8 引擎就可以视为一个组件。V8 引擎同时也是 Node.js 的 javascript 解释器,这体现了组件的可复用性。
  • 插件:插件(或扩展)是对已有应用程序或者库的功能补充,一个软件的插件(或扩展)是实现了该软件预定义接口的组件,用来向已有的软件添加功能。插件在目标软件发布时可以不预先包含,而是在运行时被使用者注册,然后再被目标软件调用。
  • 控件:gui 编程的一个概念,一般来说一个最终用户可以看到的、可交互的组件,被称为一个控件。

来自 https://www.zhihu.com/question/49536781/answer/117606933

组件化、插件化、模块化

组件化与模块化没有太多必要做名词上的区分,他们的目的都是实现系统的低耦合和可复用。
插件化:比起组件化和模块化,插件化更加强调对现有功能的灵活扩展。主要优点是面向需求变更时的开发灵活性。
系统实现了组件化与模块化,并不一定支持插件化。

现有插件化系统架构参考

Eclipse Plug-in Architecture
Confluence Plug-in Architecture
Hudson Plugin Architecture
ElasticSearch Plugin
joomla plugin

参考资料

osgi-architecture
Notes on the Eclipse Plug-in Architecture
Hudson Plugin Architecture
Converting an EJB JAR file to an OSGi EJB bundle
Felix 官方网站
Equinox 官方网站
Introduction to Maven Plugin Development
Java Web application “plugin” architecture

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容