Tomcat基础详解
第一篇:Tomcat基础篇
lecture:邓澎波
一、构建Tomcat源码环境
工欲善其事必先利其器,为了学好Tomcat源码,我们需要先在本地构建一个Tomcat的运行环境。
1.源码环境下载
源码有两种下载方式:
1.1 官网下载
https://tomcat.apache.org/
1.2 GitHub下载
当然你也可以通过GitHub来拉取源代码
https://github.com/apache/tomcat
2.Maven环境搭建
2.1 环境准备
打开IEDA导入项目,然后在项目中创建一个新的pom.xml文件,里面的内容为:
<?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.0http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.apache.tomcat</groupId><artifactId>apache-tomcat</artifactId><version>8.5</version><dependencies><dependency><groupId>org.apache.ant</groupId><artifactId>ant</artifactId><version>1.10.4</version></dependency><dependency><groupId>wsdl4j</groupId><artifactId>wsdl4j</artifactId><version>1.6.2</version></dependency><dependency><groupId>javax.xml</groupId><artifactId>jaxrpc-api</artifactId><version>1.1</version></dependency><dependency><groupId>org.eclipse.jdt.core.compiler</groupId><artifactId>ecj</artifactId><version>4.5.1</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13</version></dependency></dependencies><build><finalName>apache-tomcat</finalName><sourceDirectory>java</sourceDirectory><resources><resource><directory>java</directory></resource></resources><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin></plugins></build>
</project>
然后设置项目为Maven项目,选中pom.xml文件,鼠标右点。选择 Add as Maven Project
.
在右侧出现的Maven菜单中选择编译项目(compile)
2.2 项目启动
编译成功后进入 Bootstrap中,启动main方法
出现如下提示,说明启动成功,只是中文乱码了
2.3 解决中文乱码问题
中文乱码问题的解决方案,修改两处地方即可
1.修改org.apache.jasper.compiler.Localizer#getMessage(java.lang.String)方法
public static String getMessage(String errCode) {String errMsg = errCode;try {if (bundle != null) {errMsg = bundle.getString(errCode);}} catch (MissingResourceException e) {}try{errMsg = new String(errMsg.getBytes("ISO-8859-1"),"UTF-8");}catch (UnsupportedEncodingException e){e.printStackTrace();}return errMsg;}
2.修改org.apache.tomcat.util.res.StringManager#getString(java.lang.String)
重启服务
启动正常,但是访问的时候出现了问题
2.4 解决不支持JSP的问题
启动成功后,在访问首页的时候,出现了500错误,而且提示 无法为JSP编译类
。
原因是无法编译jsp。解决也很简单,按照下面步骤操作即可
上面的报错解决方式,可以在org.apache.catalina.startup.ContextConfig类中的configureStart方法中,添加一下JSP解析器初始化即可
context.addServletContainerInitializer(new JasperInitializer(), null);
重启服务:访问搞定
到此Tomcat的源码环境我们就已经准备好了,接下来就可以开始我们的Tomcat源码之旅了!!!
二、Tomcat源码结构介绍
在分析Tomcat源码之前,我们先来看下Tomcat源码的结构组成,这样会更加的有利于我们更好的来分析源码。
1.项目源码结构
我们先从源码结构开始。Tomcat 服务器相关的代码在 java 文件夹下面,后面我们在进入这个文件夹去分析:
之前如何手动在Tomcat中部署过项目的话,这块应该会比较清楚点。
2.Tomcat源码结构
Tomcat 源码位于 java 文件夹下面。这个java文件夹中的每个包的作用,我们简单的来介绍下,后面在分析核心源码的时候会重点讲解。
我们可以看到在java目录下,分为了两个结构,一个是javax另一个是org.apache这两个包
2.1 javax
在javax中保存的是新的JavaEE规范。可以具体来看看每个目录的作用。
模块 | 作用说明 |
---|---|
annotation | annotation 这个模块的作用是定义了一些公用的注解,避免在不同的规范中定义相同的注解。 |
ejb | ejb是个古老的传说,我们不管 |
el | 在jsp中可以使用EL表达式,这么模块解析EL表达式的 |
和邮件相关的规范 | |
persistence | 持久化相关的 |
security | 和安全相关的内容 |
servlet | 这个指定的是Servlet的开发规范,Tomcat本质上就是一个实现了Servlet规范的一个容器,Servlet定义了服务端处理Http请求和响应的方式(规范) |
websocket | 定义了使用 websocket 协议的服务端和客户端 API |
xml.ws | 定义了基于 SOAP 协议的 xml 方式的 web 服务 |
2.2 org.apache
org.apache这个包是Tomcat的源码包,也是针对上面的JavaEE规范的部分实现,Tomcat的本质就是对JavaEE的某些规范的实现合集,首先肯定实现了Servlet规范
模块 | 作用 |
---|---|
catalina | catalina是Tomcat的核心模块,里面完整的实现了Servlet规范,Tomcat启动的主方法也在里面,后面我们分析的重点。 |
coyote | tomcat 的核心代码,负责将网络请求转化后和 Catalina 进行通信。 |
el | 这个是上面javax中的el规范的实现 |
jasper | 主要负责把jsp代码转换为java代码。 |
juli | 日志相关的工具 |
naming | 命名空间相关的内容 |
tomcat | 各种辅助工具,包括 websocket 的实现。 |
3.Tomcat模块设计
连接器的作用:
- 连接器功能· 监听网络端口。
- 接受网络连接请求。
- 根据具体应用层协议(http/ajp)解析字节流,生成统一的Tomcat Request对象。
- 将Tomcat Request对象转成标准的ServletRequest。
- 调用Servlet容器,得到ServletResponse。
- 将ServletResponse转成Tomcat Response对象。
- 将Tomcat Response转成网络字节流。
- 将响应字节流写回给浏览器。
三、Tomcat的架构设计
1.Servlet规范
1.1 Servlet作用讲解
Servlet是JavaEE规范中的一种,主要是为了扩展Java作为Web服务的功能,统一定义了对应的接口,比如Servlet接口,HttpRequest接口,HttpResponse接口,Filter接口。然后由具体的服务厂商来实现这些接口功能,比如Tomcat,jetty等。
&ems;在规范里面并不会有具体的实现。可以自行看下源码,而在Servlet规范中规定了一个http请求到来的执行处理流程:对应的服务器容器会接收到对应的Http请求,然后解析该请求,然后创建对应的Servlet实例,调用对应init方法来完成初始化,把请求的相关信息封装为HttpServletRequest对象来调用Servlet的service方法来处理请求,然后通过HttpServletResponse封装响应的信息交给容器,响应给客户端。
1.2 Servlet核心API
我们再来回顾下Servlet中的核心API,这块对我们更好的掌握Tomcat的内容还是非常有帮助的。
API | 描述 |
---|---|
ServletConfig | 获取servlet初始化参数和servletContext对象。 |
ServletContext | 在整个Web应用的动态资源之间共享数据。 |
ServletRequest | 封装Http请求信息,在请求时创建。 |
ServletResponse | 封装Http响应信息,在请求时创建。 |
ServletConfig:
容器在初始化servlet时,为该servlet创建一个servletConfig对象,并将这个对象通过init()方法来传递并保存在此Servlet对象中。核心作用:
- 获取初始化信息;
- 获取ServletContext对象。
ServletContext
一个项目只有一个ServletContext对象,可以在多个Servlet中来获取这个对象,使用它可以给多个Servlet传递数据,该对象在Tomcat启动时就创建,在Tomcat关闭时才会销毁!作用是在整个Web应用的动态资源之间共享数据。
在实际的Servlet开发中,我们会实现HttpServlet接口,在该接口中会实现GenericServlet,而在GenericServlet会实现ServiceConfig接口,从而可以获取ServletContext容器对象
所以在Servlet中我们可以很容易的获取到ServletContext对象,从而完成对应的操作。
public class ServletTwoImpl extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {response.setContentType("text/html;charset=utf-8");// 1、参数传递ServletContext servletContext = this.getServletContext() ;String value = String.valueOf(servletContext.getAttribute("name")) ;System.out.println("value="+value);// 2、获取初始化参数String userName= servletContext.getInitParameter("user-name") ;System.out.println("userName="+userName);// 3、获取应用信息String servletContextName = servletContext.getServletContextName() ;System.out.println("servletContextName="+servletContextName);// 4、获取路径String pathOne = servletContext.getRealPath("/") ;String pathTwo = servletContext.getRealPath("/WEB-INF/") ;System.out.println("pathOne="+pathOne+";pathTwo="+pathTwo);response.getWriter().print("执行:doGet; value:"+value);}
}
1.3 ServletRequest
HttpServletRequest接口继承ServletRequest接口,用于封装请求信息,该对象在用户每次请求servlet时创建并传入servlet的service()方法,在该方法中,传入的servletRequest将会被强制转化为HttpservletRequest 对象来进行HTTP请求信息的处理。核心作用:
- 获取请求报文信息;
- 获取网络连接信息;
- 获取请求域属性信息。
1.4 ServletResponse
HttpServletResponse继承自ServletResponse,封装了Http响应信息。客户端每个请求,服务器都会创建一个response对象,并传入给Servlet.service()方法。核心作用:
- 设置响应头信息;
- 发送状态码;
- 设置响应正文;
- 重定向;
2.Tomcat的设计
通过上面Servlet规范的介绍,其实我们发下我们要实现Servlet规范的话,很重要的就得提供一个服务容器来获取请求,解析封装数据,并调用Servlet实例相关的方法。也就是如下图中的部分
这块的内容其实就是Tomcat,具体的我们来看看。
2.1 什么是Tomcat
Tomcat是一个容器,用于承载Servlet,那么我们说Tomcat就是一个实现了部分J2EE规范的服务器。J2 EE和Jakarta EE(Eclipse基金会)这两是啥?用于Tomcat10以后都是Jakarta EE,而9之前就是J2EE.
2.2 Tomcat的架构结构
我们通过上面的分析,知道Tomcat是一个Servlet规范的实现,要接收请求和响应请求,那么具体是如何实现的呢?这块我们可以通过conf下的server.xml得出对应的结论。
server.xml是Tomcat中最重要的配置文件,server.xml 的每一个元素都对应了Tomcat 中的一个组件 ;通过对xml文件中元素的配置,可以实现对Tomcat中各个组件的控制。因此,学习server.xml文件的配置,对于了解和使用Tomcat至关重要.
官方文档:https://tomcat.apache.org/tomcat-8.5-doc/config/server.html
<?xml version="1.0" encoding="UTF-8"?><Server port="8005" shutdown="SHUTDOWN"><Service name="Catalina"><Executor name="tomcatThreadPool" namePrefix="catalina-exec-"maxThreads="150" minSpareThreads="4"/><Connector port="8080" protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443" /><Connector executor="tomcatThreadPool"port="8080" protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443" /><Engine name="Catalina" defaultHost="localhost"><Realm className="org.apache.catalina.realm.LockOutRealm"><!-- This Realm uses the UserDatabase configured in the global JNDIresources under the key "UserDatabase". Any editsthat are performed against this UserDatabase are immediatelyavailable for use by the Realm. --><Realm className="org.apache.catalina.realm.UserDatabaseRealm"resourceName="UserDatabase"/></Realm><Host name="localhost" appBase="webapps"unpackWARs="true" autoDeploy="true"><Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"prefix="localhost_access_log" suffix=".txt"pattern="%h %l %u %t "%r" %s %b" /></Host></Engine></Service>
</Server>
极简模式
<Server><Service><Connector /><Connector /><Engine><Host><Context /><!-- 现在常常使用自动部署,不推荐配置Context元素,Context小节有详细说明 --></Host></Engine></Service>
</Server>
梳理出的结构
对应的每个组件的作用。
2.3 组件分类
官网其实对上面的组件也做了分类:
顶级元素:
- Server:是整个配置文件的根元素
- Service:代表一个Engine元素以及一组与之相连的Connector元素
连接器:
- 代表了外部客户端发送请求到特定Service的接口;同时也是外部客户端从特定Service接收响应的接口。
容器:
容器的作用是处理Connector接收进来的请求,并产生对应的响应,Engine,Host和Context都是容器,他们不是平行关系,而是父子关系。
每个组件的作用:
- Engine:可以处理所有请求
- Host:可以处理发向一个特定虚拟主机的所有请求
- Context:可以处理一个特定Web应用的所有请求
核心组件的串联关系:
当客户端请求发送过来后其实是通过这些组件相互之间配合完成了对应的操作。
- Server元素在最顶层,代表整个Tomcat容器;一个Server元素中可以有一个或多个Service元素
- Service在Connector和Engine外面包了一层,把它们组装在一起,对外提供服务。一个Service可以包含多个Connector,但是只能包含一个Engine;Connector接收请求,Engine处理请求。
- Engine、Host和Context都是容器,且Engine包含Host,Host包含Context。每个Host组件代表Engine中的一个虚拟主机;每个Context组件代表在特定Host上运行的一个Web应用.
整体Tomcat的运行架构图
四、Tomcat生命周期
在上篇文章中我们看到了Tomcat架构中的核心组件,而且各个组件都有各自的作用,各司其职,而且相互之间也有对应的父子关系,那么这些对象的创建,调用,销毁等操作是怎么处理呢?
也就是在Tomcat中的组件的对象生命周期是怎么管理的呢?针对这个问题,在Tomcat中设计了Lifecycle接口来统一管理Tomcat中的核心组件的生命周期,所以本文我们就系统的来介绍下Lifecycle接口的设计
1、LifeCycle接口设计
为了统一管理Tomcat中的核心组件的生命周期,而专门设计了LifeCycle接口来统一管理,我们来看看在LifeCycle接口中声明了哪些内容。
1.1 生命周期的方法
在LifeCycle中声明了和生命周期相关的方法,包括init(),start(),stop(),destory()等方法。
在声明的方法执行的过程中会涉及到对应的状态的转换,在LifeCycle接口的头部文档中很清楚的说了。
1.2 相关的状态处理
通过上图我们可以很清楚的看到相关的方法执行会涉及到的相关状态的转换,比如init()会从New这个状态开始,然后会进入 INITIALIZING 和 INITIALIZED 等。因为这块涉及到了对应的状态转换,在Lifecycle中声明了相关的状态和事件的生命周期字符串。
public static final String BEFORE_START_EVENT = "before_start";public static final String AFTER_START_EVENT = "after_start";public static final String STOP_EVENT = "stop";public static final String BEFORE_STOP_EVENT = "before_stop";public static final String AFTER_STOP_EVENT = "after_stop";public static final String AFTER_DESTROY_EVENT = "after_destroy";public static final String BEFORE_DESTROY_EVENT = "before_destroy";/*** The LifecycleEvent type for the "periodic" event.* 周期性事件(后台线程定时执行一些事情,比如:热部署、热替换)*/public static final String PERIODIC_EVENT = "periodic";public static final String CONFIGURE_START_EVENT = "configure_start";public static final String CONFIGURE_STOP_EVENT = "configure_stop";
在LifecycleState中建立了对应关系
针对特定的事件就会有相关的监听器来监听处理。在Lifecycle中定义了相关的处理方法。
public void addLifecycleListener(LifecycleListener listener);public LifecycleListener[] findLifecycleListeners();public void removeLifecycleListener(LifecycleListener listener);
通过方法名称我们就能很清楚该方法的相关作用,就不过程介绍了。然后来看下对应的监听器和事件接口的对应设计。
2.监听器和事件的设计
接下来看下LifecycleListener的设计。其实代码非常简单。
public interface LifecycleListener {/*** Acknowledge the occurrence of the specified event.* 触发监听器后要执行逻辑的方法* @param event LifecycleEvent that has occurred*/public void lifecycleEvent(LifecycleEvent event);}
然后来看下事件的接口
public final class LifecycleEvent extends EventObject {private static final long serialVersionUID = 1L;/*** Construct a new LifecycleEvent with the specified parameters.** @param lifecycle Component on which this event occurred* @param type Event type (required)* @param data Event data (if any)*/public LifecycleEvent(Lifecycle lifecycle, String type, Object data) {super(lifecycle); // 向上转型,可接受一切实现了生命周期的组件this.type = type;this.data = data;}/*** The event data associated with this event.* 携带的额外的数据,传递给监听器的数据*/private final Object data;/*** The event type this instance represents.* 事件类型*/private final String type;/*** @return the event data of this event.*/public Object getData() {return data;}/*** @return the Lifecycle on which this event occurred.*/public Lifecycle getLifecycle() {return (Lifecycle) getSource();}/*** @return the event type of this event.*/public String getType() {return this.type;}
}
也是非常简单,不过多的赘述。
3.LifecycleBase
通过上面的介绍我们可以看到在Tomcat中设计了Lifecycle和LifecycleListener和LifecycleEvent来管理核心组件的生命周期,那么我们就需要让每一个组件都实现相关的接口。这时你会发现交给子类的工作量其实是比较大的,不光要完成各个组件的核心功能,还得实现生命周期的相关处理,耦合性很强,这时在Tomcat中给我们提供了一个LifecycleBase的抽象类,帮助我们实现了很多和具体业务无关的处理,来简化了具体组件的业务。
3.1 事件处理
在上面的接口设计中对于监听对应的事件处理是没有实现的,在LifecycleBase把这块很好的实现了,我们来看下。首先定义了一个容器来存储所有的监听器
// 存储了所有的实现了LifecycleListener接口的监听器
private final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>();
同时提供了触发监听的相关的方法,绑定了对应的事件。
/*** Allow sub classes to fire {@link Lifecycle} events.* 监听器触发相关的事件* @param type Event type 事件类型* @param data Data associated with event.*/protected void fireLifecycleEvent(String type, Object data) {LifecycleEvent event = new LifecycleEvent(this, type, data);for (LifecycleListener listener : lifecycleListeners) {listener.lifecycleEvent(event);}}
已经针对Listener相关的处理方法
// 添加监听器@Overridepublic void addLifecycleListener(LifecycleListener listener) {lifecycleListeners.add(listener);}// 查找所有的监听并转换为了数组类型@Overridepublic LifecycleListener[] findLifecycleListeners() {return lifecycleListeners.toArray(new LifecycleListener[0]);}// 移除某个监听器@Overridepublic void removeLifecycleListener(LifecycleListener listener) {lifecycleListeners.remove(listener);}
3.2 生命周期方法
在LifecycleBase中最核心的还是实现了Lifecycle中的生命周期方法,以init方法为例我们来看。
/*** 实现了 Lifecycle 中定义的init方法* 该方法和对应的组件的状态产生的关联* @throws LifecycleException*/@Overridepublic final synchronized void init() throws LifecycleException {if (!state.equals(LifecycleState.NEW)) {// 无效的操作 只有状态为 New 的才能调用init方法进入初始化invalidTransition(Lifecycle.BEFORE_INIT_EVENT);}try {// 设置状态为初始化进行中....同步在方法中会触发对应的事件setStateInternal(LifecycleState.INITIALIZING, null, false);initInternal(); // 交给子类具体的实现 初始化操作// 更新状态为初始化完成 同步在方法中会触发对应的事件setStateInternal(LifecycleState.INITIALIZED, null, false);} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.initFail", toString());}}
源码解析:
- 我们看到首先会判断当前对象的state状态是否为NEW,因为init方法只能在NEW状态下才能开始初始化
- 如果1条件满足则会更新state的状态为
INITIALIZED
同时会触发这个事件 - 然后initInternale()方法会交给子类具体去实现,
- 等待子类处理完成后会把状态更新为
INITIALIZED
。
我们可以进入setStateInternal方法查看最后的关键代码:
// ....this.state = state; // 更新状态// 根据状态和事件的绑定关系获取对应的事件String lifecycleEvent = state.getLifecycleEvent();if (lifecycleEvent != null) {// 发布对应的事件fireLifecycleEvent(lifecycleEvent, data);}
可以看到和对应的事件关联起来了。init方法的逻辑弄清楚后,你会发现start方法,stop方法,destory方法的处理逻辑都是差不多的,可自行观看。而对应的 initInternal()方法的逻辑我们需要在 Server Service Engine Connector等核心组件中再看,这个我们会结合Tomcat的启动流程来带领大家一起查看。下一篇给大家介绍。
五、Tomcat的启动核心流程
前面给大家介绍了Tomcat中的生命周期的设计,掌握了这块对于我们分析Tomcat的核心流程是非常有帮助的,也就是我们需要创建相关的核心组件,比如Server,Service肯定都绕不开生命周期的方法。
1.启动的入口
你可以通过脚本来启动Tomcat服务(startup.bat),但如果你看过脚本的命令,你会发现最终调用的还是Bootstrap中的main方法,所以我们需要从main方法来开始
然后我们去看main方法中的代码,我们需要重点关注的方法有三个
- bootstrap.init()方法
- load()方法
- start()方法
也就是在这三个方法中会完成Tomcat的核心操作。
2.init方法
我们来看下init方法中的代码,非核心的我们直接去掉
public void init() throws Exception {// 创建相关的类加载器initClassLoaders();// 省略部分代码...// 通过反射创建了 Catalina 类对象Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");// 创建了 Catalina 实例Object startupInstance = startupClass.getConstructor().newInstance();// 省略部分代码...String methodName = "setParentClassLoader";Class<?> paramTypes[] = new Class[1];paramTypes[0] = Class.forName("java.lang.ClassLoader");Object paramValues[] = new Object[1];paramValues[0] = sharedLoader;// 把 sharedLoader 设置为了 commonLoader的父加载器Method method =startupInstance.getClass().getMethod(methodName, paramTypes);method.invoke(startupInstance, paramValues);// Catalina 实例 赋值给了 catalinaDaemoncatalinaDaemon = startupInstance;}
- 首先是调用了initClassLoaders()方法,这个方法会完成对应的ClassLoader的创建,这个比较重要,后面专门写一篇文章来介绍。
- 通过反射的方式创建了Catalina的类对象,并通过反射创建了Catalina的实例
- 设置了类加载器的父子关系
- 用过成员变量catalinaDaemon记录了我们创建的Catalina实例
这个是通过bootstrap.init()方法我们可以获取到的有用的信息。然后我们继续往下面看。
3.load方法
然后我们来看下load方法做了什么事情,代码如下:
private void load(String[] arguments) throws Exception {// Call the load() methodString methodName = "load"; // load方法的名称Object param[];Class<?> paramTypes[];if (arguments==null || arguments.length==0) {paramTypes = null;param = null;} else {paramTypes = new Class[1];paramTypes[0] = arguments.getClass();param = new Object[1];param[0] = arguments;}// catalinaDaemon 就是在 init中创建的 Catalina 对象Method method =catalinaDaemon.getClass().getMethod(methodName, paramTypes);if (log.isDebugEnabled()) {log.debug("Calling startup class " + method);}// 会执行 Catalina的load方法method.invoke(catalinaDaemon, param);}
上面的代码非常简单,通过注释我们也可以看出该方法的作用是调用 Catalina的load方法。所以我们还需要加入到Catalina的load方法中来查看,代码同样比较长,只留下关键代码
public void load() {if (loaded) {return; // 只能被加载一次}loaded = true;initDirs(); // 废弃的方法// Before digester - it may be neededinitNaming(); // 和JNDI 相关的内容 忽略// Create and execute our Digester// 创建并且执行我们的 Digester 对象 Server.xmlDigester digester = createStartDigester();// 省略掉了 Digester文件处理的代码getServer().setCatalina(this); // Server对象绑定 Catalina对象getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());// Stream redirectioninitStreams();// 省略掉了部分代码...getServer().init(); // 完成 Server Service Engine Connector等组件的init操作}
把上面的代码简化后我们发现这个Load方法其实也是蛮简单的,就做了两件事。
- 通过Apache下的Digester组件完成了Server.xml文件的解析
- 通过getServer().init() 方法完成了Server,Service,Engin,Connector等核心组件的初始化操作,这块和前面的LifecycleBase呼应起来了。
如果生命周期的内容不清楚,请看上一篇文章的介绍。
4.start方法
最后我们来看下start方法的代码。
public void start() throws Exception {if (catalinaDaemon == null) {init(); // 如果 catalinaDaemon 为空 初始化操作}// 获取的是 Catalina 中的 start方法Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);// 执行 Catalina 的start方法method.invoke(catalinaDaemon, (Object [])null);}
上面的代码逻辑也很清楚,就是通过反射的方式调用了Catalina对象的start方法。所以进入Catalina的start方法中查看。
public void start() {if (getServer() == null) {load(); // 如果Server 为空 重新 init 相关的组件}if (getServer() == null) {log.fatal("Cannot start server. Server instance is not configured.");return;}// Start the new server 关键方法--->启动Servertry {getServer().start();} catch (LifecycleException e) {// 省略...}// 省略...// Register shutdown hook 注册关闭的钩子if (useShutdownHook) {// 省略...}if (await) {await();stop();}}
通过上面的代码我们可以发现核心的代码还是getServer.start()方法,也就是通过Server对象来嵌套的调用相关注解的start方法。
5.核心流程的总结
我们可以通过下图来总结下Tomcat启动的核心流程
从图中我们可以看到Bootstrap其实没有做什么核心的事情,主要还是Catalina来完成的。
相关文章:

Tomcat基础详解
第一篇:Tomcat基础篇 lecture:邓澎波 一、构建Tomcat源码环境 工欲善其事必先利其器,为了学好Tomcat源码,我们需要先在本地构建一个Tomcat的运行环境。 1.源码环境下载 源码有两种下载方式: 1.1 官网下载 https://…...
【Python爬虫】爬取名人名言页面并进行简单的数据清洗(入门级)
目录 资源链接 一、网站选择 二、数据爬取要求 三、数据清洗要求 四、实现代码 1.数据采集 2.数据清洗 资源链接 下面有笔者所放的源码下载链接,读者可自行下载: 链接:https://pan.baidu.com/s/1YmTdlnbSJLvLrrx92zz6Qg 提取码&…...

Microsoft Visual C++ Redistributable 【安装包】【高速下载】
方法1、可以从官方下载,如下图 Visual C Redistributable for Visual Studio 2015 但是此链接只有一个版本 方法2 已经下载好并且已经整理好了2008--2022的所有版本点击下方链接即可高速下载 如果是win7-win8-win10-win11直接可以下载2015--2022版本,…...

MFC绘制哆啦A梦
文章目录 OnPaint绘制代码完整Visual Studio工程下载其他卡通人物绘制 OnPaint绘制代码 CPaintDC dc(this); // 用于绘画的设备上下文CRect rc;GetWindowRect(rc);int cxClient rc.Width();int cyClient rc.Height();// 辅助线HPEN hPen CreatePen(PS_DOT, 1, RGB(192, 192,…...

网络编程(TCP协议,UDP协议)
目录 网络编程三要素 IP IPv4 InetAddress类 端口号 协议 UDP协议 UDP协议发送数据 UDP协议接收数据 UDP的三种通信方式(代码实现) TCP协议 TCP通信程序 三次握手和四次挥手 练习 1、客户端:多次发送数据服务器:接收多次接收数据,并打印 2、客户端…...
读取Jar包下文件资源的问题及解决方案
问题 项目A代码调用到Resouces下的文件a.sh,打包成Jar包后,项目B调用对应方法时,出现报错,找不到a.sh文件路径,原来的代码可能是: URL resource getClass().getClassLoader().getResource("a.sh&qu…...
C++ 反转一个二进制串
描述 一个32位有符号整数,用二进制编码来表示。现需要将该二进制编码按位反转,计算出反转后的值。 示例1 输入: 1 返回值: -2147483648 说明: 00000000 00000000 00000000 00000001 翻转后为 10000000 000000…...

黑神话悟空-吉吉国王版本【抢先版】
在中国的游戏市场中,一款名为“黑神话悟空”的游戏引起了广泛的关注。这款游戏以中国传统的神话故事“西游记”为背景,创造了一个令人震撼的虚拟世界。今天,我们要来介绍的是这款游戏的一种特殊版本,那就是吉吉国王版本。 在吉吉国…...

【尚庭公寓SpringBoot + Vue 项目实战】预约看房与租约管理(完结)
【尚庭公寓SpringBoot Vue 项目实战】预约看房与租约管理(完结) 文章目录 【尚庭公寓SpringBoot Vue 项目实战】预约看房与租约管理(完结)1、业务说明2、接口开发2.1、预约看房管理2.1.1.保存或更新看房预约2.1.2. 查询个人预约…...
java拼图小游戏项目
创建一个Java拼图小游戏是一个有趣且富有教育意义的项目,可以锻炼你的编程技能。以下是开发一个基本拼图游戏可能需要考虑的几个步骤: 项目规划: 确定游戏的基本规则和玩法。设计游戏的界面和用户交互。 环境搭建: 确保你的开发环…...

[C++][数据结构][跳表]详细讲解
目录 0.什么是跳表?1.SkipList的优化思路2.SkipList的效率如何保证?3.SkipList实现4.SkipList VS 平衡搜索树 && Hash 0.什么是跳表? SkipList本质上也是一种查找结构,用于解决算法中的查找问题,跟平衡搜索树…...
tinyxml
github下载相关的软件包,其中有四个文件需要主要需要关注就是分别是tinyxml12.cpp,tinyxml12.h,rss网页xml文件,还有就是官方给的test文件tinyxmltest.cpp。 example1就是提供一个打开文件的方式 int example_1() {XMLDocument …...

Docker(三)-Docker常用命令
1.run run命令执行流程:2.帮助启动类命令 2.1 启动docker systemctl start docker2.2 停止docker systemctl stop docker2.3 重启docker systemctl restart docker2.4查看docker状态 systemctl status docker2.5开机启动 systemctl enable docker2.6查看docker概要信息 …...

[MRCTF2020]PixelShooter
一个apk文件 jeb打开发现是apk文件 apk游戏逆向必须知道的知识: 一般关键数据在 Assets/bin/data/managed/assembly-csharp.dll这个文件里面 我不知道jeb为什么这里我没有 apk是个压缩包 直接解压 这个文件解压也可以发现flag {Unity_1S_Fun_233}...

vue实现的商品列表网页
一、商品列表效果如下 二、代码; vue实现的商品列表网页 , 图片在vue项目的Public文件夹里的 imgs中 <template><div class"common-layout"><!-- el-container:外层容器。 当子元素中包含 <el-header> 或 <el-foo…...
【泛微系统】e-cology非标配功能概览
关于泛微非标功能的功能编号、功能名称及支持版本 编号名称支持版本001考勤功能4.500.0124-9.00+KB900190206002短信通用接口5.000.0327+KB50001003 及以上版本004计划任务接口5.0+KB50001003及以上版本005集成登录接口6.0及以上版本006流程中自定义浏览框5.0+KB50001003及以上…...

Python基础教程(二十八):pip模块
💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 💝Ὁ…...

通信系统概述
1.定义 通信系统(也称为通信网络)是利用各种通信线路将地理上分散的、具有独立功能的计算机系统和通信设备按不同的形式连接起来,依靠网络软件及通信协议实现资源共享和信息传递的系统。 2.概述 随着通信技术和网络技术的不断发展ÿ…...

http发展史(http0.9、http1.0、http1.1、http/2、http/3)详解
文章目录 HTTP/0.9HTTP/1.0HTTP/1.1队头阻塞(Head-of-Line Blocking)1. TCP 层的队头阻塞2. HTTP/1.1 的队头阻塞 HTTP/2HTTP/3 HTTP/0.9 发布时间:1991年 特点: 只支持 GET 方法没有 HTTP 头部响应中只有 HTML 内容࿰…...
Hadoop 面试题(四)
1. 简述Hadoop节点的动态上线下线的大概操作 ? 在Hadoop集群中,节点的动态上下线指的是在不停止整个集群服务的情况下,添加或移除节点。这种能力对于维护和扩展集群非常重要。以下是Hadoop节点动态上线下线的大概操作步骤: 动态…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...

Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...

实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...

企业大模型服务合规指南:深度解析备案与登记制度
伴随AI技术的爆炸式发展,尤其是大模型(LLM)在各行各业的深度应用和整合,企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者,还是积极拥抱AI转型的传统企业,在面向公众…...

海云安高敏捷信创白盒SCAP入选《中国网络安全细分领域产品名录》
近日,嘶吼安全产业研究院发布《中国网络安全细分领域产品名录》,海云安高敏捷信创白盒(SCAP)成功入选软件供应链安全领域产品名录。 在数字化转型加速的今天,网络安全已成为企业生存与发展的核心基石,为了解…...

UE5 音效系统
一.音效管理 音乐一般都是WAV,创建一个背景音乐类SoudClass,一个音效类SoundClass。所有的音乐都分为这两个类。再创建一个总音乐类,将上述两个作为它的子类。 接着我们创建一个音乐混合类SoundMix,将上述三个类翻入其中,通过它管理每个音乐…...

【PX4飞控】mavros gps相关话题分析,经纬度海拔获取方法,卫星数锁定状态获取方法
使用 ROS1-Noetic 和 mavros v1.20.1, 携带经纬度海拔的话题主要有三个: /mavros/global_position/raw/fix/mavros/gpsstatus/gps1/raw/mavros/global_position/global 查看 mavros 源码,来分析他们的发布过程。发现前两个话题都对应了同一…...