Jenkins插件编写(二)

重要概念

上一节我们讨论了Jenkins的插件项目的创建和运行,而这一节,我们将会讨论Jenkins插件编写的几个重要概念。

@Extension 和 ExtensionPoint

Jenkins的插件之所以这么丰富和强大,很大原因上是因为Extension和ExtensionPoint的设计。

ExtensionPoint定义了一系列的切入点,它可以表示项目构建的不同的阶段,可以是表示某个事件,可以表示某个特定类型,可以表示某个位置。

通过实现不同的ExtensionPoint,我们可以灵活控制插件的执行。ExtensionPoint不仅是只能有Jenkins提供,不同的插件也提供了自己的ExtensionPoint。我们自己也可以定义一个ExtensionPoint然后找到其它人在其它插件对我们的ExtensionPoint的实现。

ExnteionPoint 列表

@Extension则是让我们的类被Jenkins自动登记(类似于Spring中的@Bean),Jenkins依据ExtensionPoint为我们控制插件的执行。

下面是上一节中的项目原型的代码。

1
2
3
public class HelloWorldBuilder extends Builder implements SimpleBuildStep {
//...
}
1
2
3
4
5
6
7
8
9
10
:::表示继承,---表示实现

HelloWorldBuilder
::: Builder(Class)
::: BuildStepCompatibilityLayer(Class, Abstract, Deprecated)
--- BuildStep
--- ExtensionPoint(Interface) <====================
--- Describable(Interface)
--- SimpleBuildStep(Interface)
::: BuildStep(Interface)

从上面的类关系图中,我们看到HelloWorldBuild实现了BuildStep这个ExtensionPoint,表示这个类的可能会在项目的构建,运行和完成期间被一次或多次调用。

Jenkins会找到这个类,依据它的ExtensionPoint在合适的时间帮我们调用代码。

1
2
3
4
5
6
7
8
@Override
public void perform(Run<?, ?> run, FilePath workspace, Launcher launcher, TaskListener listener) throws InterruptedException, IOException {
if (useFrench) {
listener.getLogger().println("Bonjour, " + name + "!");
} else {
listener.getLogger().println("Hello, " + name + "!");
}
}

上面这段就是这个ExtensionPoint中会被Jenkins执行的代码,在我们配置好name后会在Console Output中输出Hello, [name]

但是有个问题,之前我们说过,Jenkins要自动登记我们的类,类必须有@Extension这个注解。但是这个类上并没有这个注解。

这里又有一个重要的概念。

Descriptor和Describable

Descriptor包含一个Describable对象的元数据,同时充当这个Describable对象的工厂类。

在Jenkins依据我们的代码创建一个实现了ExtensionPoint的Extension时候,要对这个对象进行配置(就像上一节填入name那样)和其它操作。Descriptor可以对我们的配置进行检验,依据我们的配置创建出对象,为我们的对象设置各种元属性。

1
2
3
4
5
6
7
8
9
10
11
12
@Symbol("greet")
@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
public FormValidation doCheckName(@QueryParameter String value, @QueryParameter boolean useFrench) {
//...
}

@Override
public String getDisplayName() {
return Messages.HelloWorldBuilder_DescriptorImpl_DisplayName();
}
}

如上面代码所示,在这个类上使用@Extension后,Jenkins就会通过这个Descriptor工厂类创建HelloWorldBuild(如上面类图所示我们的HelloWorldBuild实现了Describable),并且可以为这个BuildStep检验参数,设置Display Name(上一节中的Say hello world)和其它属性。

但是上一节中我们看到的配置的表单在哪呢?它又是如何映射到我们的对象的字段呢?

Config

src\main\resources\org\jenkinsci\plugins\sample\HelloWorldBuilder\中我们可以看到一个名字是config.jelly的文件。
通过在resources中的和类对应的目录下创建config.jelly文件,就可以为我们的类提供配置的表单。(在同级目录下我们可以看到一些properties文件,这些是用来做本地化用的)

1
2
3
4
5
6
7
8
9
10
11
12
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="${%Name}" field="name">
<f:textbox />
</f:entry>
<f:advanced>
<f:entry title="${%French}" field="useFrench"
description="${%FrenchDescr}">
<f:checkbox />
</f:entry>
</f:advanced>
</j:jelly>

Jenkins使用Apache Jelly编写UI。从上面我们可以看出,通过目录对应一个类,通过field属性对应一个字段,这样对一个对象进行配置。

后面我们将会从头创建一个简单的记录code coverage结果并显示出来的插件。