java 静态模块
模块化是大型Java系统的重要方面。 通常将构建脚本和项目分成多个模块以改善构建,但是在运行时很少考虑到这一点。
在Modular Java系列的第二篇文章中,我们将介绍静态模块化 。 我们将介绍如何创建捆绑包,如何将其安装到OSGi引擎以及如何在捆绑包之间建立(版本化)依赖项。 在下一个情节中,我们将研究动态模块化,并展示捆绑软件如何对即将来临的其他捆绑软件做出React。
正如Modular Java上次介绍的:这是什么? ,Java始终在开发时将包作为模块化的单元,而在部署时将JAR作为模块化的单元。 但是,尽管像Maven这样的构建工具在编译时保证了软件包和JAR的某种组合,但是这些依赖关系可能与运行时类路径不一致。 为了解决此问题,模块可以声明其依赖性要求,以便在运行时可以在执行之前对其进行检查。
OSGi是Java的运行时动态模块系统。 规范描述了OSGi运行时的工作方式。 当前版本是OSGi R4.2 (如先前的InfoQ所述 )。
OSGi模块(也称为捆绑包 )只是一个简单的JAR文件,其附加信息在存档的MANIFEST.MF
。 每个捆绑包的清单至少必须包含:
- Bundle-ManifestVersion :对于OSGi R4捆绑包必须为2(否则对于OSGi R3捆绑包默认为1)
- Bundle-SymbolicName : 捆绑软件的文本标识符,通常采用反向域名格式(例如
com.infoq
并且通常对应于其中包含的软件包 - Bundle-Version : major.minor.micro.qualifier形式的版本,其中前三个元素为数字(默认为0), qualifier为文本(默认为空字符串)
创建捆绑
最简单的捆绑包仅包含清单文件,如下所示:
Bundle-ManifestVersion: 2 Bundle-SymbolicName:com.infoq.minimal Bundle-Version: 1.0.0
但是,创建它并不是很令人兴奋,因此,让我们使用激活器创建一个。 这是特定于OSGi的代码段,在捆绑包启动时会被调用,就像每个捆绑包的main方法一样。
package com.infoq; import org.osgi.framework.*; public class ExampleActivator implements BundleActivator { public void start(BundleContext context) { System.out.println("Started"); } public void stop(BundleContext context) { System.out.println("Stopped"); } }
为了使OSGi知道哪个类是激活器,我们需要在清单中添加两个额外的项目:
Bundle-Activator: com.infoq.ExampleActivator Import-Package: org.osgi.framework
Bundle-Activator
声明要实例化的类,并在捆绑包启动时调用start()
方法。 同样,当捆绑包停止时,将调用stop()
方法。
Import-Package
怎么样? 每个捆绑软件都需要在清单中定义其依赖项,以便在运行时确定是否所有必需的代码都可用。 在这种情况下, ExampleActivator
取决于BundleContext
从org.osgi.framework
包; 如果我们未在清单中声明该依赖关系,则在运行时将收到NoClassDefFoundError
。
下载OSGi引擎
要编译和测试我们的捆绑软件,我们需要一个OSGi引擎。 下面列出了可用于OSGi R4.2的几种开源引擎。 还可以下载参考API进行编译(以防止使用任何特定于平台的功能); 但是,您仍然需要OSGi引擎来运行它。 以下是选项:
春分点 | |
---|---|
执照 | Eclipse公共许可证 |
文献资料 | http://www.eclipse.org/equinox/ |
下载 | org.eclipse.osgi_3.5.0.v20090520.jar |
笔记 | 所述 要获得控制台,请在命令行上提供 |
构架 | org.eclipse.osgi_3.5.0.v20090520.jar |
费利克斯 | |
执照 | Apache许可证 |
文献资料 | http://felix.apache.org/ |
下载 | Felix Framework发行版2.0.0 |
笔记 | 它被视为OSGi引擎的最严格合规性,还用于GlassFish和许多其他开源产品。 您需要运行 java -jar bin/felix.jar 而不是 java -jar felix.jar 因为它会查找路径 bundles 以从当前目录自动启动。 |
构架 | bin/felix.ja r |
no鱼 | |
执照 | Knopflerfish许可证(类似BSD) |
文献资料 | http://www.knopflerfish.org/ |
下载 | knopflerfish_fullbin_osgi_2.3.3.jar |
笔记 | 该JAR是自解压的zip文件; 您必须首先使用 java -jar 运行 才能解压。 不要下载“ bin_osgi ” 替代方案,因为它无法启动。 |
构架 | knopflerfish.org/osgi/framework.jar |
尽管还有一些针对嵌入式设备的较小的OSGi R3运行时(例如Concierge ),但本系列文章将重点介绍OSGi R4。
编译并运行包
得到您的framework.jar
,编译上面的示例是将OSGi框架添加到类路径,然后将其打包到JAR中的情况:
javac -cp framework.jar com/infoq/*/*.java
jar cfm example.jar MANIFEST.MF com
每个引擎都有某种具有相似(但不完全相同)命令的外壳。 在本练习中,我们将仅研究如何启动和运行引擎,以及安装和启动/停止捆绑软件。
引擎启动并运行后,您可以安装捆绑软件(指定为file://
URL),然后使用返回的数字捆绑软件ID启动和停止它。
春分点 | 费利克斯 | no鱼 | |
---|---|---|---|
发射 | java -jar org.eclipse.osgi_*.jar -console | java -jar bin/felix.jar | java -jar framework.jar -xargs minimal.xarg s |
救命 | help | ||
清单 | ss | ps | bundles |
安装 | install file:///path/to/example.jar | ||
开始 | start id | ||
更新资料 | update id | ||
停止 | stop id | ||
卸载 | uninstall id | ||
关掉 | exit | shutdown |
尽管所有外壳程序的工作方式大致相同,但是命令之间的细微差别可能会造成一些混乱。 提供了两个协调的控制台项目( Pax Shell , Posh )和启动器( Pax Runner )。 OSGi RFC 132是正在进行的提议,用于尝试使命令外壳标准化。 Apache Karaf旨在成为可以在Equinox或Felix之上运行的发行版,并提供统一的外壳以及其他功能。 虽然建议将它们用于实际部署,但本系列文章将重点介绍原始OSGi框架的实现。
如果启动OSGi框架,则应该能够从上方安装com.infoq.minimal-1.0.0.jar
(也可以使用链接的地址和install
命令从站点直接安装此文件)。 每次install
捆绑软件时,它将打印出该捆绑软件的数字ID。
根据系统中其他捆绑软件的种类,不可能知道安装时将得到的捆绑软件标识符。 但您应该能够使用适当的命令列出已安装的捆绑软件以进行查找。
依存关系
到目前为止,我们只有一个捆绑包。 模块化的好处之一是,我们可以将系统分解为多个较小的模块,从而降低应用程序的复杂性。 在某种程度上,这已经使用Java的程序包完成了,例如,在网络应用程序中经常使用带有client
程序包和server
程序包的common
程序包。 未说明的含义是client
程序包和server
程序包是独立的,并且都依赖于common
程序包。 但是就像Jetty的最近示例 ( client
意外地依赖于server
结束)一样,这并不总是容易实现的。 实际上,OSGi带给项目的一些成功纯粹是模块之间强制执行的模块化约束。
模块化的另一个好处是将“公共”程序包与非公共程序包分开。 Java的编译时系统允许隐藏的非公共类(那些在特定程序包中可见的类),但没有提供更大的灵活性。 但是,在OSGi模块中,您可以选择导出哪些软件包,而隐含的事实是未导出的软件包对其他模块不可见。
假设我们要开发一个函数来实例化URI模板 (用于Restlet )。 由于这可能是可重用的,因此我们希望将其放在自己的模块中,并且需要使用它的客户依赖我们。 (通常,bundle的粒度并不尽如人意;但是它说明了原理。)该函数将采用一个模板,例如http://www.amazon.{tld}/dp/{isbn}/
,以及用Map
包含tld=com,isbn=1411609255
,我们可以生成URL http://www.amazon.com/dp/1411609255/
(其中一个原因要做到这一点是让我们改变模板,如果亚马逊的网址方案更改,尽管酷URI不变 。)
为了提供一种在不同实现之间进行切换的简便方法,我们将提供一个接口和一个工厂。 这也将使我们看到如何在仍然为客户提供功能的同时将实现隐藏于客户之外。 代码(跨多个源文件)如下所示:
package com.infoq.templater.api; import java.util.*; public interface ITemplater { public String template(String uri, Map data); } // --- package com.infoq.templater.api; import com.infoq.templater.internal.*; public class TemplaterFactory { public static ITemplater getTemplater() { return new Templater(); } } // --- package com.infoq.templater.internal; import com.infoq.templater.api.*; import java.util.*; public class Templater implements ITemplater { public String template(String uri, Map data) { String[] elements = uri.split("\\{|\\}"); StringBuffer buf = new StringBuffer(); for(int i=0;i<elements.length;i++) buf.append(i%2 == 0 ? elements[i] : data.get(elements[i])); return buf.toString(); } }
该实现隐藏在com.infoq.templater.internal
包中,而公共API在com.infoq.templater.api
包中。 如果需要,这将为我们提供灵活性,以便稍后将实现更改为更有效的机制。 ( internal
软件包名称有些传统,但是您可以根据需要命名。)
要授予其他捆绑包访问公共API的权限,我们需要将其从捆绑包中导出 。 我们的清单如下所示:
Bundle-ManifestVersion: 2 Bundle-SymbolicName: com.infoq.templater Bundle-Version: 1.0.0Export-Package: com.infoq.templater.api
创建客户端捆绑
现在,我们可以创建使用模板程序的客户端。 使用上面的示例,创建一个激活器,其start()
方法如下所示:
package com.infoq.amazon; import org.osgi.framework.*; import com.infoq.templater.api.*; import java.util.*; public class Client implements BundleActivator { public void start(BundleContext context) { Map data = new HashMap(); data.put("tld", "co.uk"); // or "com" or "de" or ... data.put("isbn", "1411609255"); // or "1586033115" or ... System.out.println( "Starting\n" + TemplaterFactory.getTemplater().template( "http://www.amazon.{tld}/dp/{isbn}/", data)); } public void stop(BundleContext context) { } }
我们需要在清单中定义要显式导入templater API,否则我们的捆绑包将无法编译。 我们可以使用Import-Package
或Require-Bundle
来指定依赖项。 前者允许我们单独导入软件包; 后者将隐式导入捆绑软件中所有导出的软件包。 (多个包和捆绑包以逗号分隔。)
Bundle-ManifestVersion: 2 Bundle-SymbolicName: com.infoq.amazon Bundle-Version: 1.0.0 Bundle-Activator: com.infoq.amazon.ClientImport-Package: org.osgi.framework Require-Bundle: com.infoq.templater
请注意,在前面的示例中,我们已经在使用Import-Package
导入org.osgi.framework
。 在这种情况下,我们演示了使用Bundle-SymbolicName
的Require-Bundle
。 我们同样可以将其添加为Import-Package: org.osgi.framework, com.infoq.templater.api
。
无论我们如何声明对templater
包的依赖,我们都只能访问单个导出的包com.infoq.templater
。 尽管客户端可以通过TemplaterFactory.getTemplater()
访问模板程序,但我们不能直接从internal
包访问该类。 这使我们能够灵活地在将来改变实施方式而不会破坏客户。
测试系统
任何OSGi应用程序实际上只是一组捆绑软件。 在这种情况下,我们需要编译和打包捆绑包(如前所述),启动OSGi引擎,然后安装两个捆绑包。 这是Equinox的外观:
java -jar org.eclipse.osgi_* -console osgi> installfile:///tmp/com.infoq.templater-1.0.0.jar Bundle id is 1 osgi> install file:///tmp/com.infoq.amazon-1.0.0.jar Bundle id is 2 osgi> start 2 Starting http://www.amazon.co.uk/dp/1411609255
Amazon客户端捆绑包已启动; 然后,它使用先前的(公认的硬编码)值为我们实例化了URI模板。 然后在捆绑包本身启动期间将其打印出来,以确认捆绑包可以正常工作。 显然,一个真实的系统不会那么僵硬; 但可以在任何其他应用程序中使用Templater服务(例如,在基于Web的应用程序中生成链接)。 将来,我们将在OSGi上下文中研究Web应用程序。
版本化依赖
本期文章的最后一点是要注意,我们目前拥有的依赖项是未版本化的。 或者说,我们将可以使用任何版本。 捆绑包和单独的软件包都可以进行版本控制,并且次要编号的增加用于表示新功能(但保持向后兼容)。 的org.osgi.framework
包,例如,是版本1.4.0中的OSGi R4.1和1.5.0中的OSGi R4.2。 (顺便说一句,这是将捆绑软件版本和市场营销版本保持为独立概念的一个很好的理由,这是Scala语言尚未学习的内容 。)
要声明对特定版本的依赖关系,我们必须在Import-Package
或Require-Bundle
表达它。 例如,我们可以指定Require-Bundle: com.infoq.templater;bundle-version="1.0.0"
表示要使用最低版本1.0.0才能正常工作。 同样,我们可以对Import-Package: com.infoq.templater.api;version="1.0.0"
进行相同的操作Import-Package: com.infoq.templater.api;version="1.0.0"
–但请记住, 软件包的版本与捆绑软件的版本不同。 如果未指定版本,则默认为0.0.0,因此,除非您具有相应的Export-Package: com.infoq.templater.api;version="1.0.0"
否则将无法解析此导入。
也可以指定版本范围。 例如,OSGi版本号的常规含义是,主编号的增加表示向后兼容性的变化,因此我们可能只想将自己限制在1.x范围内。 为此,我们可以表示一个(bundle-)version="[1.0,2.0)"
依赖关系约束。 在这种情况下,[表示“包含”,而)表示“专有”,换句话说,即“从1.0到但不包括2.0以后”。 实际上,表示依赖性约束为“ 1.0”与“ [1.0, ∞)”相同, 换句话说,大于1.0。
尽管它不在本文讨论范围之内,但有可能一次在OSGi系统中具有捆绑软件的多个版本。 如果您有一个依赖于API 1.0版的旧客户端,又有一个依赖于API 2.0版的新客户端,这可能会很有用。 只要每个捆绑软件的依赖关系是一致的(换句话说,您就没有一个捆绑软件试图同时或直接或间接导入1.0和2.0),则应用程序将运行良好。 作为读者的练习,您可以创建2.0版的Templater API以使用泛型,然后创建一个仅依赖于1.x版以及使用2.x版的客户端。
摘要
在本文中,我们研究了开源OSGi引擎Equinox,Felix和Knopflerfish,并创建了一对相关的捆绑软件。 我们还涉及版本化的依赖项。 目前,这种模块化是静态的。 我们还没有探索OSGi的任何动态特性。 我们将在下一期中讨论它。
可安装的捆绑软件(也包含源代码):
java 静态模块