当前位置: 首页 > article >正文

tomcat12启动流程源码分析

信息: 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方法

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方法

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);

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. 指定父加载器

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);

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类的方法

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变量

没啥好说的,就是设置变量

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 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. 解析源代码

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包下的)

/**
 * 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);

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包下的类,逻辑很长复杂,我省略了部分代码
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()方法
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);

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”

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逻辑是一样的。

StandardServer
StandardService
Connector
StandardEngine
StandardHost
3.2.2.2.2.1. 值得注意的是Connector类的构造方法中创建了Http11NioProtocol实例
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实例
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实例
public StandardHost() {
    super();
    pipeline.setBasic(new StandardHostValve());
}

3.3. 设置系统打印输出

// 重定向系统输出流
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容器

getServer().init();//初始化server容器

StandardServer 继承了 LifecycleBase 抽象类,因此也继承了 init 方法。LifecycleBase 是 Tomcat 中用于管理组件生命周期的基类。它定义了一些通用的生命周期方法,如 initstartstopdestroy,并提供了状态管理和事件通知的机制。通过继承 LifecycleBaseStandardServer 可以利用这些通用的生命周期管理功能,确保在初始化、启动、停止和销毁过程中执行必要的操作和触发相应的事件。

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类型,并且使用观察者模式,执行初始化前事件。

//设置状态
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();

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 方法。

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类型,并且使用观察者模式,执行初始化前事件。

//设置状态
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初始化了,直接看下面代码

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 方法。

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();

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 方法。

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 方法。

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"初始化代码

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

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初始化

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端口。

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启动流程的最后一步

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 服务器,具体实现如下:
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();
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 方法。

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 方法。
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();
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 方法。
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();
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();
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);
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 方法。
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
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 过滤器。
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();
//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,下面是主要逻辑

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启动方法监听触发。

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文件夹准备
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
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);
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文件内容

<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文件源码

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方法核心代码是

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方法。
@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()
    这一步没有重要的逻辑。
StandardContext有3个监听器:
- **ContextConfig**:
   - **作用**`ContextConfig` 监听器负责处理 `Context` 的配置和初始化工作。它会解析 `web.xml` 文件,设置 `Context` 的各种参数,并执行必要的初始化操作。

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

- **ThreadLocalLeakPreventionListener**:
   - **作用**:防止线程本地变量泄漏。
  1. StandardContext启动方法startInternal()
    这个启动方法是tomcat中最复杂的,代码很长,先看下作用的功能:
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 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>

下面开始分析代码

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开始解析类文件了
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中
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】中出现过,现在开始分析。

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的启动方法。

/**
 * 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对象,构造方法打开了一个选择器
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总结了一下:

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方法实现:
/**
 * 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中的通道设置为读事件或写事件

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等方法。

//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方法:
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就抛异常

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内部方法

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),处理新连接

//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线程消费

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线程
}

http://www.kler.cn/a/473080.html

相关文章:

  • 推动多语言语音科技迈向新高度:INTERSPEECH 2025 ML-SUPERB 2.0 挑战赛
  • Profinet转EtherNet/IP网关连接AB PLC的应用案例
  • MongoTemplate 性能优化指南
  • 洛谷P1617————数字转英文
  • STM32-笔记34-4G遥控灯
  • 域名注册网国际域名与国内域名的区别
  • adb使用及常用命令
  • JavaEE之定时器及自我实现
  • 闲谭SpringBoot--ShardingSphere分库分表探究
  • spring mvc源码学习笔记之一
  • Java高频面试之SE-10
  • 单片机-外部中断
  • springboot 集成 etcd
  • YCM上告警项怎么修改
  • 【转载记录】Scikit-learn 报错:’super’ object has no attribute ‘__sklearn_tags__’.
  • Perl语言的语法糖
  • 【HTML+CSS+JS+VUE】web前端教程-2-HTML5介绍和基础骨架
  • 【2024华为OD-E卷-100分-boss的收入】(题目+思路+JavaC++Python解析)
  • 【并发篇】CompletableFuture学习
  • JavaScript 正则表达式
  • 代码随想录算法训练营第1天(数组1)| 704. 二分查找、27. 移除元素、977.有序数组的平方
  • 【数据库】SQL相关知识点总结1(数据库约束、三大范式、关系模型、聚合函数)
  • 为什么页面无法正确显示?都有哪些HTML和CSS相关问题?
  • PHP语言的函数实现
  • svelte5中使用react组件
  • 跨界融合:人工智能与区块链如何重新定义数据安全?