有时插件需要使用Java平台以外的Java库和Jenkins本身。
例如,连接到特定服务的插件可能会使用供应商提供的Java SDK。
这样做原则上非常简单。
只需简单声明Maven依赖该库:
<dependency>
<groupId>com.yoyodyne.cloud</groupId>
<artifactId>cloud-access-sdk</artifactId>
<version>1.0</version>
</dependency>
(假设该存储库在Maven中心可以得到。
如果不能,可以将工件上传到Jenkins Artifactory存储库以供插件使用。
请向开发者名单寻求帮助。
*not*试图在源代码控制中保留这样的二进制文件。
除了在编译期间提供SDK类(比如+com.yoyodyne.cloud.*)之外,
用于创建Jenkins插件的+maven-hpi-plugin+会注意到依赖关系本身并不是一个Jenkins插件,
而不是_bundle_里面 +yourplugin.hpi
作为 WEB-INF/lib/cloud-access-sdk-1.0.jar
。
在运行时,插件类加载器将从+WEB-INF/lib/cloud-access-sdk-1.0.jar+加载类,
就像从+WEB-INF/lib/yourplugin.jar+(你的插件自己的代码,从+src/main/java/+ 和 src/main/resources/
)一样。
因此你的插件的类可以引用该库中的类。
其他插件依赖于您的插件也可以。
为junk检查 WEB-INF/lib/*.jar
注意Maven依赖包括 all transitive 依赖。
捆绑库时可能会导致意外的结果。
例如 com.yoyodyne.cloud:cloud-access-sdk+的POM可能会声明它需要 +commons-net:commons-net:3.5
。
你的插件也会因此捆绑+commons-net-3.5.jar+ 。
如果你不小心,+WEB-INF/lib/+可能会填满数兆字节的东西,但实际上并未使用。
使用库包装插件
在实践中,Jenkins功能插件捆绑各种第三方库是不可取的。
通常,其他插件需要一些相同的库,因此多个插件将捆绑拷贝。
即使所有这些副本碰巧是完全相同的版本,“下游”插件也可能导致链接错误。
用户将结束下载相同代码的多个副本,
增加产品和+$JENKINS_HOME/plugins/+大小,加载更新中心,延迟HotSpot编译器等。
另一个问题是,更新库版本在多个插件中独立捆绑时变得难以集中。
虽然每个插件定义一个库的确切版本确实可以降低API兼容性错误的风险,
但是当新的安全漏洞(或其他重要修补程序)宣布时,保证更新库是不重要的。
为了集中化图书馆管理,您可以定义一个_wrapper_插件,或者找到其他人已经定义的插件。
这是一个Jenkins插件,它不包含它自己的源代码,只是在某些库上包含+dependency+。
在更新中心发布后,其他“功能”插件可以声明简单的插件到插件的依赖于它,从而使用该库。
偶尔,在包装器插件本身中包含一些API类是有意义的,
任何使用该库的Jenkins代码都会合理地需要一些样板适配器来适应标准的Jenkins设施。
为了降低使用不兼容的更改中断插件功能的库插件更新的风险,
建议在封装插件的+artifactId+中包含库的主要版本,例如+commons-lang3+。
(假设库遵循类似http://semver.org/ [语义版本])的一些东西。)
因此可能会同时加载多个主要版本。
当然,这意味着关键修复必须作为所有发行版本的更新发布,因此只能为库的_supported_版本执行此操作。
pluginFirstClassLoader
和它的不满
Java类加载器以及Jenkins插件类加载器的默认行为,
是首先通过向父装载器请求该名称的类来服务+loadClass+请求,
并且只有当它不能检查该类是否可以在启动加载器中存在的JAR之间定义时。
这通常是合理的,因为它可以确保来自不同的插件的字节码中提到的类型
在运行时解析为相同的+Class+对象,从而允许API使用共享类型签名工作。
然而,有时候并不希望父母先被搜索。
例如,Jenkins核心目前捆绑了大量的第三方库,并将其所有软件包公开给插件。
对于这个问题,一些插件绑定了第三方库,然后偶然暴露出来
到需要依赖捆绑插件中定义的API的其他插件。
如果一个插件也需要一些相同库的变体供它自己使用,它会令人惊讶地无法加载它,
因为父类加载器会先找到它。
为了避免这种行为,可以在插件的Maven POM中设置一个标志 pluginFirstClassLoader
。
(这只是生成一个JAR清单条目,在运行时由Jenkins插件系统解释。)
该标志指示插件类加载器为任何提及的类名称_first_ 检查其自己的JAR。
使用此模式时必须非常小心,因为任何通常在核心(或“上游”插件)中定义的类型,
在您调用的方法的Java签名的任何部分中都提到了这一点,因此它们不得在您的JAR中重复使用,否则会导致链接错误。
因此,最好保留给纯粹由内部实现使用的库。
一个更加有限的标志是+maskClasses+,它只阻塞来自父装载器的选定类或包,而不是所有东西。
您必须在Java链接器的传递闭包下手动验证被屏蔽的类是否完整:
例如,从捆绑在Jenkins核心的库中屏蔽一个软件包而不是另一个软件包可能会使被屏蔽的软件包中的类无法解析。
有一个相关标志+globalMaskClasses+,用于调整_every loaded plugin_的行为,以基本覆盖Java平台的组件。
如果您不理解本节的任何部分,请不要使用这些选项。 即使你做了,也要三思。
Shading
另一种独揽库依赖关系和类加载复杂性的方法大致被称为_shading_,
以 Maven Shade插件为例(虽然可能有多种技术)。
在这种情况下,不是试图控制给定的+classLoader.loadClass("org.apache.commons.lang.StringUtils")+ 调用找到定义的加载器的位置,
这个想法是将整个库捆绑在一个独立的包前缀下,重写所有静态和反射类(或资源)负载,
既可以在库中,也可以从插件中定义的代码进行捆绑。
因此+your-plugin.hpi!/WEB-INF/lib/commons-lang-shaded.jar+ 可能包含 +org/jenkinsci/plugins/yourplugin/commonslang/StringUtils.class+等条目;
和插件源代码一样
import org.apache.commons.lang.StringUtils;
// …
if (StringUtils.isEmpty(arg)) {/* … */}
会导致+your-plugin.hpi!/WEB-INF/lib/classes.jar+ 中的字节码在其常量池中引用+org.jenkinsci.plugins.yourplugin.commonslang.StringUtils+类型。
由于该类型名称在整个Jenkins JVM中是唯一的,因此不存在从错误位置加载的风险;
从Jenkins的角度来看,就好像你将整个Commons Lang库复制并粘贴到插件的一些源中。
虽然这个系统确实解决了链接错误的风险,但它并没有减少Jenkins中库的版本数量,
如库封装器一节所述。
然而,在某些情况下,库包装器本身也会将其用于官方目的,
以便于这样封装的库就会出现在一个包名称下,表明主要的发行版本。
因此,使用包装库的插件会引用重新包装的名称:
import org.jenkinsci.commons_lang2.StringUtils;