作为一个优秀的程序开发者,我们不仅需要对自己编写的程序有深刻的理解和认识,同样的我们还需要对我们编写的程序的运行环境有一个彻底的认识,这样我们在能在这条路上走的更远。相信大家对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目录下的那些。具体的内容如下:
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
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中,我们在项目中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连接器的作用(未完待续。。。。。。。。)