背景
不同客户对系统的需求会有所不同,通常会有一定的定制化需求,例如增加一些新功能、完善原有功能等。
因此面对不同客户时,需要在原有系统的基础上进行不同程度的修改和二次开发。
而随着客户的增多,将导致系统在版本、功能、需求等方面的管理变得愈加繁杂,其中也可能存在重复开发、功能无法重用、开发工作量过大等问题。
因此需要寻求一种解决方案使得系统的开发更加灵活,即实现可定制化的目标,实现以更少的开发工作量应对变化的需求。
目标
实现可定制化的目标,实现以更少的开发工作量应对变化的需求。
实现路径
主要实现路径:系统插件化
附录:对可定制化的理解
- 从客户角度来看,实现最终对外的系统服务可定制化,有众多方案。因为对于客户,系统的内部实现是一个黑箱,只要对外的服务可供选择和定制即可。
- 从系统开发的角度来看,实现系统服务的可定制化的目的就是为了以更少的开发工作量应对变化的需求,本质上需要完成如下任务:
- 对系统进行合理切分,且应当保证切分出来的各部分耦合度足够低,例如模块化、组件化、插件化
- 提供各个部分之间交互、通信的机制
从上面的思考来看,实现可定制化也可有其它路径:
- 微服务
- 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 的元数据描述信息。
例如:
作者:404_89_117_101
链接:https://www.jianshu.com/p/795576b1d4f6
来源:简书