Learn Jenkins the hard way (1) - 从源码运行Jenkins开始

前言

在上一篇文章中,总结了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模板的机制进行讨论。

时间: 2024-10-01 00:24:26

Learn Jenkins the hard way (1) - 从源码运行Jenkins开始的相关文章

网上下载了一个asp.net的源码运行都没问题,可是sql数据库内容不改变咋办

问题描述 网上下载了一个asp.net的源码运行都没问题,可是sql数据库内容不改变咋办?求帮助,,急用! 解决方案 解决方案二:运行后什么功能都能实现,但是在页面更新的内容后,数据库不能同步更新..解决方案三:既然是网上下载来的源代码,我估计你需要根据你的实际情况,重新配置一下数据库连接字符串.这玩意儿,一般都在web.config这个文件里.在VS里打开web.config,本质是个XML文件.在其中找到connection之类的,然后根据你的实际情况修改一下连接字符串吧.至于如何编写连接字

Linux有问必答:怎么用CheckInstall从源码创建一个RPM或DEB包

Linux有问必答:怎么用CheckInstall从源码创建一个RPM或DEB包 问题:我想要从源码创建安装的软件包.有没有一种方式从源码来创建和安装软件包,而不是运行"make install"?这样的话,以后如果我想,我可以容易的卸载程序. 如果你已经从它的源码运行"make install"安装了linux程序.想完整移除它将变得真的很麻烦,除非程序的开发者在Makefile里提供了uninstall的目标设置.否则你必须在安装前后比较你系统里文件的完整列表,

分享非常漂亮的WPF界面框架源码及其实现原理

原文 http://www.cnblogs.com/baihmpgy/archive/2013/05/09/3068370.html 在上文<分享一个非常漂亮的WPF界面框架http://www.cnblogs.com/baihmpgy/archive/2013/05/06/3062220.html>中我简单的介绍了一个界面框架,有朋友已经指出了,这个界面框架是基于ModernUI来实现的,在该文我将分享所有的源码,并详细描述如何基于ModernUI来构造一个非常通用的.插件化的WPF开发框架

深入perf4j源码

前言 引用一老程序员同事的一句话:"项目做到最后就是监控了."在一天和那同事打电话聊天时他突然冒出来的一句话.后来我仔细回味这句话,越来越觉得挺有道理的.自己在现在的项目里就做了好几个监控相关的任务,而且也一直在想办法获取更多的监控数据,如每个进程内存使用情况.线程使用状态.某些方法的性能等.不过这句话只说了一半,监控是为了获取数据,但是有了数据后还要根据这些数据来做相应的决策,比如判断机器.进程的健康状况,如何改进系统等.最近对性能调优特别感兴趣,但是在性能调优前首先要收集性能数据,

Vue.js源码(2):初探List Rendering

下面例子来自官网,虽然看上去就比Hello World多了一个v-for,但是内部多了好多的处理过程.但是这就是框架,只给你留下最美妙的东西,让生活变得简单. <div id="mountNode">      <ul>          <li v-for="todo in todos">            {{ todo.text }}          </li>      </ul>  <

深度剖析ConcurrentHashMap源码

概述 你可能会在一些技术类的书籍上看到下面这样一段关于HahsMap和Hashtable的表述: HashMap是非线程安全的,Hashtable是线程安全的. 不知道大家有什么反应,我当时只是记住了,知道面试的时候能回答上来就行了-至于为什么是线程安全的,内部怎么实现的,却不怎么了解. 今天我们将深入剖析一个比Hashtable性能更优的线程安全的Map类,它就是ConcurrentHashMap,本文基于Java 7的源码做剖析. ConcurrentHashMap的目的 多线程环境下,使用

源码-现在Android手机都带有云服务,是怎么实现的

问题描述 现在Android手机都带有云服务,是怎么实现的 现在Android手机都带有云服务,是怎么实现的,,是对Android底层源码的改造吗 解决方案 http://www.imooc.com/learn/254 解决方案二: 不需要,只要对某些上层api进行改造,加上云端存储就可以了. 解决方案三: 题主,你可以百度一下Bmob或者七牛

Mybatis源码分析之存储过程调用和运行流程_java

这一篇我们学习一下Mybatis调用存储过程的使用和运行流程.首先我们先创建一个简单的存储过程 DELIMITER $ CREATE PROCEDURE mybatis.ges_user_count(IN age INT, OUT user_count INT) BEGIN SELECT COUNT(*) FROM users WHERE users.age=age INTO user_count; END $ 这个存储过程的含义其实比较简单的,就是输入age,然后执行select count(

Eclipse查看Hadoop源码

1.开发环境 1.Hadoop-1.2.1 2.apache-ant-1.8.0 2.新建Java项目 项目叫"Hadoop_sourcecode" 3.拷贝Hadoop中源码 Hadoop包中src文件文件夹下core.hdfs.mapred文件夹拷贝到项目的src中 4.改变目录结构 删除原来的目录结构:   增加新的目录结构:        选定后的目录结构      5.添加Jar包 需要包含进来的jar包: "\hadoop-1.2.1\lib"中所有ja