log
信息: Server.服务器版本: Apache Tomcat/12.0.x-dev
信息: Java虚拟机版本: 21
下载源码https://github.com/apache/tomcat,并用idea打开,配置ant编译插件,或者使用我的代码
启动脚本是/bin/startup.bat
,内部又执行了bin\catalina.bat
脚本,最终是执行了java --classpath bin\bootstrap.jar org.apache.catalina.startup.Bootstrap
1. Bootstrap启动类main方法
java
private static final Object daemonLock = new Object();
private static volatile Bootstrap daemon = null;
public static void main(String args[]) {//传参空数组
synchronized (daemonLock) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
log.error("Init exception", t);
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 (command.equals("start")) {
daemon.setAwait(true);//设置属性为true
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
}
} catch (Throwable t) {
System.exit(1);
}
}
首先执行 init()
方法,然后执行 load()
方法,最后执行 start()
方法。下面开始逐个分析
2. 启动类init方法
java
ClassLoader catalinaLoader = null;
public void init() throws Exception {
//创建类加载器
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.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;
Method method = startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
2.1. 创建类加载器initClassLoaders
catalinaLoader = createClassLoader("common", null);
java
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception {
//配置类获取信息
String value = CatalinaProperties.getProperty("common" + ".loader");
//value是: "${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
value = replace(value);
List<Repository> repositories = new ArrayList<>();
String[] repositoryPaths = getPaths(value);
return ClassLoaderFactory.createClassLoader(repositoryPaths, parent);
}
public static ClassLoader createClassLoader(List<Repository> repositoryPaths, final ClassLoader parent){
//把上面的路径转换成具体的文件: repositoryPaths
// 0 = {URL@1158} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/"
// 1 = {URL@1159} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/annotations-api.jar"
// 2 = {URL@1160} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/catalina-ant.jar"
// 3 = {URL@1161} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/catalina-ha.jar"
// 4 = {URL@1162} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/catalina-ssi.jar"
// 5 = {URL@1163} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/catalina-storeconfig.jar"
// 6 = {URL@1164} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/catalina-tribes.jar"
// 7 = {URL@1165} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/catalina.jar"
// 8 = {URL@1166} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/ecj-4.33.jar"
// 9 = {URL@1167} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/el-api.jar"
// 10 = {URL@1168} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/jakartaee-migration-1.0.8-shaded.jar"
// 11 = {URL@1169} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/jasper-el.jar"
// 12 = {URL@1170} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/jasper.jar"
// 13 = {URL@1171} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/jaspic-api.jar"
// 14 = {URL@1172} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/jsp-api.jar"
// 15 = {URL@1173} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/servlet-api.jar"
// 16 = {URL@1174} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-api.jar"
// 17 = {URL@1175} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-coyote-ffm.jar"
// 18 = {URL@1176} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-coyote.jar"
// 19 = {URL@1177} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-dbcp.jar"
// 20 = {URL@1178} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-cs.jar"
// 21 = {URL@1179} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-de.jar"
// 22 = {URL@1180} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-es.jar"
// 23 = {URL@1181} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-fr.jar"
// 24 = {URL@1182} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-ja.jar"
// 25 = {URL@1183} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-ko.jar"
// 26 = {URL@1184} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-pt-BR.jar"
// 27 = {URL@1185} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-ru.jar"
// 28 = {URL@1186} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-zh-CN.jar"
// 29 = {URL@1187} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-jdbc.jar"
// 30 = {URL@1188} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-jni.jar"
// 31 = {URL@1189} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-util-scan.jar"
// 32 = {URL@1190} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-util.jar"
// 33 = {URL@1191} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-websocket.jar"
// 34 = {URL@1192} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/websocket-api.jar"
// 35 = {URL@1193} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/websocket-client-api.jar"
if (parent == null) {
return new URLClassLoader(repositoryPaths);
} else {
return new URLClassLoader(repositoryPaths, parent);
}
}
//创建了一个URLClassLoader
2.2. 指定父加载器
java
public void setParentClassLoader(ClassLoader parentClassLoader) {
this.parentClassLoader = parentClassLoader;
}
init
方法完成后,主要做了以下几件事情:
- 创建了类加载器
catalinaLoader
,用于加载 Tomcat 的核心类。 - 设置当前线程的上下文类加载器为
catalinaLoader
。 - 通过反射机制加载
org.apache.catalina.startup.Catalina
类,并创建其实例。 - 设置
Catalina
实例的父类加载器为共享类加载器sharedLoader
。
整体来说,init
方法的作用是初始化 Tomcat 启动所需的类加载器,并准备好 Catalina
实例以供后续使用。
3. 启动类load方法
daemon.load(args);
java
Catalina catalinaDaemon;//这个值在上一步init方法赋值了
private void load(String[] arguments) throws Exception {
// Call the load() method
String methodName = "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;
}
Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isTraceEnabled()) {
log.trace("Calling startup class " + method);
}
method.invoke(catalinaDaemon, param);
//也是反射调用,调用了Catalina类中的load方法
}
在看下Catalina类的方法
java
public void load() {
// Before digester - it may be needed
initNaming();//设置系统naming变量
// Parse main server.xml
parseServerXml(true);//这个是重点,解析server.xml文件
Server s = getServer();
if (s == null) { //StandardServer[8005]
return;
}
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());//D:\apps\tomcat-source\tomcat\output\build
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());//D:\apps\tomcat-source\tomcat\output\build
// Stream redirection
initStreams();//设置系统打印输出
// Start the new server
try {
getServer().init();//初始化server容器
} catch (LifecycleException e) {
if (throwOnInitFailure) {
throw new Error(e);
}
}
}
3.1. 设置系统naming变量
没啥好说的,就是设置变量
java
System.setProperty("catalina.useNaming", "true");
System.setProperty("java.naming.factory.url.pkgs", "org.apache.naming");
System.setProperty("java.naming.factory.initial", "org.apache.naming.java.javaURLContextFactory");
3.2. 解析server.xml文件
parseServerXml(true);
3.2.1. server.xml文件内容
删除了注释
xml
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<!-- A "Service" is a collection of one or more "Connectors" that share
a single "Container" Note: A "Service" is not itself a "Container",
so you may not define subcomponents such as "Valves" at this level.
Documentation at /docs/config/service.html
-->
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<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>
3.2.2. 解析源代码
java
protected void parseServerXml(boolean start) {
// Set configuration source配置文件目录
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), "conf/server.xml"));
File file = configFile();//D:\apps\tomcat-source\tomcat\output\build\conf\server.xml
try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
// Create and execute our Digester
Digester digester = createStartDigester();//创建digester工具用于解析xml文件
InputStream inputStream = resource.getInputStream();
//resource: D:\apps\tomcat-source\tomcat\output\build\conf\server.xml
InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
inputSource.setByteStream(inputStream);
digester.push(this);
//开始解析xml
digester.parse(inputSource);
} catch (Exception e) {
log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e);
}
}
3.2.2.1. 创建digester工具类
创建一个支持解析xml格式的工具,用于解析sever.xml文件,把对应的xml标签创建对应的java类。此类继承了org.xml.sax.ext.DefaultHandler2
类(jre包下的)
java
/**
* Create and configure the Digester we will be using for startup.
*
* @return the main digester to parse server.xml
*/
protected Digester createStartDigester() {
// Initialize the digester
Digester digester = new Digester();
// Configure the actions we will be using 配置<Server>标签的java类,还有setServer方法
digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className");
digester.addSetProperties("Server");
digester.addSetNext("Server", "setServer", "org.apache.catalina.Server");
//GlobalNamingResources标签
digester.addObjectCreate("Server/GlobalNamingResources", "org.apache.catalina.deploy.NamingResourcesImpl");
digester.addSetProperties("Server/GlobalNamingResources");
digester.addSetNext("Server/GlobalNamingResources", "setGlobalNamingResources", "org.apache.catalina.deploy.NamingResourcesImpl");
digester.addRule("Server/Listener", new ListenerCreateRule(null, "className"));
digester.addSetProperties("Server/Listener");
digester.addSetNext("Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener");
//创建<Service>标签对应的java类
digester.addObjectCreate("Server/Service", "org.apache.catalina.core.StandardService", "className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service", "addService", "org.apache.catalina.Service");
//Connector标签对应的java类
digester.addRule("Server/Service/Connector", new ConnectorCreateRule());
digester.addSetProperties("Server/Service/Connector", new String[] { "executor", "sslImplementationName", "protocol" });
digester.addSetNext("Server/Service/Connector", "addConnector", "org.apache.catalina.connector.Connector");
// Add RuleSets for nested elements
digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
// When the 'engine' is found, set the parentClassLoader.
digester.addRule("Server/Service/Engine", new SetParentClassLoaderRule(parentClassLoader));
//省略部分代码...
return digester;
}
3.2.2.2. digester解析
digester.parse(inputSource);
java
public void parse(InputSource inputSource) throws SAXException, IOException {
// parse document
try {
XMLInputSource xmlInputSource =
new XMLInputSource(inputSource.getPublicId(),
inputSource.getSystemId(),
null, false);
xmlInputSource.setByteStream(inputSource.getByteStream());
xmlInputSource.setCharacterStream(inputSource.getCharacterStream());
xmlInputSource.setEncoding(inputSource.getEncoding());
parse(xmlInputSource);
}
}
//最终调用了jre库下面的类
public boolean scanDocument(boolean complete) throws IOException, XNIException {
// keep dispatching "events"
fEntityManager.setEntityHandler(this);
int event = next();//这个方法是核心解析处理方法,生成xml标签对应的java类也是在这个方法中
do {
//循环读取xml文件里面的每一个标签内容,包含注释,和下面做匹配
//断点发现真正处理的标签的逻辑不在这些case中,而是在next()方法中。下面详细看一下next方法
switch (event) {
case XMLStreamConstants.START_DOCUMENT ://文档解析开始
break;
case XMLStreamConstants.START_ELEMENT ://标签解析开始
break;
case XMLStreamConstants.CHARACTERS ://字符处理
fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity);
fDocumentHandler.characters(getCharacterData(),null);
break;
case XMLStreamConstants.SPACE:
break;
case XMLStreamConstants.ENTITY_REFERENCE ://实体引用
fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity);
break;
case XMLStreamConstants.PROCESSING_INSTRUCTION :
fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity);
fDocumentHandler.processingInstruction(getPITarget(),getPIData(),null);
break;
case XMLStreamConstants.COMMENT ://文档注释
fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity);
fDocumentHandler.comment(getCharacterData(),null);
break;
case XMLStreamConstants.DTD :
break;
case XMLStreamConstants.CDATA:
break;
default :
return false;
}
//System.out.println("here in before calling next");
event = next();
//System.out.println("here in after calling next");
} while (event!=XMLStreamConstants.END_DOCUMENT && complete);
if(event == XMLStreamConstants.END_DOCUMENT) {
fDocumentHandler.endDocument(null);
return false;
}
return true;
} // scanDocument(boolean):boolean
3.2.2.2.1. 解析xml文件next()核心方法
读取xml文件内容,判断标签类型,如果是'<'
开头的,走到SCANNER_STATE_START_OF_MARKUP
java
//下面的这些方法是java包下的类,逻辑很长复杂,我省略了部分代码
public int next() throws IOException, XNIException {
try {
do {
if (fEntityScanner.skipChar('<', null)) {
fScannerState = SCANNER_STATE_START_OF_MARKUP;
}
switch (fScannerState) {
case SCANNER_STATE_START_OF_MARKUP: {
//标签hook方法
return scanStartElement();
}
//省略部分代码...
}
} while (fScannerState == SCANNER_STATE_PROLOG || fScannerState == SCANNER_STATE_START_OF_MARKUP );
}
//组装参数,继续调用,第一步是扫描到了<Server>标签
protected boolean scanStartElement(){
//省略部分代码...
String fElementQName = "Server";
Attributes fAttributes = {["port","8005","shutdown"]};
fDocumentHandler.startElement(fElementQName, fAttributes, null, null);
}
//调用到了tomcat创建的Digester类
public void startElement(String namespaceURI, String localName, String qName, Attributes list) {
// Fire "begin" events for all relevant rules
//getRules方法就是获取最开始的配置信息,这里获取到的是Server标签的配置
List<Rule> rules = getRules().match(namespaceURI, match);
//rules = {ArrayList@1877} size = 3
// 0 = {ObjectCreateRule@1871} "ObjectCreateRule[className=org.apache.catalina.core.StandardServer, attributeName=className]"
// 1 = {SetPropertiesRule@2025} "SetPropertiesRule[]"
// 2 = {SetNextRule@2026} "SetNextRule[methodName=setServer, paramType=org.apache.catalina.Server]"
matches.push(rules);
if ((rules != null) && (rules.size() > 0)) {
//这里的rulus就是上面的这三个对象:功能分别是创建StandardServer对象、设置server对象里面的变量、调用setServer方法
for (Rule value : rules) {
try {
//执行具体的逻辑:例如创建StandardServer对象、设置StandardServer属性、调用setServer方法
//详情参考下面的分析代码
value.begin(namespaceURI, name, list);
} catch (Error e) {
log.error(sm.getString("digester.error.begin"), e);
throw e;
}
}
}
}
3.2.2.2.1.1. Digester类的getRules()方法
js
this.rules = {RulesBase@1296}
cache = {HashMap@2032} size = 137
"Server/Service/Engine/Host/Cluster/Channel/Membership/Member" -> {ArrayList@2137} size = 3
"Server/Service/Engine/Host/Cluster/Channel/ChannelListener" -> {ArrayList@2139} size = 3
"Server" -> {ArrayList@1877} size = 3
"Server/Service/Engine/Host/Context/Realm/Realm/Realm" -> {ArrayList@2141} size = 3
"Server/Service/Engine/Host/Context" -> {ArrayList@2143} size = 4
"Server/Listener" -> {ArrayList@2249} size = 3
"Server/GlobalNamingResources" -> {ArrayList@2251} size = 3
"Server/Service/Engine/Cluster/Channel/Sender" -> {ArrayList@2253} size = 3
"Server/GlobalNamingResources/Resource" -> {ArrayList@2283} size = 3
"Server/Service" -> {ArrayList@2285} size = 3
//省略部分。一共是137个key值
3.2.2.2.1.2. 创建StandardServer对象逻辑
value.begin(namespaceURI, name, list);
java
public void begin(String namespace, String name, Attributes attributes) {
//获取到了 org.apache.catalina.core.StandardServer
String realClassName = getRealClassName(attributes);
//反射创建StandardServer
// Instantiate the new object and push it on the context stack
Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
Object instance = clazz.getConstructor().newInstance();
//把对象放到digester中
digester.push(instance);
}
3.2.2.2.1.3. 设置StandardServer属性
赋值变量。"port","8005"
java
public void begin(String namespace, String theName, Attributes attributes) {
//attributes = ["port","8005","shutdown"];
for (int i = 0; i < attributes.getLength(); i++) {
String name = attributes.getLocalName(i);//port
String value = attributes.getValue(i);//8005
//赋值
IntrospectionUtils.setProperty(top, name, value, true, actualMethod);
}
}
//赋值方法setProperty
public static boolean setProperty(Object o, String name, String value, boolean invokeSetProperty, StringBuilder actualMethod) {
//上面的传参"o"是StandardServer实例对象
String setter = "set" + capitalize(name);//setPort
try {
Method methods[] = findMethods(o.getClass());
Method setPropertyMethodVoid = null;
Method setPropertyMethodBool = null;
// First, the ideal case - a setFoo( String ) method
//反射调用setPort方法
for (Method item : methods) {
Class<?> paramT[] = item.getParameterTypes();
if (setter.equals(item.getName())) {
//反射invoke
item.invoke(o, new Object[]{value});
return true;
}
}
}
//省略部分代码...
3.2.2.2.1.4. 调用setServer方法
给Catalina实例赋值sever
3.2.2.2.2. 继续解析server.xml文件
解析server标签中的Listener、Service、Connector、Engine、Host等标签,创建对应的实例java类。
创建逻辑和上面Server逻辑是一样的。
sh
StandardServer
StandardService
Connector
StandardEngine
StandardHost
3.2.2.2.2.1. 值得注意的是Connector类的构造方法中创建了Http11NioProtocol实例
java
if (protocol == null || "HTTP/1.1".equals(protocol) ||
org.apache.coyote.http11.Http11NioProtocol.class.getName().equals(protocol)) {
return new org.apache.coyote.http11.Http11NioProtocol();//tomcat8之后默认使用nio
} else if ("AJP/1.3".equals(protocol) ||
org.apache.coyote.ajp.AjpNioProtocol.class.getName().equals(protocol)) {
return new org.apache.coyote.ajp.AjpNioProtocol();
} else {
// Instantiate protocol handler
Class<?> clazz = Class.forName(protocol);
return (ProtocolHandler) clazz.getConstructor().newInstance();
}
3.2.2.2.2.2. StandardEngine构造方法中创建了StandardEngineValve实例
java
public StandardEngine() {
pipeline.setBasic(new StandardEngineValve());
// By default, the engine will hold the reloading thread
backgroundProcessorDelay = 10;
}
3.2.2.2.2.3. StandardHost构造方法中创建了StandardHostValve实例
java
public StandardHost() {
super();
pipeline.setBasic(new StandardHostValve());
}
3.3. 设置系统打印输出
java
// 重定向系统输出流
protected void initStreams() {
// Replace System.out and System.err with a custom PrintStream
System.setOut(new SystemLogHandler(System.out));
System.setErr(new SystemLogHandler(System.err));
}
3.4. 初始化server容器
java
getServer().init();//初始化server容器
StandardServer
继承了 LifecycleBase
抽象类,因此也继承了 init
方法。LifecycleBase
是 Tomcat 中用于管理组件生命周期的基类。它定义了一些通用的生命周期方法,如 init
、start
、stop
和 destroy
,并提供了状态管理和事件通知的机制。通过继承 LifecycleBase
,StandardServer
可以利用这些通用的生命周期管理功能,确保在初始化、启动、停止和销毁过程中执行必要的操作和触发相应的事件。
java
public final synchronized void init() throws LifecycleException {
try {
setStateInternal(LifecycleState.INITIALIZING, null, false);//StandServer初始化前执行
initInternal();//StandardServer初始化
setStateInternal(LifecycleState.INITIALIZED, null, false);//StandardServer初始化后执行,这里没有我们关心的逻辑,重点是上一行【StandardServer初始化】
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
3.4.1. StandServer初始化前执行
setStateInternal(LifecycleState.INITIALIZING, null, false);
设置当前standerServer
的状态为INITIALIZING
类型,并且使用观察者模式,执行初始化前事件。
java
//设置状态
this.state = LifecycleState.INITIALIZING;
//触发事件
protected void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(this, type, data);
for (LifecycleListener listener : lifecycleListeners) {//遍历事件监听器列表执行事件方法
listener.lifecycleEvent(event);//调用具体的事件监听器
}
}
3.4.1.1. StandServer事件监听器列表
下面这些监听器是在server.xml文件配置的
在 Tomcat 12 中,StandardServer
类的 lifecycleListeners
包含以下监听器:
NamingContextListener
:用于管理 JNDI(Java Naming and Directory Interface)资源的生命周期。它在 Tomcat 启动时初始化 JNDI 资源,并在停止时清理这些资源。VersionLoggerListener
:用于在 Tomcat 启动时记录版本信息,包括操作系统、JVM 和 Tomcat 的版本。AprLifecycleListener
:用于初始化和终止 APR(Apache Portable Runtime)库。它还负责初始化 SSL(如果配置了)并处理 FIPS 模式(如果启用)。JreMemoryLeakPreventionListener
:用于防止 JRE 内存泄漏。它通过触发一些特定的 JVM 操作来防止常见的内存泄漏问题。GlobalResourcesLifecycleListener
:用于管理全局 JNDI 资源。它在 Tomcat 启动时初始化这些资源,并在停止时清理它们。ThreadLocalLeakPreventionListener
:用于防止线程本地变量引起的内存泄漏。它在 Tomcat 停止时清理线程本地变量,以防止内存泄漏。
这些监听器在 Tomcat 启动、停止等生命周期事件中执行特定的操作,以确保服务器的正常运行和资源管理。这些都是基本的监听器,这里不再详细分析代码
3.4.2. StandServer初始化init()
initInternal();
java
protected void initInternal() throws LifecycleException {
//注册当前standerServer到java的MBean中(在Java中,MBean[Managed Bean]是用于管理和监控资源的 Java 对象。类似于Spring的IoC。来实现对象的管理和监控。)
super.initInternal();
// Register the naming resources
//把NamingResourcesImpl对象注册到MBean中
globalNamingResources.init();
// Initialize our defined Services
for (Service service : findServices()) {//这里的service就一个:StandardService
service.init();//StandardService初始化
}
}
3.4.2.1. StandardService初始化init()
service.init();
我们先看一下StandardService
类,也继承了LifecycleBase
抽象类,因此也继承了 init
方法。
java
public final synchronized void init() throws LifecycleException {
try {
setStateInternal(LifecycleState.INITIALIZING, null, false);//StandardService初始化前执行,主要是触发初始化前监听器,standerService默认没有监听器,这里没有逻辑
initInternal();//StandardService初始化
setStateInternal(LifecycleState.INITIALIZED, null, false);//StandardService初始化后执行,这里没有重要的逻辑
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
3.4.2.1.1. StandardService初始化前执行
setStateInternal(LifecycleState.INITIALIZING, null, false);
设置当前standerService
的状态为INITIALIZING
类型,并且使用观察者模式,执行初始化前事件。
java
//设置状态
this.state = LifecycleState.INITIALIZING;
//触发事件
protected void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(this, type, data);
for (LifecycleListener listener : lifecycleListeners) {//遍历事件监听器列表执行事件方法,在这里的service没有监听器,lifecycleListeners.size()=0
listener.lifecycleEvent(event);//调用具体的事件监听器
}
}
3.4.2.1.2. StandardService初始化
initInternal()
开始执行service初始化了,直接看下面代码
java
protected void initInternal() throws LifecycleException {
//注册StandardService到MBean中
super.initInternal();
if (engine != null) {//执行StanderEngine初始化,这里也可以看出来只有一个engine
engine.init();
}
// Initialize any Executors
//默认没有配置executor,没有找到!
//看源码这个Executor继承了java包下的ExecutorService,多线程相关
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
// Initialize mapper listener
//mapperListener映射器初始化
mapperListener.init();
// Initialize our defined Connectors
//这里只有一个connector:["http-nio-8080"]
for (Connector connector : findConnectors()) {//执行Connector初始化,这里可以看到允许存在多个connector
connector.init();
}
}
3.4.2.1.2.1. StandardEngine初始化init()
engine.init();
执行engine初始化操作。
我们先看一下StandardEngine
类,也继承了LifecycleBase
抽象类,因此也继承了 init
方法。
java
public final synchronized void init() throws LifecycleException {
try {
setStateInternal(LifecycleState.INITIALIZING, null, false);//StandardEngine初始化前执行,触发监听器,这里没有INITIALIZING的监听事件
initInternal();//StandardEngine初始化
setStateInternal(LifecycleState.INITIALIZED, null, false);//StandardEngine初始后执行,触发监听器,这里没有INITIALIZED的监听事件
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
3.4.2.1.2.1.1. StandardEngine初始化
initInternal();
java
protected void initInternal() throws LifecycleException {
// Ensure that a Realm is present before any attempt is made to start
// one. This will create the default NullRealm if necessary.
getRealm();//获取LockOutRealm[StandardEngine] 这个是在server.xml文件配置的
super.initInternal();//注册StanderdEngine到MBean
}
3.4.2.1.2.2. mapperListener映射器初始化
mapperListener.init();
MapperListener初始化方法。
我们先看一下MapperListener
类,也继承了LifecycleBase
抽象类,因此也继承了 init
方法。
java
public final synchronized void init() throws LifecycleException {
try {
setStateInternal(LifecycleState.INITIALIZING, null, false);//MapperListener初始化前执行,主要是触发初始化前监听器,MapperListener默认没有监听器,这里没有逻辑
initInternal();//MapperListener初始化:注册MapperListener到MBean
setStateInternal(LifecycleState.INITIALIZED, null, false);//MapperListener初始化后执行,这里没有重要的逻辑
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
3.4.2.1.2.3. Connector初始化init()
connector.init();
,Connector["http-nio-8080"]
初始化方法。
我们先看一下Connector
类,也继承了LifecycleBase
抽象类,因此也继承了 init
方法。
java
public final synchronized void init() throws LifecycleException {
try {
setStateInternal(LifecycleState.INITIALIZING, null, false);//Connector初始化前执行,主要是触发初始化前监听器,Connector默认没有监听器,这里没有逻辑
initInternal();//Connector初始化
setStateInternal(LifecycleState.INITIALIZED, null, false);//Connector初始化后执行,这里没有重要的逻辑
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
3.4.2.1.2.3.1. Connector初始化
initInternal();
Connector"http-nio-8080"初始化代码
java
protected void initInternal() throws LifecycleException {
//注册Connector到MBean
super.initInternal();
// Initialize adapter
//创建adapter对象:CoyoteAdapter 是 Tomcat 中的一个适配器类,用于将 Coyote 请求和响应对象转换为 Tomcat 的内部请求和响应对象
adapter = new CoyoteAdapter(this);
//Http11NioProtocol指定adapter
protocolHandler.setAdapter(adapter);
// Make sure parseBodyMethodsSet has a default
//配置默认请求解析方法:POST
if (null == parseBodyMethodsSet) {
setParseBodyMethods(getParseBodyMethods());
}
try {
//http-nio-8080协议初始化
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
3.4.2.1.2.3.1.1. Http11NioProtocol初始化
protocolHandler.init();
http-nio-8080协议初始化,注意这个类没有继承 LifecycleBase
java
public void init() throws Exception {
//创建HTTP标头值解析器实现。将原始的 HTTP 数据流解析成结构化的数据:解析请求行:请求行、请求头、请求体、响应头等等
httpParser = new HttpParser(relaxedPathChars, relaxedQueryChars);
try {
//当前类Http11NioProtocol继承了AbstractProtocol
super.init();//AbstractProtocol初始化
} finally {
//nothing
}
}
3.4.2.1.2.3.1.2. AbstractProtocol初始化
super.init();
AbstractProtocol初始化
java
public void init() throws Exception {
//输出日志
if (getLog().isInfoEnabled()) {
getLog().info("信息: 初始化协议处理器 [http-nio-8080]");
logPortOffset();
}
//注册Http11NioProtocol[http-nio-8080]到MBean
if (oname == null) {
// Component not pre-registered so register it
oname = createObjectName();//Catalina:type=ProtocolHandler,port=8080
if (oname != null) {
Registry.getRegistry(null, null).registerComponent(this, oname, null);
}
}
if (this.domain != null) {
ObjectName rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
this.rgOname = rgOname;//Catalina:type=GlobalRequestProcessor,name="http-nio-8080"
//注册GlobalRequestProcessor到MBean
Registry.getRegistry(null, null).registerComponent(getHandler().getGlobal(), rgOname, null);
}
String endpointName = getName();//"http-nio-8080"
endpoint.setName(endpointName.substring(1, endpointName.length() - 1));//http-nio-8080
endpoint.setDomain(domain);//Catalina
//endpoint = {NioEndpoint@2353}
endpoint.init();//NioEndpoint初始化
}
3.4.2.1.2.3.1.3. NioEndpoint初始化
endpoint.init()
,NioEndpoint"http-nio-8080" 初始化。主要逻辑是就是bind 8080端口。
java
public final void init() throws Exception {
if (bindOnInit) {//初始化的时候,开始绑定端口[8080]
bindWithCleanup();//执行bind0方法:是native方法。
bindState = BindState.BOUND_ON_INIT;
}
if (this.domain != null) {
// Register endpoint (as ThreadPool - historical name)
oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");
//Catalina:name="http-nio-8080",type=ThreadPool 注册到MBean
Registry.getRegistry(null, null).registerComponent(this, oname, null);
ObjectName socketPropertiesOname = new ObjectName(domain +
":type=SocketProperties,name=\"" + getName() + "\"");
//Catalina:name="http-nio-8080",type=SocketProperties 注册到MBean
socketProperties.setObjectName(socketPropertiesOname);
Registry.getRegistry(null, null).registerComponent(socketProperties, socketPropertiesOname, null);
}
}
至此server.load
方法就完成了
4. 启动类start方法
daemon.start();
是tomcat最重要的一步,也是tomcat启动流程的最后一步
java
public void start() {
//记录启动开始时间
long t1 = System.nanoTime();
// Start the new server
try {
//执行StandardServer的start方法
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug(sm.getString("catalina.destroyFail"), e1);
}
return;
}
if (log.isInfoEnabled()) {
log.info("信息: [55700]毫秒后服务器启动");
}
// Register shutdown hook
//注册关闭tomcat的hook,当关闭tomcat时执行
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(false);
}
}
//保持main线程一直运行,直到shutdown命令
if (await) {
await();
stop();
}
}
4.1. 执行StandardServer的start
getServer().start();
执行StandardServer的start方法。
StandardServer
类继承了LifecycleBase
抽象类,因此也继承了start
方法。LifecycleBase
提供了通用的生命周期管理功能,包括init
、start
、stop
和destroy
方法。StandardServer
的start
方法用于启动 Tomcat 服务器,具体实现如下:
java
public final synchronized void start() throws LifecycleException {
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);//触发before_start事件,相应的事件监听器触发执行逻辑,这里standardServer的监听器有6个,参考 标题【#### 3.4.1.1. StandServer事件监听器列表】
startInternal();//StandardServer启动方法
setStateInternal(LifecycleState.STARTED, null, false);//触发after_start事件
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
StandardServer
启动方法:startInternal();
java
protected void startInternal() throws LifecycleException {
//触发configure_start事件
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);//设置为start状态
// Initialize utility executor
synchronized (utilityExecutorLock) {
//创建utilityExecutor调度线程池,设置corePoolSize=2
reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
//注册utilityExecutor到MBean
register(utilityExecutor, "type=UtilityExecutor");
}
//全局命名资源启动,例如数据库连接、JNDI
globalNamingResources.start();
// Start our defined Services
for (Service service : findServices()) {//只有一个service = "StandardService[Catalina]"
service.start();//StandardService启动
}
if (periodicEventDelay > 0) {
monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(this::startPeriodicLifecycleEvent, 0, 60,
TimeUnit.SECONDS);
}
}
4.2. 执行NamingResourcesImpl的start
globalNamingResources.start();
全局命名资源启动。
NamingResourcesImpl
类继承了 LifecycleBase
抽象类,因此也继承了 start
方法。
java
public final synchronized void start() throws LifecycleException {
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);//触发before_start事件,相应的事件监听器触发执行逻辑,这里NamingResourcesImpl没有监听器
startInternal();//NamingResourcesImpl启动方法,主要逻辑是触发configure_start、start两个事件,但是NamingResourcesImpl没有监听器不执行逻辑
setStateInternal(LifecycleState.STARTED, null, false);//触发after_start事件,NamingResourcesImpl没有监听器不执行逻辑
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
4.2. 执行StandardService的start
service.start()
StandardService启动。
StandardService
类继承了LifecycleBase
抽象类,因此也继承了start
方法。
java
public final synchronized void start() throws LifecycleException {
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);//触发before_start事件,相应的事件监听器触发执行逻辑,这里 StandardService 没有监听器
startInternal();//StandardService 启动方法
setStateInternal(LifecycleState.STARTED, null, false);//触发after_start事件,StandardService 没有监听器不执行逻辑
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
StandardService
启动方法:startInternal();
java
protected void startInternal() throws LifecycleException {
if (log.isInfoEnabled()) {
log.info("正在启动服务[Catalina]");
}
setState(LifecycleState.STARTING);//start
// Start our defined Container first
if (engine != null) {
engine.start();//StandardEngine启动
}
for (Executor executor : findExecutors()) {
executor.start();
}
mapperListener.start();
// Start our defined Connectors second
for (Connector connector : findConnectors()) {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
}
4.2.1. 执行StandardEngine的start
engine.start()
StandardEngine启动。
StandardEngine
类继承了LifecycleBase
抽象类,因此也继承了start
方法。
java
public final synchronized void start() throws LifecycleException {
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);//触发before_start事件,相应的事件监听器触发执行逻辑,这里 StandardEngine 没有处理before_start事件
startInternal();//StandardEngine 启动方法
setStateInternal(LifecycleState.STARTED, null, false);//触发after_start事件,StandardEngine 没有重要逻辑
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
StandardEngine
启动方法:startInternal();
java
protected void startInternal() throws LifecycleException {
// Log our server identification information
if (log.isInfoEnabled()) {
log.info("正在启动 Servlet 引擎:[Apache Tomcat/12.0.x-dev]");
}
// Standard container startup
super.startInternal();//父类是ContainerBase
}
- engine父类
ContainerBase
启动方法:startInternal();
java
protected void startInternal() throws LifecycleException {
//重新创建startStopExecutor线程池,用于多线程执行启动子容器逻辑
reconfigureStartStopExecutor(getStartStopThreads());
// Start our subordinate components, if any
//获取LockOutRealm[StandardEngine] 这个是在server.xml文件配置的,用于配置tomcat管理页面登录账号与密码 http://localhost:8080/manager/html
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();//启动tomcat管理页面登录账号与密码数据库类,有兴趣话再仔细看下逻辑
}
// Start our child containers, if any
Container[] children = findChildren();//只获取到一个:StandardHost[localhost]
List<Future<Void>> results = new ArrayList<>(children.length);
for (Container child : children) {
//child = StandardHost[localhost]
//异步执行StandardHost的启动方法start。下面详情分析这一步
results.add(startStopExecutor.submit(new StartChild(child)));
}
MultiThrowable multiThrowable = null;//定义错误集合
for (Future<Void> result : results) {
try {
result.get();
} catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
if (multiThrowable == null) {
multiThrowable = new MultiThrowable();
}
multiThrowable.add(e);//如果报错了添加到错误集合
}
}
if (multiThrowable != null) {//如果有错误 报错、程序终止
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"), multiThrowable.getThrowable());
}
// Start the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
setState(LifecycleState.STARTING);
// Start our thread
if (backgroundProcessorDelay > 0) {
monitorFuture = Container.getService(ContainerBase.this).getServer().getUtilityExecutor()
.scheduleWithFixedDelay(new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
}
}
4.2.1.1. 执行StandardHost的start
results.add(startStopExecutor.submit(new StartChild(child)));
这一步是异步,先看下new StartChild(child);
java
private static class StartChild implements Callable<Void> {
private Container child;
StartChild(Container child) {
//child = StandardHost[localhost]
this.child = child;
}
@Override
public Void call() throws LifecycleException {
child.start();//调用start
return null;
}
}
就是StandardHost又封装了一层,继承了Callable接口,值得注意的是异步执行结果是Void类型。
StandardHost
启动方法start()
StandardHost
类继承了LifecycleBase
抽象类,因此也继承了start
方法。
java
public final synchronized void start() throws LifecycleException {
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);//触发before_start事件,相应的事件监听器触发执行逻辑
startInternal();//StandardHost 启动方法
setStateInternal(LifecycleState.STARTED, null, false);//触发after_start事件,StandardHost 没有重要逻辑
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
StandardHost
启动前执行方法setStateInternal(before_start)
StandardHost有一个事件监听器:HostConfig
。
java
public void beforeStart() {
//host = StandardHost[localhost]
if (host.getCreateDirs()) {//默认true
File[] dirs = new File[] { host.getAppBaseFile(), host.getConfigBaseFile() };
//getAppBaseFile() = D:\apps\tomcat-source\tomcat\output\build\webapps
//getConfigBaseFile() = D:\apps\tomcat-source\tomcat\output\build\conf\Catalina\localhost
for (File dir : dirs) {
//保证文件夹创建成功
if (!dir.mkdirs() && !dir.isDirectory()) {
log.error(sm.getString("hostConfig.createDirs", dir));
}
}
}
}
StandardHost
启动方法startInternal();
host启动方法主要逻辑是配置valve(阀门)。用于在请求处理的不同阶段对请求和响应进行拦截和处理。Valve 类似于 Servlet 过滤器。
java
protected void startInternal() throws LifecycleException {
// Set error report valve
String errorValve = getErrorReportValveClass();//org.apache.catalina.valves.ErrorReportValve
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
//getPipeline().getValves() =
// 0 = {AccessLogValve@4043} 用于记录 HTTP 请求的访问日志。
// 1 = {StandardHostValve@4044} 负责处理 HTTP 请求并将其传递给适当的子容器(通常是 StandardContext,即具体的 Web 应用程序)。
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if (!found) {
Valve valve = ErrorReportValve.class.getName().equals(errorValve) ? new ErrorReportValve() :
(Valve) Class.forName(errorValve).getConstructor().newInstance();
getPipeline().addValve(valve);
//{ErrorReportValve@4408} 生成和处理错误响应页面。
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("standardHost.invalidErrorReportValveClass", errorValve), t);
}
}
//综上,为StandardHost配置3个valve
super.startInternal();//父类`ContainerBase`启动方法:`startInternal();`
}
StandardHost
配置3个valve
作用:
-
AccessLogValve:
- 作用:记录每个请求的访问日志。
- 详细描述 :
AccessLogValve
用于记录 HTTP 请求的详细信息,包括客户端 IP 地址、请求时间、请求方法、请求 URI、响应状态码等。它通常用于生成服务器的访问日志,以便进行分析和监控。
-
ErrorReportValve:
- 作用:处理和显示错误页面。
- 详细描述 :
ErrorReportValve
在发生错误时生成并返回一个用户友好的错误页面。它捕获 HTTP 错误状态码(如 404、500 等)和异常,并根据配置生成相应的错误响应页面。
-
StandardHostValve:
- 作用 :处理请求并将其分派到适当的
Context
。 - 详细描述 :
StandardHostValve
是Host
容器的默认Valve
,负责将传入的请求分派到适当的Context
(即 Web 应用)。它根据请求的 URI 确定目标Context
,并将请求传递给该Context
进行处理。
- 作用 :处理请求并将其分派到适当的
StandardHost
父类ContainerBase
启动方法:startInternal();
java
//pipeline = "StandardPipeline[StandardEngine[Catalina].StandardHost[localhost]]"
//pipeline作用是管理和执行一系列的 Valve
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();//调用StandardPipeline的启动方法
}
//其中主要逻辑是,遍历管理的valve,每个valve执行start方法
Valve current = first;
while (current != null) {
if (current instanceof Lifecycle) {
((Lifecycle) current).start();
}
current = current.getNext();
}
//StandardPipeline管理的valve:
// AccessLogValve[StandardEngine[Catalina].StandardHost[localhost]]
// ErrorReportValve[StandardEngine[Catalina].StandardHost[localhost]]
// StandardHostValve[StandardEngine[Catalina].StandardHost[localhost]]
//一共三个,没有执行start方法没有主要的逻辑。
值得注意的是StandardHost
的父类的启动方法中有设置状态为start的逻辑,触发了事件监听:
setState(LifecycleState.STARTING);
这个监听类是HostConfig
,下面是主要逻辑
java
public void start() {
try {
//注册Catalina:host=localhost,type=Deployer 到MBean
ObjectName hostON = host.getObjectName();
oname = new ObjectName(hostON.getDomain() + ":type=Deployer,host=" + host.getName());
Registry.getRegistry(null, null).registerComponent(this, oname, this.getClass().getName());
} catch (Exception e) {
log.warn(sm.getString("hostConfig.jmx.register", oname), e);
}
if (host.getDeployOnStartup()) {//默认true,自动化部署webapps文件夹下的应用
deployApps();//部署业务应用,这一步很重要
}
}
4.2.1.1.1. deployApps()
部署webapps应用
这段代码在HostConfig
中。由StandardHost
启动方法监听触发。
java
protected void deployApps() {
// Migrate legacy Java EE apps from legacyAppBase
//迁移旧版 Java EE 应用程序:调用 migrateLegacyApps 方法,将旧版应用程序从 legacyAppBase 迁移到新的应用程序基础目录。
migrateLegacyApps();//没有主要逻辑
File appBase = host.getAppBaseFile();//D:\apps\tomcat-source\tomcat\output\build\webapps
File configBase = host.getConfigBaseFile();//D:\apps\tomcat-source\tomcat\output\build\conf\Catalina\localhost
String[] filteredAppPaths = filterAppPaths(appBase.list());
//这个就是我们的tomcat家目录下面webapps中的默认文件应用
//filteredAppPaths = {String[5]@5272} ["docs", "examples", "host-manager", "manager", "ROOT"]
// Deploy XML descriptors from configBase
//部署 XML 描述符:调用 deployDescriptors 方法,从配置基础目录中部署 XML 描述符。
deployDescriptors(configBase, configBase.list());//没有主要逻辑
// Deploy WARs
//部署 WAR 文件:调用 deployWARs 方法,从应用程序基础目录中部署 WAR 文件。
//默认文件中没有war包,我们直接看下一步的文件包部署。其实war包和文件包部署原理是相似的,war解压后就是一个完整的文件包。
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
//部署展开的文件夹:调用 deployDirectories 方法,从应用程序基础目录中部署展开的文件夹。
deployDirectories(appBase, filteredAppPaths);
}
4.2.1.1.1.1. 部署展开的文件夹 deployDirectories(appBase, filteredAppPaths);
默认的文件有["docs", "examples", "host-manager", "manager", "ROOT"]
。我们只看examples
文件夹部署过程吧,其他的原理一样。
- 部署examples文件夹准备
java
protected void deployDirectories(File appBase, String[] files) {
//获取线程池
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<>();
for (String file : files) {
if (file.equalsIgnoreCase("META-INF")) {
continue;
}
if (file.equalsIgnoreCase("WEB-INF")) {
continue;
}
File dir = new File(appBase, file);
//dir = D:\apps\tomcat-source\tomcat\output\build\webapps\examples
if (dir.isDirectory()) {
ContextName cn = new ContextName(file, false);
//cn = /examples
if (tryAddServiced(cn.getName())) {//true
try {
if (deploymentExists(cn.getName())) {//false
removeServiced(cn.getName());
continue;
}
// DeployDirectory will call removeServiced
//异步执行部署docs文件夹
results.add(es.submit(new DeployDirectory(this, cn, dir)));
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
removeServiced(cn.getName());
throw t;
}
}
}
}
for (Future<?> result : results) {
try {
result.get();//获取部署结果
} catch (Exception e) {
log.error(sm.getString("hostConfig.deployDir.threaded.error"), e);
}
}
}
- 异步执行部署docs文件夹封装Runnable
results.add(es.submit(new DeployDirectory(this, cn, dir)));
先看下DeployDirectory
类。继承了Runnable
java
private static class DeployDirectory implements Runnable {
private HostConfig config;
private ContextName cn;
private File dir;
DeployDirectory(HostConfig config, ContextName cn, File dir) {
this.config = config;
this.cn = cn;
this.dir = dir;
}
@Override
public void run() {
try {
config.deployDirectory(cn, dir);//部署方法
} finally {
config.removeServiced(cn.getName());
}
}
}
- 执行核心部署逻辑deployDirectory
config.deployDirectory(cn, dir);
js
this = {HostConfig@3762} //代码依然是在HostConfig类中
cn = {ContextName@5420} "/examples"
dir = {File@5406} "D:\apps\tomcat-source\tomcat\output\build\webapps\examples" //部署的文件
我们先看下D:\apps\tomcat-source\tomcat\output\build\webapps\examples\META-INF\context.xml文件内容
xml
<Context ignoreAnnotations="true">
<CookieProcessor className="org.apache.tomcat.util.http.Rfc6265CookieProcessor"
sameSiteCookies="strict" />
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" />
</Context>
下面看下部署examples文件源码
java
protected void deployDirectory(ContextName cn, File dir) {
if (log.isInfoEnabled()) {
startTime = System.currentTimeMillis();
log.info("把web 应用程序部署到目录 [D:\apps\tomcat-source\tomcat\output\build\webapps\examples]");
}
// D:\apps\tomcat-source\tomcat\output\build\webapps\examples\META-INF\context.xml
context = (Context) digester.parse(xml);//digester是xml解析类,生成对应的java类
//org.apache.catalina.startup.ContextConfig
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
context.addLifecycleListener(listener);
//这里给context中添加了一个事件监听器ContextConfig,后续用到了!
//添加应用到StandardHost
host.addChild(context);
//省略部分代码...
}
4.2.1.1.1.1. digester解析context.xml文件 context = (Context) digester.parse(xml);
解析xml文件,生成了StandardContext [/examples]
类。
值得注意的是StandardContext构造方法中新增了StandardContextValve
阀门类
4.2.1.1.1.2. StandardContext[/examples]
添加应用到StandardHost
StandardHost类中,addChild方法核心代码是
java
protected final HashMap<String,Container> children = new HashMap<>();
public void addChild(Container child) {
//child = {StandardContext@5784} "StandardEngine[Catalina].StandardHost[localhost].StandardContext[/examples]"
children.put("/examples", child);
//调用StandardContext的start启动方法
child.start();
}
4.2.1.1.1.3. StandardContext[/examples]
的start启动方法
child.start();
。 同样StandardContext继承了LifecycleBase,因此继承了start方法。
java
@Override
public final synchronized void start() throws LifecycleException {
// state = NEW 默认是new,这里是true
if (state.equals(LifecycleState.NEW)) {
init();//初始化方法
}
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);//启动前
startInternal();//启动方法
setStateInternal(LifecycleState.STARTED, null, false);//启动后
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
- StandardContext初始化init()
这一步没有重要的逻辑。
js
StandardContext有3个监听器:
- **ContextConfig**:
- **作用**:`ContextConfig` 监听器负责处理 `Context` 的配置和初始化工作。它会解析 `web.xml` 文件,设置 `Context` 的各种参数,并执行必要的初始化操作。
- **StandardHost$MemoryLeakTrackingListener**:
- **作用**:跟踪和检测内存泄漏。
- **ThreadLocalLeakPreventionListener**:
- **作用**:防止线程本地变量泄漏。
- StandardContext启动方法startInternal()
这个启动方法是tomcat中最复杂的,代码很长,先看下作用的功能:
js
1. 日志记录和通知:记录启动日志并发送 JMX 通知,表示上下文正在启动。
2. 初始化资源:确保命名资源和工作目录已正确配置。
3. 加载器和资源:配置默认资源和类加载器。
4. 字符集映射:初始化字符集映射。
5. 命名上下文:配置命名上下文监听器。
6. 启动子组件:启动加载器、Realm、子容器和管道中的阀门。
7. 管理器配置:配置会话管理器,特别是在集群环境中。
8. 设置上下文属性:将资源、实例管理器、Jar扫描器等设置为上下文属性。
9. 调用初始化器:调用 `ServletContainerInitializer` 进行初始化。
10. 启动监听器和过滤器:配置并启动应用程序事件监听器和过滤器。
11. 加载启动时加载的Servlet:加载和初始化所有"启动时加载"的Servlet。
12. 启动后台处理线程:启动容器后台处理线程。
13. 设置状态:根据启动结果设置上下文的可用状态,并发送相应的JMX通知。
我们只分析一下最主要的步骤:解析StandardWrapper
,在此之前,我们先看下examples/WEB-INF/web.xml
文件内容
xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
metadata-complete="false"> <!-- 注解扫描解析 -->
<filter>
<filter-name>HTTP header security filter</filter-name>
<filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>hstsEnabled</param-name>
<param-value>false</param-value>
</init-param>
</filter>
<servlet>
<servlet-name>RequestInfoExample</servlet-name>
<servlet-class>RequestInfoExample</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RequestInfoExample</servlet-name>
<url-pattern>/servlets/servlet/RequestInfoExample/*</url-pattern>
</servlet-mapping>
<!-- 等等,定义了很多servlet -->
</web-app>
下面开始分析代码
java
protected void startInternal() throws LifecycleException {
//触发configure_start事件
//触发了ContextConfig监听器的方法:webConfig();
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
}
//扫描web.xml文件,扫描/WEB-INF/classes文件夹下的类
protected void webConfig() {
private final Map<String,String> servletMappings = new HashMap<>();
InputSource contextWebXml = getContextWebXmlSource();
//file:/D:/apps/tomcat-source/tomcat/output/build/webapps/examples/WEB-INF/web.xml
//解析web.xml,把配置的servlet解析到【servletMappings】
if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
ok = false;
}
//解析文件结果
// webXml.servletMappings = {HashMap@3471} size = 16
// "/async/stockticker" -> "stock"
// "/servlets/servlet/CookieExample" -> "CookieExample"
// "/CompressionTest" -> "CompressionFilterTestServlet"
// "/servlets/servlet/RequestParamExample" -> "RequestParamExample"
// "/servlets/servlet/RequestInfoExample/*" -> "RequestInfoExample"
// "/servlets/servlet/SessionExample" -> "SessionExample"
// "/async/async0" -> "async0"
// "/servlets/trailers/response" -> "responsetrailer"
// "/async/async1" -> "async1"
// "/servlets/servlet/RequestHeaderExample" -> "RequestHeaderExample"
// "/servlets/nonblocking/numberwriter" -> "numberwriter"
// "/servlets/servlet/HelloWorldExample" -> "HelloWorldExample"
// "/servletToJsp" -> "ServletToJsp"
// "/servlets/nonblocking/bytecounter" -> "bytecounter"
// "/async/async2" -> "async2"
// "/async/async3" -> "async3"
if (!webXml.isMetadataComplete()) {//这个是web.xml配置的metadata-complete="false"
// Steps 4 & 5.
//解析/WEB-INF/classes/*类
processClasses(webXml, orderedFragments);
}
//配置StandardContext,添加wrapper
configureContext(webXml);
}
另外,扫描注解配置,需要设置web.xml配置为<web-app metadata-complete="false">
- 解析/WEB-INF/classes/*类
processClasses(webXml, orderedFragments);
当前应用D:/apps/tomcat-source/tomcat/output/build/webapps/examples开始解析类文件了
java
protected void processClasses(WebXml webXml, Set<WebXml> orderedFragments) {
if (ok) {
WebResource[] webResources = context.getResources().listResources("/WEB-INF/classes");
/** webResources = 路径下的文件夹如下:
/WEB-INF/classes/
├── async
├── checkbox
├── colors
├── compressionFilters
├── CookieExample.java
├── dates
├── error
├── examples
├── filters
├── HelloWorldExample.java
├── HelloWorldExample2ForServletAnnotation.java
├── jsp2
├── listeners
├── nonblocking
├── num
├── RequestHeaderExample.java
├── RequestInfoExample.java
├── RequestParamExample.java
├── ServletToJsp.java
├── SessionExample.java
├── sessions
├── trailers
├── util
├── validators
└── websocket
*/
for (WebResource webResource : webResources) {
// Skip the META-INF directory from any JARs that have been
// expanded in to WEB-INF/classes (sometimes IDEs do this).
if ("META-INF".equals(webResource.getName())) {
continue;
}
//解析类文件 webResource = D:\apps\tomcat-source\tomcat\output\build\webapps\examples\WEB-INF\classes\HelloWorldExample2ForServletAnnotation.class
//这个类HelloWorldExample2ForServletAnnotation是我自己加的,实现了HttpServlet接口的doGet方法,添加了注解@WebServlet({"/HelloWorldExample2ForServletAnnotation"})
processAnnotationsWebResource(webResource, webXml, webXml.isMetadataComplete(), javaClassCache);
}
}
}
//最终调用了这个方法,获取类注解:@WebServlet、@WebFilter、@WebListener
protected void processClass(WebXml fragment, JavaClass clazz) {
AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries();
if (annotationsEntries != null) {
String className = clazz.getClassName();
for (AnnotationEntry ae : annotationsEntries) {
String type = ae.getAnnotationType();
if ("Ljakarta/servlet/annotation/WebServlet;".equals(type)) {
//className = "HelloWorldExample2ForServletAnnotation"
processAnnotationWebServlet(className, ae, fragment);
} else if ("Ljakarta/servlet/annotation/WebFilter;".equals(type)) {
processAnnotationWebFilter(className, ae, fragment);
} else if ("Ljakarta/servlet/annotation/WebListener;".equals(type)) {
fragment.addListener(className);
} else {
// Unknown annotation - ignore
}
}
}
}
//添加到webXml.servletMappings
public void addServlet(ServletDef servletDef) {
//servletName = "HelloWorldExample2ForServletAnnotation"
//servletClass = "HelloWorldExample2ForServletAnnotation"
servlets.put(servletDef.getServletName(), servletDef);
if (overridable) {
servletDef.setOverridable(overridable);
}
}
- 配置StandardContext,添加wrapper
把上一步处理好的servletMappings转换成wrapper,并添加到context中
java
for (ServletDef servlet : webxml.getServlets().values()) {
Wrapper wrapper = context.createWrapper();//StandardWrapper
wrapper.setName(servlet.getServletName());
wrapper.setServletClass(servlet.getServletClass());
//context = {StandardContext@3456} "StandardEngine[Catalina].StandardHost[localhost].StandardContext[/examples]"
context.addChild(wrapper);
}
至此,我们看到把servlet封装成wrapper,wrapper添加到context,context是host的子容器,host属于engine,engine在service中,service是顶级容器server的子容器。
4.2.2. 执行Connector的start
这段代码在StandardService中的start方法中,上述【## 4.2. 执行StandardService的start】中出现过,现在开始分析。
java
for (Connector connector : findConnectors()) {
// If it has already failed, don't try and start it
//只有一个元素 Connector["http-nio-8080"]
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
connector的核心就是一个Http11NioProtocol
网络协议类,在启动方法中主要逻辑就是调用Http11NioProtocol
的启动方法。
java
/**
* Start the NIO endpoint, creating acceptor, poller threads.
*/
@Override
public void startInternal() throws Exception {
//设置同时处理请求的最大连接数,默认 8*1024=8192
LimitLatch limitLatch = initializeConnectionLatch();
// Start poller thread
//异步创建nio请求消费者
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
// Start acceptor thread
//异步创建nio请求生产者
acceptor = new Acceptor<>(this);
String threadName = getName() + "-Acceptor";
acceptor.setThreadName(threadName);
Thread t = new Thread(acceptor, threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
4.2.2.1. 启动nioEndpoint线程Poller
- 首先创建了
Poller
对象,构造方法打开了一个选择器
java
public class Poller implements Runnable {//继承了Runnable
public Poller() throws IOException {
//Selector.open() 是 Java NIO 中用于打开一个新的选择器的静态方法。选择器是一个多路复用器,可以检测多个通道的 I/O 事件(如读、写、连接等),从而实现非阻塞 I/O 操作。
this.selector = Selector.open();
}
//省略部分代码
}
这里没有继续看Selector类的源码,这是java包下的,而且里面好多逻辑调用了native方法,看到不源码,我用AI总结了一下:
js
1. **打开选择器**:通过 `Selector.open()` 方法创建一个新的选择器实例。
2. **注册通道**:将一个或多个通道注册到选择器上,并指定感兴趣的 I/O 事件。
3. **选择就绪通道**:使用选择器的 `select()`、`select(long)`、`selectNow()` 方法可以检测哪些通道已经准备好进行 I/O 操作。
4. **处理就绪通道**:通过 `selectedKeys()` 方法获取已准备好进行 I/O 操作的通道的键集合,并对这些键进行处理。
5. **唤醒选择器**:通过 `wakeup()` 方法可以唤醒阻塞在选择操作上的线程。
6. **关闭选择器**:通过 `close()` 方法关闭选择器,释放相关资源。
这里说的通道在tomcat里就是`SocketChannel`,通道可以读写数据,通道与缓冲区(Buffer)结合使用,数据总是从通道读到缓冲区中,或者从缓冲区写到通道中。(就类似于读写File,使用更大的数组读写:提效)
- 异步执行
Poller
线程
看一下Poller的run方法实现:
java
/**
* The background thread that adds sockets to the Poller, checks the
* poller for triggered events and hands the associated socket off to an
* appropriate processor as events occur.
*/
@Override
public void run() {
// Loop until destroy() is called
while (true) {
boolean hasEvents = false;
try {
if (!close) {
//读事件
hasEvents = events();
if (wakeupCounter.getAndSet(-1) > 0) {//wakeupCounter:默认是0,如果有新事件添加到evens队列,就+1,这样就不用阻塞获取channel
// If we are here, means we have other stuff to do
// Do a non blocking select
keyCount = selector.selectNow();//立即获取已准备好的通道数量
} else {
keyCount = selector.select(selectorTimeout);//阻塞1秒,获取已准备好的通道数量
}
wakeupCounter.set(0);
}
} catch (Throwable x) {
ExceptionUtils.handleThrowable(x);
log.error(sm.getString("endpoint.nio.selectorLoopError"), x);
continue;
}
//获取已准备好的通道集合
Iterator<SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;
// Walk through the collection of ready keys and dispatch
// any active event.
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
iterator.remove();
//获取socket通道
NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (socketWrapper != null) {
//处理socket请求
processKey(sk, socketWrapper);
}
}
// Process timeouts
//处理超时
timeout(keyCount,hasEvents);
}
}
4.2.2.1.1. 读事件events()
方法
这个方法的作用就是把selector中的通道设置为读事件或写事件
java
private final SynchronizedQueue<PollerEvent> events =
new SynchronizedQueue<>();//创建events队列
public boolean events() {
boolean result = false;
PollerEvent pe = null;
//遍历events队列事件,这个事件是由Acceptor添加的,下面会讲到
for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++ ) {
result = true;
//获取socket通道
NioSocketWrapper socketWrapper = pe.getSocketWrapper();
SocketChannel sc = socketWrapper.getSocket().getIOChannel();
int interestOps = pe.getInterestOps();//acceptor线程创建的pollerEvent都是注册事件,下面的代码有分析
if (sc == null) {
log.warn(sm.getString("endpoint.nio.nullSocketChannel"));
socketWrapper.close();
} else if (interestOps == OP_REGISTER) {//注册事件改成读事件
try {
sc.register(getSelector(), SelectionKey.OP_READ, socketWrapper);
} catch (Exception x) {
log.error(sm.getString("endpoint.nio.registerFail"), x);
}
} else {
final SelectionKey key = sc.keyFor(getSelector());
if (key == null) {
// The key was cancelled (e.g. due to socket closure)
// and removed from the selector while it was being
// processed. Count down the connections at this point
// since it won't have been counted down when the socket
// closed.
socketWrapper.close();
} else {
final NioSocketWrapper attachment = (NioSocketWrapper) key.attachment();
if (attachment != null) {
// We are registering the key to start with, reset the fairness counter.
try {
int ops = key.interestOps() | interestOps;
//普通的请求接口,设置为读事件或写事件,一般来说都是读
attachment.interestOps(ops);
key.interestOps(ops);
} catch (CancelledKeyException ckx) {
socketWrapper.close();
}
} else {
socketWrapper.close();
}
}
}
if (running && eventCache != null) {
pe.reset();
eventCache.push(pe);
}
}
return result;
}
4.2.2.1.2. 处理socket请求
processKey(sk, socketWrapper);
把客户端的socketChannel请求进行处理。调用http的doGet、doPost等方法。
java
//1. processKey是一个异步方法,实现了Runnable
sc = createSocketProcessor(socketWrapper, event);
executor.execute(sc);//异步
//2. 当前是在Connector中,获取对应的service
if (status == SocketEvent.OPEN_READ) {
state = service(socketWrapper);
}//上面说过,pipeline是管理valve的,当前service的子容器engine的pipeline只有一个阀门:【StandardEngineValve】
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
//3. StandardEngineValve的invoke逻辑
//standardHost的pipeline有三个,上面也说过:
// 0 = {AccessLogValve@4541} //输出日志,然后执行【ErrorReportValve】的invoke 例如getNext().invoke(request, response);
// 1 = {ErrorReportValve@4556} //直接执行【StandardHostValve】的invoke
// 2 = {StandardHostValve@4566} 重点看这个
host.getPipeline().getFirst().invoke(request, response);
//4. StandardHostValve的invoke逻辑
//context的pipeline有三个:
// 0 = {RemoteAddrValve@4681} 校验ip,然后执行【FormAuthenticator】的invoke
// 1 = {FormAuthenticator@4684} 校验tomcat登录信息,然后执行【StandardContextValve】的invoke
// 2 = {StandardContextValve@4708} 重点看这个,这个阀门是实例化Context的时候创建的,上面提到过
context.getPipeline().getFirst().invoke(request, response);
//5. StandardContextValve的invoke逻辑
//我当前的请求路径是http://localhost:8080/examples/HelloWorldExample2ForServletAnnotation
//对应的wrapper是"StandardEngine[Catalina].StandardHost[localhost].StandardContext[/examples].StandardWrapper[HelloWorldExample2ForServletAnnotation]"
//对应的valve只有一个:StandardWrapperValve
request.getWrapper().getPipeline().getFirst().invoke(request, response);
//6. StandardWrapperValve的invoke逻辑
//filterChain默认有两个:
// 0 = org.apache.catalina.filters.HttpHeaderSecurityFilter
// 1 = org.apache.tomcat.websocket.server.WsFilter
filterChain.doFilter(request.getRequest(), response.getResponse());
//7. filterChain的doFilter
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
Filter filter = filterConfig.getFilter();//遍历每个过滤器,执行
filter.doFilter(request, response, this);
} catch (IOException | ServletException | RuntimeException e) {
throw e;
}
return;
}
//最后执行业务类
servlet.service(request, response);
}
//8. 执行业务类的doGet
if (method.equals(METHOD_GET)) {
doGet(req, resp);
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
System.out.print("请求参数:"+request);
response.write("你好世界");
}
4.2.2.2. 启动nioEndpoint线程Acceptor
- 首先是创建了Acceptor对象,继承了Runnable,然后异步线程执行run方法,先看下run方法:
java
public void run() {
try {
// Loop until we receive a shutdown command
//循环执行,直到关闭tomcat服务
while (!stopCalled) {
state = AcceptorState.RUNNING;
try {
//if we have reached max connections, wait
//检查请求连接总数,到达最大连接数8192,上面代码分析过【connectionLimitLatch】
endpoint.countUpOrAwaitConnection();
U socket = null;
try {
// Accept the next incoming connection from the server
// socket
// 阻塞监听8080端口新连接
socket = endpoint.serverSocketAccept();
} catch (Exception ioe) {
// We didn't get a socket
endpoint.countDownConnection();
}
// Configure the socket
if (!stopCalled && !endpoint.isPaused()) {
// setSocketOptions() will hand the socket off to
// an appropriate processor if successful
//新连接socket注册
if (!endpoint.setSocketOptions(socket)) {
endpoint.closeSocket(socket);
}
} else {
endpoint.destroySocket(socket);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
String msg = sm.getString("endpoint.accept.fail");
log.error(msg, t);
}
}
} finally {
stopLatch.countDown();
}
state = AcceptorState.ENDED;
}
4.2.2.2.1. 检查请求连接总数是否达到最大连接数
endpoint.countUpOrAwaitConnection();
如果达到默认的连接数8192就抛异常
java
long newCount = count.incrementAndGet(); //每次循环都会加1,值得注意的是,当处理完成当前socket或者程序报错,都会把count减1
if (newCount > limit) //limit=8192,newCount=当前连接数
throw new InterruptedException();
4.2.2.2.2. 阻塞监听8080端口新连接
socket = endpoint.serverSocketAccept();
这个是java内部方法
java
int n = Net.accept(fd, newfd, issa);//是个native方法,阻塞接收新连接,
//最终返回新连接的SocketChannel
return new SocketChannelImpl(provider(), family, newfd, sa);
4.2.2.2.3. 新连接socket注册
endpoint.setSocketOptions(socket)
,处理新连接
java
//socket = {SocketChannelImpl@4755} "java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:8080 remote=/[0:0:0:0:0:0:0:1]:50015]"
@Override
protected boolean setSocketOptions(SocketChannel socket) {
NioSocketWrapper socketWrapper = null;
try {
// Allocate channel and wrapper
NioChannel channel = null;
SocketBufferHandler bufhandler = new SocketBufferHandler(
socketProperties.getAppReadBufSize(),
socketProperties.getAppWriteBufSize(),
socketProperties.getDirectBuffer());
//新创建一个channel
channel = createChannel(bufhandler);
//组装Wrapper
NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);
//设置新连接socket
channel.reset(socket, newWrapper);
connections.put(socket, newWrapper);
socketWrapper = newWrapper;
// Set socket properties
// Disable blocking, polling will be used
socket.configureBlocking(false);
if (getUnixDomainSocketPath() == null) {
socketProperties.setProperties(socket.socket());
}
socketWrapper.setReadTimeout(getConnectionTimeout());
socketWrapper.setWriteTimeout(getConnectionTimeout());
socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
//最重要的一步,注册socketWrapper到events事件队列
poller.register(socketWrapper);
return true;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
try {
log.error(sm.getString("endpoint.socketOptionsError"), t);
} catch (Throwable tt) {
ExceptionUtils.handleThrowable(tt);
}
if (socketWrapper == null) {
destroySocket(socket);
}
}
// Tell to close the socket if needed
return false;
}
4.2.2.2.3.1. 新连接socketWrapper注册到events事件队列
poller.register(socketWrapper);
注册到事件队列,给Poller线程消费
java
public void register(final NioSocketWrapper socketWrapper) {
//设置读事件给socketWrapper
socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
//新增Poller事件,并设置注册事件类型
PollerEvent pollerEvent = createPollerEvent(socketWrapper, OP_REGISTER);
//添加poller事件到events事件队列
addEvent(pollerEvent);
}
//addEvent(pollerEvent);方法内容
events.offer(event); //events就是上面poller线程的事件队列SynchronizedQueue
if (wakeupCounter.incrementAndGet() == 0) {
selector.wakeup();//唤醒poller线程
}