对模块和元数据进行打包

我们这个模块系统需要一种方法来对模块的内容以及描述导入和导出的元数据进行打包,将其包括到一个可部署的单元中。

Java 已经有了标准的部署单元:JAR 文件。JAR 文件可能并不算一种非常成熟的模块,但对于移动大块的编译代码还是不错的,所以我们并不需要创建新的东西。那么现在的唯一问题是,将元数据(即导入和导出列表、版本等等)放在哪里?

看起来配置格式强烈地受到一时潮流的影响;如果我们是在 2000 年到 2006 年期间设计这个模块系统,我们很可能会选择将元数据放到 JAR 文件下的某个 XML 文件中这种方式能够工作,但会遇到许多问题:对于流程,XML 文件并不是特别有效率,尤其是我们必须在 JAR 文件的某个地方才能找到它,而且在进行语法分析之前还要对其进行解压。JAR 文件是一个 ZIP 压缩包,所以要找到某个特定文件,意味着必须读取末端,找到用于跟踪记录的中央目录,然后再跳转到该目录指定的分支上。换句话说,通常不得不读取整个 JAR 文件,对于需扫描大型目录的工具,如果这个目录下有很多模块,这个过程将变得非常痛苦。比如,搜索某个可用的模块,以满足某个依赖关系。

另外 XML 几乎不能人工编辑。为了正确的编辑这种文件,我们需要使用特定的编辑根据。

另一方面,如果是在 2006年之后设计这个模块系统,我们的第一个想法会是使用 Java 注释(annotation)。如果使用适当,我非常喜欢注释,将类似 @Export(version="1.0.0") 的东西放到 Java 源文件中的包声明上,很明显比在单独文件中对其进行维护要更有吸引力。不过,等一下……在包的每个源文件中,包声明都会重复一次;难道我们也必须在所有源文件中加入注释?

为了解决这个问题,Java 语言规范(JLS)建议使用一个名为“package-info.java” 特定源文件。但对于不属于任何特定包的元数据怎么处理呢?比如导入包的列表或模块本身的名称和版本。Java 语言规范建议我们需要使用另一个特定源文件,使用类似“module-info.java”名称。

到目前一切顺利,现在让我们看看如何对模块进行处理。

这些特定的源文件将在 package-info.class 和 module-info.class 中被编译为字节码,这样就不需要打开 ZIP 压缩的 JAR 文件来查看元数据了。所有模块扫描工具都必须对整个模块系统进行读取,而且也必须能够处理字节码。运行时模块系统自身也必须立即为模块常见一个类加载器,用于读取它的元数据;结果是,如果我们能够将类加载器的创建推迟到真正从模块中加载某个类那个时刻,就可以消除大量的优化工作。

已经发生的事实是,OSGi 的设计的确是在 2000 年之前,所以它的确选择了这些方案中的其中之一。回头看看 JAR 文件规范,答案自动浮现:META-INF/MANIFEST.MF 是应用程序专用元数据的标准位置。在规范中这样写道:“忽略不可理解的属性。这类属性可能包含应用程序所用的特定部署新型。”

MANIFEST.MF 专为提高流程的效率而设计,而且它至少比 XML 更快。某种长度上,它是可读的;至少与 XML 一样可读,很明显比编译的 Java 字节码更具有可读性。此外,标准的 jar 命令行工具通常将 MANIFEST.MF 放到 JAR 文件的第一项中,所以为了获取元数据,工具只需扫描文件中的前几百个字节。

令人遗憾的是 MANIFEST.MF 并不完美。其一,由于规则要求每行不超过 72 个字节,手工编写相对困难,考虑到单个 UTF-8 字符为 1-6 个字节,这种规则会导致一些问题。一个更好的方式是利用另一格式的模板来生成 MANIFEST.MF。Bnd 工具是这样的,Maven 的 Bundle Pulin 和 SpringSource 的 Bundlor 也是如此。

事实上,Bnd 甚至包括对于处理注释的实验式的支持,比如 @Exporton 源代码注释。这样我们将能够获得来自2个方面的好处:注释的便利性,以及 MANIFEST.MF 的效率和运行时可读性/工具性。

后期绑定

模块拼图的最后一块是部署到接口的后期绑定。我认为这是模块化一个至关重要的功能,虽然某些模块系统对此完全忽略,或者认为它不属于模块化这个范围。

