译文:OSGi Module依赖

原文作者:David H Nebinger

原文地址:https://web.liferay.com/web/user.26526/blog/-/blogs/osgi-module-dependencies

如有问题,欢迎在原文下面讨论,也欢迎在这里留言。

 

假设在Liferay DXP平台上开发module的时候,遇见了需要运行环境(runtime)的依赖的时候,该怎么办?

在这片简短的文章中,我会介绍几种方法...

假设你有一个module需要iText(和iText的依赖)作为依赖。这其实和module本身的功能关系不是很大,但是你现在有这个依赖,需要一种方法来使用它。

 

方法 1 - 放在全局目录下

这种方法最简单也最粗暴。所有在全局类加载器(比如tomcat的lib和lib/ext)中的类可以被所有类访问,包括Liferay OSGi容器。

但是全局的jar有全局的问题。不仅所需要的jar需要在全局目录,所有jar的依赖也需要在全局目录。并且全局类只有一个版本,其他的消费类无法使用不同的版本。

 

方法 2 - 让OSGi处理

 

这个是第二简单的方法,但是可能无法使用。如果你在module中声明一个运行环境的依赖,并且OSGi中有一个bundle可以满足依赖的话,module会使用使用这个依赖。

当你确认OSGi中依赖可以被使用的时候,这个方法就可以使用。因为这个方法是利用portal中已有的依赖,或者你之前已经部署到OSGi容器中的依赖(有些jar可能已经包含了OSGi bundle的信息,可以直接部署到容器中)。

例如,假设我们要声明iText依赖,虽然iText应该不会作为bundle已经部署到了OSGi中,所以如果依赖OSGi容器来使用iText很可能会出错的。现在仅是用作举例

使用build.gradle文件来声明运行环境依赖。这段代码是用来声明iText运行环境依赖的

 

runtime group: 'com.iowagie', name: 'itext', version: '1.4.8'

 

如果iText(和其依赖)已经成功的部署到OSGi容器中,通过运行环境依赖声明就可以在你自己的module中使用了。如果iText不可用,你的module就不会启动,并且会报错 -- 依赖无法满足。

 

方法 3 - 制作成一个超级胖子Module

 

像那些巨型jar一样,巨型module会拥有所有依赖的类直接暴露在module jar中。

使用Gradle和BND很好实现。

在build.gradle中,你应该像方法2中一样声明运行环境依赖。

并且通过在bnd.bnd中包含所需的资源使module成为一个巨型module:

Include-Resource: @itext-1.4.8.jar

在这里引入依赖jar,一般在gradle下来依赖或者浏览maven库的时候都可以看到具体版本。

要注意的是,也需要引入依赖所以依赖的jar。例如,iText2.08依赖于BouncyCasle mail和prov,所以这些依赖也需要添加:

Include-Resource: @itext-2.0.8.jar,@bcmail-138.jar,@bcprov-138.jar

也需要在build.gradle中添加这些依赖,以便gradle引入这些jar。

如果使用zip工具打开module jar包的话,会看见所有依赖的jar会被解压,class文件会被直接放在module jar包中。

 

方法 4 - 在Module中引入整个jar

 

最后的方法是在module中引入jar,和巨型module不一样,这个方法将整个jar包含到module jar中

和方法2,3相似的是,也需要在build.gradle中声明运行环境依赖。

引用jar是在bnd.bnd中完成的。

首先需要定义Bundle-ClassPath属性来引入module jar和其余的依赖jar。在下面的例子中,我指定iText jar会包含在module jar中:

 

Bundle-ClassPath:\
  .,\
  lib/itext.jar

这里我们不使用Include-Resource声明,而是使用-includeresource来-将jar引入到bundle中:

-includeresource:\
  lib/itext.jar=itext-1.4.8.jar

在这里,我们将itext-1.4.8.jar包含到module中,保存为lib/itext.jar。itext-1.4.8是通过Gradle导入的运行环境依赖。

这种语法也支持通配符,可以有效利用build.gradle选择版本的特性。这个例子是用来包含任何版本的commons-lang:

-includeresource:\
  lib/itext.jar=itext-1.4.8.jar,\
  lib/commons-lang.jar=commons-lang=[0-9]*.jar

如果使用压缩软件打开module的jar文件的话,可以看到jar文件会在lib文件夹下。

 

总结

实际项目中应该使用哪种方法?和Liferay开发一样,因需而异。

全局方法适用于只需要一种版本的jar包并且需要用到大量的依赖的情况。例如,项目中有20个不同的module全部依赖于iText 1.4.8,这样,全局方法就是最好的选择。

第二种方法只用于依赖jar是OSGi bundle的情况。在这种情况下,你可以使用不同的版本,并且不用担心去编辑bnd文件。

第三种和第四种方法是最常用的放法。在上面这些情境下,你的依赖是包含在module中的,OSGi类容器中的类是不会被不同版本的ja“污染”的。并且,module也是不依赖于当前容器环境的,因为module自己已经包含了所有的依赖,运行环境也不需要为module事前准备依赖。

我个人很喜欢使用第四种方法,以为巨型jar在解压缩类的时候,可能会有路径上的交错(例如xml和配置文件)。第四种方法就不会有这样的问题。

 

希望你喜欢

 

译者补充:

在使用方法3,4的时候,OSGi容器会要求导入要引用jar的所有依赖,这样的结果是,需要导入无尽的包,因为依赖还有依赖,依赖还有依赖,子子孙孙无穷匮也。

所以,需要通过bnd.bnd来声明那些包在容器中不需要导入。

例如在我使用google-gwt servlet jar的时候。我需要导入这个包:

Bundle-ClassPath:\
    .,\
    lib/gwt-servlet.jar,\
    lib/gson.jar

-includeresource:\
    lib/gwt-servlet.jar=gwt-servlet-2.8.0.jar,\
    lib/gson.jar=gson-2.8.0.jar
 
但是却需要额外声明,这些包我不需要import
Import-Package: \
    !com.google.gwt.xhr.client.*,\
    !com.google.gwt.core.*,\
    !com.google.gwt.dev.*,\
    !com.google.gwt.i18n.*,\
    !com.google.gwt.json.*,\
    !com.google.gwt.junit.*,\
    !com.google.gwt.thirdparty.*,\
    !com.google.gwt.uibinder.*,\
    !com.google.gwt.user.*,\
    !com.google.gwt.util.*,\
    !jsinterop.*,!javax.annotation.*,!javax.imageio.*,!javax.lang.model.*,\
    !javax.tools.*,!javax.validation.*,!org.apache.*,!org.objectweb.*,\
    !org.w3c.*,!sun.*,!junit.framework,\
    *

其中!是不导入的意思。最后的*是指导入所需要并且在OSGi容器中能导入的。