tomcat12启动流程源码分析

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 方法完成后,主要做了以下几件事情:

  1. 创建了类加载器 catalinaLoader,用于加载 Tomcat 的核心类。
  2. 设置当前线程的上下文类加载器为 catalinaLoader
  3. 通过反射机制加载 org.apache.catalina.startup.Catalina 类,并创建其实例。
  4. 设置 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 &quot;%r&quot; %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 中用于管理组件生命周期的基类。它定义了一些通用的生命周期方法,如 initstartstopdestroy,并提供了状态管理和事件通知的机制。通过继承 LifecycleBaseStandardServer 可以利用这些通用的生命周期管理功能,确保在初始化、启动、停止和销毁过程中执行必要的操作和触发相应的事件。

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 包含以下监听器:

  1. NamingContextListener:用于管理 JNDI(Java Naming and Directory Interface)资源的生命周期。它在 Tomcat 启动时初始化 JNDI 资源,并在停止时清理这些资源。
  2. VersionLoggerListener:用于在 Tomcat 启动时记录版本信息,包括操作系统、JVM 和 Tomcat 的版本。
  3. AprLifecycleListener:用于初始化和终止 APR(Apache Portable Runtime)库。它还负责初始化 SSL(如果配置了)并处理 FIPS 模式(如果启用)。
  4. JreMemoryLeakPreventionListener:用于防止 JRE 内存泄漏。它通过触发一些特定的 JVM 操作来防止常见的内存泄漏问题。
  5. GlobalResourcesLifecycleListener:用于管理全局 JNDI 资源。它在 Tomcat 启动时初始化这些资源,并在停止时清理它们。
  6. 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

  1. getServer().start(); 执行StandardServer的start方法。
    StandardServer 类继承了 LifecycleBase 抽象类,因此也继承了 start 方法。LifecycleBase 提供了通用的生命周期管理功能,包括 initstartstopdestroy 方法。StandardServerstart 方法用于启动 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());
    }
}
  1. 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

  1. 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());
    }
}
  1. 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

  1. 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());
    }
}
  1. 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
}
  1. 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
  1. 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类型。

  1. 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());
    }
}
  1. 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));
            }
        }
    }
}
  1. 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
    • 详细描述StandardHostValveHost 容器的默认 Valve,负责将传入的请求分派到适当的 Context(即 Web 应用)。它根据请求的 URI 确定目标 Context,并将请求传递给该 Context 进行处理。
  1. 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文件夹部署过程吧,其他的原理一样。

  1. 部署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);
        }
    }
}
  1. 异步执行部署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());
        }
    }
}
  1. 执行核心部署逻辑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启动方法
  1. 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());
    }
}
  1. StandardContext初始化init()
    这一步没有重要的逻辑。
js 复制代码
StandardContext有3个监听器:
- **ContextConfig**:
   - **作用**:`ContextConfig` 监听器负责处理 `Context` 的配置和初始化工作。它会解析 `web.xml` 文件,设置 `Context` 的各种参数,并执行必要的初始化操作。

- **StandardHost$MemoryLeakTrackingListener**:
   - **作用**:跟踪和检测内存泄漏。

- **ThreadLocalLeakPreventionListener**:
   - **作用**:防止线程本地变量泄漏。
  1. 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">

  1. 解析/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);
    }
}
  1. 配置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
  1. 首先创建了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,使用更大的数组读写:提效)
  1. 异步执行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
  1. 首先是创建了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线程
}
相关推荐
Listen·Rain20 小时前
用AI开发出一个AI
java·人工智能·spring boot·tomcat·intellij-idea·mybatis·visual studio
Full Stack Developme2 天前
SpringBoot 内嵌 Tomcat 的启动流程
spring boot·后端·tomcat
Full Stack Developme2 天前
Tomcat 如何处理HTTP请求
java·http·tomcat
Full Stack Developme3 天前
Tomcat 设计原理
java·tomcat
Full Stack Developme3 天前
Tomcat NIO连接器内部如何实现零拷贝 Session管理如何实现集群同步
java·tomcat·nio
BullSmall4 天前
Tomcat 部署 Jenkins → Docker Jenkins 完整迁移方案
docker·tomcat·jenkins
baozhengw4 天前
信创改造-SpringBootJar包改为WAR包部署Tomcat
java·tomcat
Java面试题总结10 天前
mybatis插件
java·tomcat·mybatis
左左右右左右摇晃10 天前
手写Tomcat原理整理
java·开发语言·笔记·tomcat
智码看视界13 天前
Tomcat架构深度拆解:Connector和Container到底怎么配合的?
java·servlet·架构·tomcat·web服务器