前言
在上一篇文章中,总结了Jenkins的罪与罚。从本文开始,我们将迈入Jenkins的源码学习部分。学习源码的方式有很多种,对于Jenkins而言,网上关于源码的学习非常有限,比较建议大家先阅读官方关于如何成为contributor的文档,了解大体的结构后再逐步深入。
从源码本地运行Jenkins
学习任何源码前,首先要做的事情是将源码跑起来。克隆Jenkins的源码:
git clone https://github.com/jenkinsci/jenkins.git
//可以切换到当前的稳定分支
//git checkout jenkins-2.32.2
Jenkins2.0以上的版本依赖JDK1.7以上,以及Maven3.0.4以上(如果需要本地调试Jenkins还需要安装Node.js)。编译Jenkins可以依照官方的文档:
mvn -Plight-test install
默认情况下Jenkins编译的时候会进行单元测试,导致编译的时间比较长,也可以采用如下的方式skip掉单元测试的构建
mvn clean install -pl war -am -DskipTests
Jenkins是一个比较大的项目,里面有非常多的依赖,因此最好使用一个国内的Maven加速源进行加速,可以在.m2的settings.xml中配置mirror
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
编译完成即可在target目录下找到jenkins.war了,运行如下命令Jenkins就启动起来了。
java -jar jenkins.war
如果希望从IDE里面调试Jenkins,Jenkins支持通过IDE进行远程调试,在Jenkins源代码的war文件路径下执行如下命令
mvnDebug jenkins-dev:run -f war
在IDE中配置远程调试端口
此时在浏览器中输入localhost:8080/jenkins即可访问从源码运行的Jenkins
Jenkins的架构
在正式接触Jenkins的源码前,需要大致理解下Jenkins的架构方式,这样有助于我们选择从什么位置入手代码的学习。Jenkins的整体代码是依托于Stapler来实现的,Stapler是一个将应用程序对象和 URL 装订在一起的 lib 库,使编写 web 应用程序更加方便。Stapler 的核心思想是自动为应用程序对象绑定 URL,并创建直观的 URL 层次结构,下面是一个Stapler架构的示意图。
其实Stapler的原理非常好理解,他将一个对象绑定为Root Object,然后通过通过反射的机制,将不同的路由映射成不同的对象与方法。举一个简单的例子,Hudson实例被映射到了路由的根路径,对于二级路径/project,即会被映射为Hudson对象的一个getProject方法;如果Project下还有下一级的路由比如/project/status,那么getProject就会返回一个带有getStatus方法的对象依次类推。
当我们了解了Stapler的原理后,就可以开始从点到面的开始研究一些Jenkins的实现,然后进一步理解Jenkins的源码了。
摸索Jenkins
虽然我们已经了解了Stapler的原理了,可是对应到Jenkins大量的源码还是很难入手,在这里要介绍一个小窍门,Stapler的某些设计很有意思,可能在设计之初就考虑到了调试的复杂性,Stapler会将路由处理的链路通过Header头的方式进行传递,通过查看链路的传递信息就可以快速找到对应的代码位置与访问链路,下面带大家调试一下Jenkins的首页。访问http://localhost:8080/jenkins/,打开Chrome的Network调试,找到/jenkins的路由tab。
我们来仔细分析右侧的Stapler的trace信息,可以很快的明白页面是如何渲染的。
Stapler-Trace-001:-> evaluate(<hudson.model.Hudson@34079395> :hudson.model.Hudson,"")
Stapler-Trace-002:-> evaluate(((StaplerProxy)<hudson.model.Hudson@34079395>).getTarget(),"")
Stapler-Trace-003:-> evaluate(((StaplerFallback)<hudson.model.Hudson@34079395>).getStaplerFallback(),"")
Stapler-Trace-004:-> evaluate(<hudson.model.AllView@5a0669bd[view/All/]> :hudson.model.AllView,"")
Stapler-Trace-005:-> hudson/model/View/index.jelly on <hudson.model.AllView@5a0669bd[view/All/]>
首先路由到达URL的根即Hudson.module.Hudson对象,然后通过StaplerFallback代理给了hudson.model.AllView对象,最终渲染了hudson/model/View/index.jelly这个文件。在这里需要补充的是,Stapler还有两种机制,一种是StaplerFallback,一种是StaplerProxy,如果对应URL的反射Model类实现了这两个接口,那么他们可以将页面的模板进行代理,或者对象镜像代理,因此如果单纯从Stapler的约定大于配置的机制中来学习Jenkins的源码会带来很多的困扰,因此有很多时候找不到对应的模板文件与类文件,这也就是为什么Stapler的Trace特别重要的原因。
我们回过头来看下Jenkins的首页,找到resources/hudson/model/View/index.jelly
<?jelly escape-by-default='true'?>
<st:compress xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<l:layout title="${it.class.name=='hudson.model.AllView' ? '%Dashboard' : it.viewName}${not empty it.ownerItemGroup.fullDisplayName?' ['+it.ownerItemGroup.fullDisplayName+']':''}" norefresh="${!it.automaticRefreshEnabled}">
<j:set var="view" value="${it}"/> <!-- expose view to the scripts we include from owner -->
<st:include page="sidepanel.jelly" />
<l:main-panel>
<st:include page="view-index-top.jelly" it="${it.owner}" optional="true">
<!-- allow the owner to take over the top section, but we also need the default to be backward compatible -->
<div id="view-message">
<div id="systemmessage">
<j:out value="${app.systemMessage!=null ? app.markupFormatter.translate(app.systemMessage) : ''}" />
</div>
<t:editableDescription permission="${it.CONFIGURE}"/>
</div>
</st:include>
<j:set var="items" value="${it.items}"/>
<st:include page="main.jelly" />
</l:main-panel>
<l:header>
<!-- for screen resolution detection -->
<l:yui module="cookie" />
<script>
YAHOO.util.Cookie.set("screenResolution", screen.width+"x"+screen.height);
</script>
</l:header>
</l:layout>
</st:compress>
此时可以尝试注释掉一些代码,比如注释掉main.jelly的这一行,然后刷新下页面,右侧的主面板已经被屏蔽掉了。
可以通过这种方法,快速对Jenkins感兴趣的页面进行学习、修改。
最后
在本篇文章中,我们讨论了Jenkins的基本的路由机制,以及如何通过调试Stapler Trace的方式快速的学习Jenkins,在下一篇文章中,我们将会对Jenkins中涉及到的关于Jelly模板的机制进行讨论。