作为一个优秀的程序开发者,我们不仅需要对自己编写的程序有深刻的理解和认识,同样的我们还需要对我们编写的程序的运行环境有一个彻底的认识,这样我们在能在这条路上走的更远。相信大家对tomcat都很熟悉,目前也是大陆使用的较多的一种开源的Web容器。对于Tomcat的基本使用这里不做过多的介绍。具体的下载和使用可以查看我在2018年写的https://blog.csdn.net/qq_38701478/article/details/85003063这篇文章。
好了我们进入今天的主题,首先准备好源码,这里使用的版本是tomcat8.5-42
首先我们来打开Idea 创建一个空的工程
点击完成,然后我们将下载好的源码包解压,放到上面刚刚创建的空工程对应的文件夹下
然后我们需要进行两个操作,首先创建一个home目录,接着将conf文件夹和webapps文件夹拷贝到home目录中(其他的不需要)
好了,接着我们在源码的目录下创建一个pom.xml的文件,因为我们要以Maven工程的模式来构建源码,pom文件中主要的内容就是tomcat 依赖的jar包,对应于lib目录下的那些。具体的内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.tomcat</groupId>
<artifactId>apache-tomcat-8.5.42-src</artifactId>
<name>Tomcat8.5</name>
<version>8.5</version>
<build>
<finalName>Tomcat8.5</finalName>
<sourceDirectory>java</sourceDirectory>
<!-- <testSourceDirectory>test</testSourceDirectory>-->
<resources>
<resource>
<directory>java</directory>
</resource>
</resources>
<!-- <testResources>
<testResource>
<directory>test</directory>
</testResource>
</testResources>-->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>ant</groupId>
<artifactId>ant</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxrpc</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.5.1</version>
</dependency>
</dependencies>
</project>
接着我们回到Idea中,我们在项目中new一个module ,选择这从已经存在的项目中创建。
选中pom.xml文件即可。等待项目加载完成,(在此之前请见检查自己的maven环境是否正常)
导入成功之后项目结构如下:
好了,我们先来看一下,tomcat 的项目架构,我们都知道,一个Java项目的入口都是一个main方法,我们想要自己运行这个工程就要找到他的程序入口Main方法,首先我们都很熟悉,我们在启动tomcat的时候都是使用的startup.sh或者是startup.bat这两个脚本,这两个脚本一个是Linux下的一个是Windows平台下的,首先我们先来看一下startup.bat这个。
我们发现 这个批处理文件中实际上调用的是catalina.bat,好吧,我们打开catalina.bat这个文件看看,虽然我们可能看不懂windows下面的批处理脚本,但是我们可以挑一些我们熟悉的东西看,大家仔细看的话,会发现一个叫做MAINCLASS的变量,如下图所示:
从字面上来理解,这个应该就是我们要找的著启动类了,也就是说Main 方法就是在该类中,好了,我们接着打开BootStrap这个类,
我们看一下,发现里面确实有一个main方法,
好了,主启动类找到了,接下来我们需要配置一下运行时的参数,我们选择添加一个Application
-Dcatalina.home=D:/projec/java/IdeaProjects/tomcat-src/apache-tomcat-8.5.42-src/home
-Dcatalina.base=D:/projec/java/IdeaProjects/tomcat-src/apache-tomcat-8.5.42-src/home
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=D:/projec/java/IdeaProjects/tomcat-src/apache-tomcat-8.5.42-src/home/conf/logging.properties
好了,我们启动刚刚配置的BootStrap这个Application即可
点击debug ,就可以启动项目了,首次启动的过程可能有点漫长
我们发现8080端口成功的启动了,打开浏览器,我们发现可以正常的访问主页
如果发现访问的时候报错了,可以在ContextConfig中的configureStart函数中手动将JSP解析器初始 在770行webConfig();方法后加上context.addServletContainerInitializer(new JasperInitializer(), null); 即可。
好了,我们到这里我们已经成功的构建了tomcat8.5版本的源码了,并且已经可以在本地运行了。下面我们就来看看Tomcat的项目架构,首先我们都知道,tomcat是一种web容器,主要用来发布我们的Web项目,用户可以通过Http请求来访问我们发布的项目。关于HTTP协议这里不做过多的介绍,相信大家也很熟悉,我们重点介绍Servlet容器。
首先我们来看这张图
在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。下面我们来看一下这张架构图:
上图就是Tomcat 的完整的架构,这里先简单说一下上图中每个组件的作用首先Catalina容器主要是调用业务代码,Coyote是一个连接器,主要负责通讯功能,Jsaper是JSP的解析工具,JavaEL用来解析服务端的表达式语言、Naming 提供JNDI 服务,Juli 提供日志服务。我们在使用tomcat的功能的时候其实都是被由上述组件共同完成的。、
这里我们先来说说 Coyote连接器的作用,首先来看一下下面这张架构图
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模型吧,如下图所示:
需要注意的是在 8.0 之前 , Tomcat 默认采用的I/O方式为 BIO , 之后改为 NIO。 无论 NIO、NIO2 还是 APR, 在性能方面均优于以往的BIO。 如果采用APR, 甚至可以达到 Apache HTTP Server 的性能。
下面来看一下连接器的组件,如下图所示:
我们可以看到EndPoint 的作用就是负责底层的通信,他是具体Socket接收和发送处理器,是对传输层的抽象,因此EndPoint用来实现TCP/IP协议的。至于Processor, 它是Coyote 协议处理接口 ,用来实现HTTP协议,Processor接收来自EndPoint的Socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理,Processor是对应用层协议的抽象。 最后 Adapter的作用就是 将便是 Request 和 Response 对象进一步封装为ServletRequest 和 ServletResponse。
下面来总结一下:
好了,最后给大家介绍一下Jasper引擎,Jasper模块是Tomcat的JSP核心引擎,我们知道JSP本质上是一个Servlet。Tomcat使用Jasper对JSP语法进行解析,生成Servlet并生成Class字节码,用户在进行访问jsp时,会访问Servlet,最终将访问的结果直接响应在浏览器端。如下图所示:
我们可以发现,在tomcat的web.xml文件中配置了一个org.apache.jasper.servlet.JspServlet,他就是用于处理所有的.jsp 或 .jspx 结尾的请求,该Servlet 实现即是运行时编译的入口。 将jsp文件编译成Java字节码然后再有jvm解释执行。
好了关于tomcat的组件就给大家介绍这么多了,如果想深入的学习tomcat这里推荐一本书《Tomcat架构解析》https://download.csdn.net/download/qq_38701478/12837241
欢迎关注本人公众号 代码洁癖症患者