3

jenkins源码阅读:启动和请求处理

 2 years ago
source link: https://myzhan.github.io/posts/jenkins-src-entry-point/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

jenkins源码阅读:启动和请求处理

calendar Jul 16, 2016
clock 2 min read

jenkins 编译后,会生成一个 jenkins.war 文件,阅读 jenkins 源码,我们需要研究的一切,都在这个文件里面了。按照习惯,先寻找入口点,然后顺藤摸瓜,才能进入源码树中。

如果 jenkins 独立运行,启动命令是:

$ java -jar jenkins.war

所以 jenkins.war 其实是一个普通的 jar 包。java 命令运行一个 jar 文件,会在 jenkens.war/META-INF/ 目录下,找 MANIFEST.MF 文件。这个文件里面有入口类,在这里,是 Main,即是 jenkins.war/Main.class 文件。

jenkins.war/META-INF/MANIFEST.MF

$ cat MANIFEST.MF
Manifest-Version: 1.0
Jenkins-Version: 2.13-SNAPSHOT
Implementation-Version: 2.13-SNAPSHOT
Built-By: myzhan
Build-Jdk: 1.7.0_79
Hudson-Version: 1.395
Created-By: Apache Maven 3.3.3
Main-Class: Main
Archiver-Version: Plexus Archiver

反编译一下 Main.class 文件,一个标准的 Main 文件,属于 default 包。但是这个 Main 文件,并不在 jenkins 的源码里面,即不在 jenkins core 里面。过了一遍反编译的源码,发现它先将 jenkins.war 解压到 $JENKINS_HOME/war 目录下,再启动一个 winstone 容器。鉴于jenkins 本身是一个 Servlet 程序,我猜测,在这个入口,是先启动一个 Servlet 容器,再加载 jenkins 本身。

通过查看 $JENKINS_SRC/war/pom.xml 文件,发现这个 Main 文件,在 org.jenkins-ci:executable-war 这个依赖中,验证了我的猜测。

既然是一个 Servlet 程序,那我们再去 $JENKINS_HOME/war/WEB-INF/web.xml 找入口点。与 Servlet 有关的配置,在这里。

$JENKINS_HOME/war/WEB-INF/web.xml

<servlet>
    <servlet-name>Stapler</servlet-name>
    <servlet-class>org.kohsuke.stapler.Stapler</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>Stapler</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

在这里,jenkins 将所有 URL 请求的处理,都交给了 Stapler。根据 Stapler 的文档,了解到它提供了 URL 到对象的映射。即所有请求,都先交给 Stapler 处理,然后 Stapler 通过反射的方式,找到对应的处理类及方法。

顺着 web.xml 往下看,终于找到了与 jenkins core 有关的配置。

$JENKINS_HOME/war/WEB-INF/web.xml

<listener>
    <listener-class>hudson.WebAppMain</listener-class>
</listener>

WebAppMain.java 文件,在 $JENKINS_SRC/core/src/main/java/hudson 目录下面,文件开头的注释,解释了它的身份。

$JENKINS_SRC/core/src/main/java/hudson/WebAppMain.java

/**
* Entry point when Hudson is used as a webapp.
*
* @author Kohsuke Kawaguchi
*/

这个类的代码大约 400 行,大多是一些初始化工作,下面这段代码引起了我的注意。

$JENKINS_SRC/core/src/main/java/hudson/WebAppMain.java

context.setAttribute(APP, new HudsonIsLoading());
initThread = new Thread("Jenkins initialization thread") {
    @Override
    public void run() {
        Jenkins instance = new Hudson(_home, context);
        context.setAttribute(APP, instance);
    }
};
initThread.start();

熟悉 jenkins 的同学,到了这里有没有一点似曾相识的感觉? jenkins 在刚启动的时候,会显示一个 loading 页面,启动完成后,才切到主页,就是靠这段代码实现的,HudsonIsLoading 就是 loading 页面,Hudson 就是主页。

现在看来 context.setAttribute(APP, xxxx) 这个方法调用,能把 xxxx 对象挂到 “/” 这个 URL 下面。不过 context.setAttribute 方法,在 Servlet 编程里面,只是保存了一个变量而已,为什么能挂到 “/” 下面呢?答案一定在 Stapler 的源码里!从 web.xml 配置我们可以知道,入口在 org.kohsuke.stapler.Stapler。继续查看 Stapler 的源码,我找到了答案。

/org/kohsuke/stapler/Stapler.java

protected @Override void service(HttpServletRequest req, HttpServletResponse rsp) {
    Object root = webApp.getApp();
    if(root==null)
        throw new ServletException("there's no \"app\" attribute in the application context.");
    invoke( req, rsp, root, servletPath);
}

/org/kohsuke/stapler/WebApp.java

/**
* Returns the 'app' object, which is the user-specified object that
* sits at the root of the URL hierarchy and handles the request to '/'.
*/
public Object getApp() {
    return context.getAttribute("app");
}

跟踪到这里,jenkins 的启动,和处理请求的过程,已经清楚。总结一下,主要是下面这几步。

  1. 启动 winstone 容器,加载 jenkins。
  2. Stapler 负责处理请求。
  3. WebAppMain 负责初始化。

最后,写一个 Hello World 庆祝一下,在 Jenkins.java 中,加入以下代码。

$JENKINS_SRC/core/src/main/java/jenkins/model/Jenkins.java

public void doHelloWorld( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
    rsp.getWriter().println("Hello World");
}

重新编译生成 jenkins.war,运行后用浏览器打开 http://localhost:8080/helloWorld 即可看到 Hello World。

注意这里的 URL 是大小写敏感的。

如果想了解 Stapler 的基本用法,可以查看下面这几篇文档。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK