.net解析传过来的xml_Tomcat源码构建以及架构解析

bebab3d2b6d860cc7686e69686b5a9e0.png

       周末两天时间,太无聊了。于是我就在想作为一个优秀的程序开发者,我们不仅需要对自己编写的程序有深刻的理解和认识,同样的我们还需要对我们编写的程序的运行环境有一个彻底的认识,这样我们在能在这条路上走的更远。所以我用了这两天的时间亲自撸了一边tomcat的源码,下面和大家一起分享本次的成果。

      相信大家对tomcat都很熟悉,目前也是中国大陆使用的较多的一种开源的Web容器。对于Tomcat的基本使用这里不做过多的介绍。具体的下载和配置可以查看我在2018年写的https://blog.csdn.net/qq_38701478/article/details/85003063  这篇文章。

     好了我们进入今天的主题,首先准备好源码,这里使用的版本是tomcat8.5-42的版本。关于源码的下载大家可以去官网(http://tomcat.apache.org/)下载 。下载完成之后解压源码包,如下图所示:

b04c983c3c7d22f61f03946426c92470.png

   接下来我们进入到源码解压的路径下,创建一个home目录,并且将conf和webapps目录移动到home目录中,如下图所示 :

ecc8c1a0572a377b2c97dd320d3d02a5.png

home目录中:

9d000f2995fa5a919ee2ac61da788433.png

接下来 我们就开始使用maven来构建一把tomcat的源码 ,首先我们在apache-tomcat-8.5.42-src目录下创建一个pom.xml文件,该文件中加入tomcat所需要的依赖,如下所示:

<?xml version="1.0" encoding="UTF-8"?>         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    4.0.0    org.apache.tomcat    apache-tomcat-8.5.42-src    Tomcat8.5    8.5            Tomcat8.5        java                                           java                                                               org.apache.maven.plugins                maven-compiler-plugin                2.3                                    UTF-8                    1.8                    1.8                                                                junit            junit            4.12            test                            org.easymock            easymock            3.4                            ant            ant            1.7.0                            wsdl4j            wsdl4j            1.6.2                            javax.xml            jaxrpc            1.1                            org.eclipse.jdt.core.compiler            ecj            4.5.1            

好了,接下来 我么打开idea,选择导入源码项目 ,按以下步骤导入即可:

65b0e250c6086c5628fb403ecc1ff8e0.png

45914075d119edcc7538a37a443aca0c.png

我们选中pom文件 点击ok即可将源码导入到idea中 ,如下图所示:

7f5b1ccc4d0b73b5911fe1b386918a51.png

我们点击最左边的project, 就可以看到tomcat源码的结构 如下图:

2b0539ad8c92bdaf5d54c26d99d7c406.png

我们都知道Java程序都有一个入口,也就是我们常说的main方法 ,好了我们直接来全局搜索一下main方法的位置

3de7f833a8bd5096f4c85b82e56816fb.png

似乎和我们像的不太一样,emmm.........好趴,想想其他的办法  我们平时在启动tomcat的时候都是执行bin目录下的startup.sh文件(windows下是startup.bat文件)。那么我们就来看看 这个文件吧,看看能不能发现什么有用的东西。

6dadfcf2774fcf7a6bbefdc21d8cc52c.png

我们在这个文件中没有发现什么有用的信息,但是 在第56行发现了该文件执行了catalina.bat这个脚本 ,好吧 我们继续来查看catalina.bat这个脚本。

0e174a0b03af8cc7d689b8f99351fbeb.png

功夫不负有心人 我们在第257行发现了有一个可疑的地方MAINCLASS这个变量的值是org.apache.catalina.startup.Bootstrap。看起来这个就是启动类啊。我们打开这个类瞅一眼:

8bed41950cdb802cafd47f0c55fe7823.png

果然我们发现了熟悉的main方法。好吧 我们先来执行一把

33cf1e22079c14536ab4f0c3aeb32fd7.png

我么发现程序启动报错了,原因是找不到配置文件server.xml。好吧 我们来编辑一下启动需要的配置,按以下步骤操作:

8cc9d022037b151ae61cbd1d2c123066.png

d43c25b0294e1c0e23468ee7bbddb04d.png

在虚拟机参数选项栏中加入以下配置即可:

-Dcatalina.home=D:/projec/wxzhh_src/apache-tomcat-8.5.42-src/home-Dcatalina.base=D:/projec/wxzhh_src/apache-tomcat-8.5.42-src/home-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager-Djava.util.logging.config.file=D:/projec/wxzhh_src/apache-tomcat-8.5.42-src/home/conf/logging.properties

这里的路径根据自己的实际情况修改 我存放源码的路径是D:/projec/wxzhh_src。好了 我们再来启动一把试试:

3169c798322aadb5584015c3c190a997.png

我们发现好像启动成功了,熟悉的8080端口已经在监听的状态了,于是我们打开浏览器 访问一下试试:

cec332a91d7897805bbbeec39c5af046.png

错误来的很突然,突然报500了,还是我们熟悉的空指针异常!Unable to compile class for JSP。不能编译JSP文件 ,好吧 肯定是JSP解析器出问题了,书上给的解释是是我们直接启动org.apache.catalina.startup.Bootstrap的时候没有加载JasperInitializer,从而无法编译JSP。解决办法是在tomcat的源码ContextConfig中的configureStart函数中手动将JSP解析器初始化

5e312f30cd59065bdfde53c6d36452e3.png

我们在webConfig();方法之前加上一行代码即可:

context.addServletContainerInitializer(new JasperInitializer(), null);

好吧 我们再继续启动项目,然后再打开浏览器访问试试 

604618e827766266bbba27f94fa1c66d.png

     OK了!!我们成功的构建了一把Tomcat的源码,下面我们就来看看Tomcat的项目架构。      首先我们都知道,tomcat是一种web容器,主要用来发布我们的Web项目,用户可以通过Http请求来访问我们发布的项目。关于HTTP协议这里不做过多的介绍,相信大家也很熟悉,我们重点介绍Servlet容器。首先我们来看这张图

c41c164d3c6ea8014d96059e9c137e51.png

       在Tomcat 内部逻辑上主要是有两个部分组成,一部分是接受客户端的HTTP请求的HTTP服务器,另一部分是处理Servlet的Servlet容器,HTTP服务器不直接处理业务请求,而是把请求转发给Servlet容器来进行处理,Servlet容器通过接口来调用实际的业务代码来完成用户的请求。 这样设计的好处就是达到了HTTP服务器和具体以的业务代码解耦的目的。

        我们都知道Servlet容器和Servlet接口共同组成了一套规范,叫做Servlet规范,在tomcat内部对这个规范做了具体的实现,同时还让他们具备了HTTP服务器的功能。

      我们可以回忆一下,在学习JavaWeb的时候,我们要实现某种业务功能,只需要实现一个Servlet,并把它注册Tomcat(Servlet容器)中,最后tomcat就可以帮我们完成剩下的工作了,现在是不是知道了原因了

      接下来我们再来看看tomcat的源码,上面我们已经到了Tomcat 的主启动类,我们来看一下main方法里面的内容

public static void main(String args[]) {        if (daemon == null) {            // Don't set daemon until init() has completed            Bootstrap bootstrap = new Bootstrap();            try {                bootstrap.init();            } catch (Throwable t) {                handleThrowable(t);                t.printStackTrace();                return;            }            daemon = bootstrap;        } else {            // When running as a service the call to stop will be on a new            // thread so make sure the correct class loader is used to prevent            // a range of class not found exceptions.            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);        }        try {            String command = "start";            if (args.length > 0) {                command = args[args.length - 1];            }            if (command.equals("startd")) {                args[args.length - 1] = "start";                daemon.load(args);                daemon.start();            } else if (command.equals("stopd")) {                args[args.length - 1] = "stop";                daemon.stop();            } else if (command.equals("start")) {                daemon.setAwait(true);                daemon.load(args);                daemon.start();                if (null == daemon.getServer()) {                    System.exit(1);                }            } else if (command.equals("stop")) {                daemon.stopServer(args);            } else if (command.equals("configtest")) {                daemon.load(args);                if (null == daemon.getServer()) {                    System.exit(1);                }                System.exit(0);            } else {                log.warn("Bootstrap: command \"" + command + "\" does not exist.");            }        } catch (Throwable t) {            // Unwrap the Exception for clearer error reporting            if (t instanceof InvocationTargetException &&                    t.getCause() != null) {                t = t.getCause();            }            handleThrowable(t);            t.printStackTrace();            System.exit(1);        }    }
       我们发现上述代码中有一行是bootstrap.init(); 这里我们可以跟踪进去看一下。代码如下 :
 public void init() throws Exception {        initClassLoaders();        Thread.currentThread().setContextClassLoader(catalinaLoader);        SecurityClassLoad.securityClassLoad(catalinaLoader);        // Load our startup class and call its process() method        if (log.isDebugEnabled())            log.debug("Loading startup class");        Class> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");        Object startupInstance = startupClass.getConstructor().newInstance();        // Set the shared extensions class loader        if (log.isDebugEnabled())            log.debug("Setting startup class properties");        String methodName = "setParentClassLoader";        Class> paramTypes[] = new Class[1];        paramTypes[0] = Class.forName("java.lang.ClassLoader");        Object paramValues[] = new Object[1];        paramValues[0] = sharedLoader;        Method method =            startupInstance.getClass().getMethod(methodName, paramTypes);        method.invoke(startupInstance, paramValues);        catalinaDaemon = startupInstance;    }

       我们发现这个方法其实是加载了 org.apache.catalina.startup.Catalina这个类,这里我们可以理解成bootstrap.init();方法其实就是在初始化org.apache.catalina.startup.Catalina这个类。也就是我们在启动tomcat的时候经常看到的日志信息中的那个catalina。

        这里先给大家介绍一下Catalina这个对象,也被称为Catalina容器。其实大家应该也隐隐的感觉到了,之前给大家介绍的Servlet容器的名字就是叫Catalina。下面我们来看一下这张架构图:

21467f9e7b760286abdfac6176bed46c.png

         上图就是Tomcat 的完整的架构,这里先简单说一下上图中每个组件的作用首先Catalina容器主要是调用业务代码,Coyote是一个连接器,主要负责通讯功能,Jsaper是JSP的解析工具,JavaEL用来解析服务端的表达式语言、Naming 提供JNDI 服务,Juli 提供日志服务。我们在使用tomcat的功能的时候其实都是被由上述组件共同完成的。

       这里我们先来说说 Coyote连接器的作用,首先来看一下下面这张架构图

7ec84088d70555a3c3381d76668395be.png

       Coyote连接器就是tomcat对(外部)客户端提供的访问接口,客户端通过Coyote与服务器建立连接、发送请求并接受响应 。上图就是Coyote连接器的工作模式。Coyote 封装了底层的网络通信(Socket 请求及响应处理),为Catalina 容器提供了统一的接口,使Catalina 容器与具体的请求协议及IO操作方式完全解耦。Coyote 将Socket 输入转换封装为 Request 对象,交由Catalina 容器进行处理,处理请求完成后, Catalina 通过Coyote 提供的Response 对象将结果写入输出流 。

        作为独立的模块,Coyote只负责具体的协议以及IO相关的操作。与Servlet 规范实现没有直接关系,因此即便是 Request 和 Response 对象也并未实现Servlet规范对应的接口, 而是在Catalina 中将他们进一步封装为ServletRequest 和 ServletResponse

    好了,提到IO和协议这里也简单的说明一下Tomcat内部支持的一些协议和IO模型吧,如下图所示:

5418da68fe9b2155d00406ebe95d7f95.png

aac59798d3ca869fa793d318e8050ecc.png

     需要注意的是在 8.0 之前 , Tomcat 默认采用的I/O方式为 BIO , 之后改为 NIO。无论 NIO、NIO2 还是 APR, 在性能方面均优于以往的BIO。如果采用APR, 甚至可以达到 Apache HTTP Server 的性能。

    下面来看一下连接器的组件,如下图所示:

a03f9d93cb6fc771a78c7a319a938d08.png

      我们可以看到EndPoint 的作用就是负责底层的通信,他是具体Socket接收和发送处理器,是对传输层的抽象,因此EndPoint用来实现TCP/IP协议的。至于Processor,  它是Coyote 协议处理接口 ,用来实现HTTP协议,Processor接收来自EndPoint的Socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理,Processor是对应用层协议的抽象。 最后  Adapter的作用就是 将便是 Request 和 Response 对象进一步封装为ServletRequest 和 ServletResponse。

下面copy书上的一句话来总结一下:

54a2dabbc1de7edcb1d29192b782c519.png

     好了,最后给大家介绍一下Jasper引擎,Jasper模块是Tomcat的JSP核心引擎,我们知道JSP本质上是一个Servlet。Tomcat使用Jasper对JSP语法进行解析,生成Servlet并生成Class字节码,用户在进行访问jsp时,会访问Servlet,最终将访问的结果直接响应在浏览器端。如下图所示:

7889c0238905b8f97a5134076b52baf8.png

  我们可以发现,在tomcat的web.xml文件中配置了一个org.apache.jasper.servlet.JspServlet,他就是用于处理所有的.jsp 或 .jspx 结尾的请求,该Servlet 实现即是运行时编译的入口。 将jsp文件编译成Java字节码然后再有jvm解释执行。

 好了今天关于tomcat的组件就给大家介绍这么多了,晚安!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值