人们都知道,Java 中的接口会破坏功能提供者和使用方之间的耦合性。定义一个接口,其作用相对于使用方和提供方的合同,如何一方都不需直接获得对方的信息,这样我们就可以将它们放到不同的模块中,而这些模块之间不存在互相的依赖关系。而是每一个模块对于接口存在依靠性,我们可以选择囧这个接口放在第三个模块中。唯一的问题是如何为使用方类提供接口实例,而最常见的答案是使用依赖注入(Dependency Injection,缩写为DI),比如 Spring 或 Guice。

因此,为了完成我们的模块系统,只需使用现有的 DI 框架即可。毕竟我们追求的简洁性,声明一个问题不属于我们处理的范围,让别人来解决,没有什么比这个还简单。但是,这种方式并不是非常令人满意,因为 DI 框架事实上也需要知道模块的边界。传统的 DI 使用方式的问题在于它会创建巨大的中心化配置,这个配置会对所有模块产生影响。Peter Kriens 将这一问题称为“全能类”(God Class)问题,在这个问题中,一个组件了解每个模块的所有内容,并要求所有模块对其进行绑定(作为一个无神论者,我认为这个不可能做到,但即便你是有神论者,我肯定你也同意除了当前已存在的上帝之外,我们不应再去制造更多神)。这些全能类(或 XML 配置)非常脆弱,难于维护,否定了将代码划分到模块中所带来的大多数好处。

我们应该寻找一种去中心化的方法。不是让全能类告诉我们去做什么,我们可以假设,每个模块可能常见对象并将它们发布到某些地方,而其他模块可以找到它们。我们将这些发布的对象成为“服务”,而它们发布的地方称为“服务寄存器”。有关服务,最重要的信息是它进行部署的接口,所以我们可以将它作为最初的注册码。现在,一个模块,如果需要找到特定接口的实例,只需查询寄存器,看看当时提供哪些服务。寄存器本身仍然是位于任何模块之外的中性化组件,但它不是全能的,而是更像一个共享黑板。

我们不需要放弃 DI,事实上它还非常有用:现有的 DI 框架可用来向其他服务中注入服务,以及将某些对象发布为服务。DI 框架不在指挥整个系统,相反它只是在单个模块中的部署的应用。我们甚至可以使用多个 DI 框架,比如在同一个应用程序中同时使用 Spring 和 Guice,当想要集成第三方组件而这个组件使用的框架不是我们所选择的那个时,这是非常有用的。最后,服务寄存器为发布和查询提供可编程的 API 接口,但只能用于低阶工作,如部署一个新的 DI 框架。

总结

希望以上的泛泛而论能够解释为什么OSGi 会是现在这个样子;从某种意义上说,这是一种技术的进化。人们将会继续抱怨OSGi 太复杂,但我认为任何存在的复杂性都是必要的,用于解决我以上描述的难题。

当然它并不是完美的。比如,版本控制还可以进行改善,尤其是对于那些版本方案非常奇怪的第三方库。为版本编号赋予一定的意义,仍然是正确的做法,但为了对版本和 API 兼容性进行管理,还需要更多的协助工具。还有传统的库,仍然在危险的假设一个扁平化系统类路径的存在。按照我的观点,任何在类名称中使用字符串或调用 Class.forName() 来获得对象的库都是错误的,因为它假设所有类对于模块都是可见的,而在任何类型的模块化系统中,这都是不正确的。很遗憾,这些问题还不能在一夜之间完全解决,所以处理这些破损的库,我们需要一些策略。不过处理这些问题需要一种不同的方式,从而对于其他人来说,不至于破坏模块化的规则。

转载于:https://www.cnblogs.com/silentjesse/archive/2013/04/07/3003992.html

MANIFEST.MF相关推荐

  1. MANIFEST.MF文件的格式

    MANIFEST.MF文件的格式 1. 基本格式 属性名称+:+空格+属性值 2. 没行最多72个字符,换行继续必须以空格开头 3. 文件最后必须要有一个回车换行 4. Class-Path 当前路径 ...

  2. java jar包中MANIFEST.MF中有版本信息

    有时候查看人家的源代码,但有可能该软件有很多个版本,这个时候就可以通过 MANIFEST.MF来查看当前项目引用的是哪个版本.当然了如果本身jar包名已经是用版本号来命名的.则不需要这样查看. 可以参 ...

  3. MANIFEST.MF文件全面解析

    (一)简介 当我们用Jar命令打完包后,会在根目录下面创建META-INF目录,该目录下面会有一些对该Jar包信息的描述,其中肯定会有一个MANIFEST.MF文件,该文件包含了该Jar包的版本.创建 ...

  4. Android APK 签名文件MANIFEST.MF、CERT.SF、CERT.RSA分析

    首先我们找一个已经签名的apk文件,修改后缀名为zip,然后解压.可以看到里面有一个META-INF文件夹,里面就是签名验证的文件.有三个文件MANIFEST.MF.CERT.SF.CERT.RSA分 ...

  5. manifest.mf._MANIFEST.MF和feature.xml版本控制规则

    manifest.mf. 我永远都忘记了OSIF插件和功能的 MANIFEST.MF和feature.xml中的依赖项声明的规则是什么. 谷歌搜索经常导致沮丧而不是答案. 因此,因为今天我实际上找到了 ...

  6. MANIFEST.MF和feature.xml版本控制规则

    我永远忘记了OSIF插件和功能的 MANIFEST.MF和feature.xml中的依赖项声明的规则是什么. 谷歌搜索经常导致沮丧而不是答案. 所以,因为今天我实际上找到了这些规则的简要列表,所以我想 ...

  7. Java 打 jar 包时,MANIFEST.MF 文件详解

    具体详情,详见<MANIFEST.MF文件详解> manifest.mf 的编写规则 不能有空行和空格的地方 第一行不可以是空行(第一行的行前不可以有空行),行与行之间不能有空行,每行的行 ...

  8. Springboot读取jar包中的MANIFEST.MF文件内容

    Springboot打包成可执行jar包后,包结构如下所示: 工程编译后的文件都在BOOT-INF/classes目录下,如果需要读取并显示META-INF/MANIFEST.MF文件内容,那么可以用 ...

  9. IDEA打jar包时出现manifest.mf already exists in vfs解决办法

    1.什么是MANIFEST.MF 下面MANIFEST.MF的介绍来自百度百科http://baike.baidu.com/link?url=QQ97CnYdkb0RqF33qFh9XiRfF_2NG ...

  10. MANIFEST.MF的用途(转载)

    可以用的上的有: 1. Main-Class 指定程序的入口,这样可以直接用java -jar xxx.jar来运行程序. 2. Class-Path 指定jar包的依赖关系,class loader ...

最新文章

  1. java配置常量_Java构建时间常量配置
  2. Go语言int类型绑定方法
  3. 20145104张家明实验五
  4. kaggle研究生招生(中)
  5. idea分支如何刷新显示最新
  6. python安装运行时提示不是内部或外部命令怎么办_如何解决cmd运行python提示不是内部命令...
  7. a标签里面设置onclick_HTML学习笔记:各种常用标签
  8. 帆软 JAVA扩展_java报表开发工具FineReport教程之报表设计:单元格扩展
  9. 记一次lvs-tunnel模式的故障分析(7)
  10. Linux怎么添加交换空间,如何在Ubuntu上增加swap交换空间
  11. 重磅!百度飞桨开源语音基础模型库|中英文语音识别、语音翻译、语音合成、声音分类通通一行代码轻松搞定...
  12. 【编译原理笔记12】中间代码生成:简单赋值语句的翻译,数组引用的翻译
  13. CentOS/Ubuntu 下 MySQL 的安装
  14. gitlab一键安装 笔记
  15. oracle goldengate director,Oracle GoldenGate Director安装和配置
  16. Gaussian09 optimization trajectory: python script
  17. 2016 hack.lu-ctf redacted RSA数据恢复
  18. 用C语言编写大写字母用ascll表示,用java实现输入ASCII码,打印出对应的大写字母。...
  19. 引用 中国计算机学术权威期刊
  20. 用友杨雨春 融合是企业互联网化的下一个主旋律

热门文章

  1. 雷林鹏分享:Laravel 安装
  2. 游戏化设计:平台用户激励系统——互联网平台建设...
  3. Docker系列教程06-实战:修改Nginx首页
  4. 测试库的接收到的数据是否完整(jrtplib为列)
  5. wordpress教程之函数site_url()、home_url()、bloginfo(‘url’)的区别
  6. 使用Scala的强大api快速加工数据
  7. MYSQL MYSQLI PDO
  8. 统治世界的十大算法(转)
  9. VMware ESXI5.0的安装配置
  10. jq输出html 单引号引号转义符,javascript - 由于JSON中的单引号转义,jQuery.parseJSON抛出“无效的JSON”错误...