美文网首页
Tomcat源码解析-组件之StandardServer

Tomcat源码解析-组件之StandardServer

作者: 贪睡的企鹅 | 来源:发表于2019-08-09 12:33 被阅读0次

    1 StandardServer职责

    StandardServer是tomcat容器的最高层的组件,职责如下:
    实现Tomcat一键启动关闭,管理全局 JDNI资源,管理子组件,阻塞tomcat主线程。

    StandardServer子组件

    image

    2 运行流程

    2.1 Bootstarp运行流程

    Bootstarp作为tomcat启动类,JVM会调用main函数完成tomcat启动。在其内部流程如下:

    • 1 实例化Bootstrap对象。

    • 2 对Bootstrap对象init,其内部包括构建Tomcat类加载器,并实例化Catalina对象

    • 3 以main函数args参数作为指令,通过调用Catalina类中不同的方法,完成相应的功能。

      • startd:首先调用load方法,接着调用start方法,
      • start:首先调用setAwait方法,接着调用load方法,最后调用start方法
      • stopd:调stopServer方法
      • configtest:调load方法
    image

    核心方法功能

    • bootstrap.init():负责初始化Bootstrap,在初始化的过程最重要的就是构建Tomcat类加载器,并创建Catalina。
    • bootstrap.load(args):负责通过反射调用Catalina.load方法,实现tomcat加载。
    • bootstrap.start():负责通过反射调用Catalina.start方法,实现tomcat启动。
    • bootstrap.stop():负责通过反射调用Catalina.start方法,实现tomcat停止。
    • bootstrap.setAwait(true):负责通过反射调用Catalina.setAwait方法,设置tomcat是否需要启动一个Socket等待接受shutdown命令,用来停止Tomcat.
    2.2 Catalina运行流程
    image
    • catalina.init():负责使用Digester解析Server.xml,并实例化Server.xml中描述的所有组件,之后调用调用Server组件初始化方法init().

    • catalina.start():负责调用Server组件start启动方法,之后给当前JVM进程注册一个钩子线程,该线程负责用来当tomcat被关闭时完成一些清理工作。最后调用Server组件await()方法阻塞当前线程(tomcat主线程),如果当前线程从await()方法返回表示tomcat被停止。

    • catalina.stop():负责向JVM清理掉注册的钩子线程,顺序调用Server组件停止方法stop()以及清理方法destroy()。

    • catalina.stopServer():负责顺序调用Server组件停止方法stop()以及清理方法destroy(),并向Tomcat指定的端口发送shutdown指令,停止tomcat主线程。

    3 解析server.xml

    Tomcat使用Digester解析server.xml,Digester是一款用于将xml转换为Java对象的事件驱动型工具,是对SAX的高层次的封装。相对于SAX可以为xml中每一个标签设置对应的解析规则。详见 Tomcat相关技术-Digester(二)

    这里通过解析server.xml实例化StandardServer,并设置server.xml文件中定义的属性初始化。

    server.xml配置

    <Server port="8005" shutdown="SHUTDOWN">
      <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
      <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
      <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>
    
      
      <Service name="Catalina">
      </Service>
      ....
    </Server>
    
    3.1 解析<Server>标签

    <Server>标签用来表示当前StandardServer组件

    //解析<Server>标签
    /** 解析<server>标签实例化StandardServer对象,并push到操作栈中 **/
    digester.addObjectCreate("Server",
                             "org.apache.catalina.core.StandardServer",
                             "className");
                             
    /** 解析<server>标签将标签中属性值映射到StandardServer对象中**/  
    digester.addSetProperties("Server");
    
    /** 解析</server>标签将操作栈栈顶对象设置到次栈顶对象属性中**/
    //将StandardServer对象设置到Catalina启动类对象的server属性中
    digester.addSetNext("Server",
                        "setServer",
                        "org.apache.catalina.Server");
    

    StandardServer构造函数

    public StandardServer() {
        super();
        globalNamingResources = new NamingResourcesImpl();
        globalNamingResources.setContainer(this);
    
        /** 判断是否开启JNDI服务 **/
        if (isUseNaming()) {
            namingContextListener = new NamingContextListener();
            addLifecycleListener(namingContextListener);
        } else {
            namingContextListener = null;
        }
    }
    

    将<server>标签属性映射到StandardServer对象属性中

    /**
     * Tomcat shutdown操作,对应字符串指令
     */
    private String shutdown = "SHUTDOWN";
    
    /**
     * Tomcat ShutDown操作,服务端监听Socket端口号。
     */
    private int port = 8005;
    
    /**
     * Tomcat ShutDown执行,服务端监听Socket地址。
     */
    private String address = "localhost";
    
    image
    3.2 解析<GlobalNamingResources>标签

    <GlobalNamingResources>标签中定义了全局JNDI资源,

    //解析<Server>GlobalNamingResources>标签
    /** 解析<GlobalNamingResources>标签实例化NamingResourcesImpl对象,并push到操作栈中 **/
    digester.addObjectCreate("Server/GlobalNamingResources",
                     "org.apache.catalina.deploy.NamingResourcesImpl");
    
    /** 解析<GlobalNamingResources>标签将标签中属性值映射到NamingResourcesImpl对象中**/                  
    digester.addSetProperties("Server/GlobalNamingResources");
    
    
    /** 解析</GlobalNamingResources>标签将操作栈栈顶对象作为次栈顶对象StandardServer.setGlobalNamingResources方法调用的参数,设置到StandardServer属性中**/
    digester.addSetNext("Server/GlobalNamingResources",
                "setGlobalNamingResources",
                "org.apache.catalina.deploy.NamingResourcesImpl");
                
    
              
    
    3.2.1 设置globalNamingResources属性
    /**
     * 设置globalNamingResources属性
     */
    @Override
    public void setGlobalNamingResources
        (NamingResourcesImpl globalNamingResources) {
    
        /** 获取设置前globalNamingResources **/
        NamingResourcesImpl oldGlobalNamingResources =
            this.globalNamingResources;
    
        /** 设置globalNamingResources **/
        this.globalNamingResources = globalNamingResources;
        this.globalNamingResources.setContainer(this);
    
        /** 触发属性变更通知 **/
        support.firePropertyChange("globalNamingResources",
                                   oldGlobalNamingResources,
                                   this.globalNamingResources);
    }
    
    3.3 解析<Listener>标签

    <Listener>标签中定义StandardServer组件中需要的LifecycleListener监听器。<Server>标签内可以设置多个<Listener>。

    //解析<Server><Listener>标签
    /** 解析<Listener>标签实例化标签中className属性定义的对象,并push到操作栈中 **/
    digester.addObjectCreate("Server/Listener",
                             null, // MUST be specified in the element
                             "className");
    /** 解析<Listener>标签将标签中属性值映射到其实例化对象中**/
    digester.addSetProperties("Server/Listener");
    
    /** 解析</Listener>标签将操作栈栈顶对象作为次栈顶对象StandardServer.addLifecycleListener方法调用的参数,设置到StandardServer属性中**/
    digester.addSetNext("Server/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");
    

    Server中定义的Listener

     <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
     <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
     <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
     <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
     <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
    

    添加LifecycleListener监听器

    /**
     * 给当前组件添加一个生命周期监听器
     */
    @Override
    public void addLifecycleListener(LifecycleListener listener) {
        lifecycleListeners.add(listener);
    }
    
    3.4 解析<Service>属性

    <Service>标签中定义StandardServer组件中子组件Service。<Server>标签内可以设置多个<Service>。

    //解析<Server><Service>标签
    /** 解析<Service>标签实例化StandardService对象,并push到操作栈中 **/
    digester.addObjectCreate("Server/Service",
                             "org.apache.catalina.core.StandardService",
                             "className");
    /** 解析<Service>标签将标签中属性值映射到StandardService对象中**/
    digester.addSetProperties("Server/Service");
    /** 解析</Service>标签将操作栈栈顶对象作为次栈顶对象StandardServer.addService方法调用的参数,设置到StandardServer属性中**/
    digester.addSetNext("Server/Service",
                        "addService",
                        "org.apache.catalina.Service");
    

    添加Service子组件

    /**
     * 将service子组件加到Tomcat Server组件中
     */
    @Override
    public void addService(Service service) {
    
        /** service 反向关联外部 Server组件 **/
        service.setServer(this);
    
        synchronized (servicesLock) {
            /** 将service组件添加到Server.ervices数组类型属性中 **/
            Service results[] = new Service[services.length + 1];
            System.arraycopy(services, 0, results, 0, services.length);
            results[services.length] = service;
            services = results;
    
            /** 如果当前Server组件已经启动,则启动添加Service组件 **/
            if (getState().isAvailable()) {
                try {
                    service.start();
                } catch (LifecycleException e) {
                    // Ignore
                }
            }
    
            /** 将service属性更改通知给监听器  **/
            support.firePropertyChange("service", null, service);
        }
    }
    

    4 实现一键启动

    4.1 组件生命周期

    StandardServer作为tomcat最上层的组件,和其他所有组件一样都实现了Lifecycle 接口。

    public interface Lifecycle {
        ....
        // 初始化方法
        public void init() throws LifecycleException;
        // 启动方法
        public void start() throws LifecycleException;
        // 停止方法,和start对应
        public void stop() throws LifecycleException;
        // 销毁方法,和init对应
        public void destroy() throws LifecycleException;
        // 获取生命周期状态
        public LifecycleState getState();
        // 获取字符串类型的生命周期状态
        public String getStateName();
    }
    

    Tomcat 定义一个基类LifecycleBase 来实现 Lifecycle 接口,把一些公共的逻辑放到基类中去,比如生命状态的转变与维护、生命事件的触发以及监听器的添加和删除等,而子类就负责实现自己的初始化、启动和停止等模板方法。为了避免跟基类中的方法同名,我们把具体子类的实现方法改个名字,在后面加上 Internal,叫 initInternal、startInternal 等。

    /**
     *  组件初始化动作,所有组件通用操作
     *   1 检查校验当前组件状态是否能够初始化
     *   2 修改当前的状态从 NEW-->INITIALIZING
     *   3 调用每个组件模板方法实现完成初始化动作
     *   4 修改当前的状态从 INITIALIZING-->INITIALIZED
     */
    @Override
    public final synchronized void init() throws LifecycleException {
        /** 非NEW状态,不允许调用init()方法 **/
        if (!state.equals(LifecycleState.NEW)) {
            /** 从sm获取"lifecycleBase.invalidTransition"属性对应日志格式,抛出LifecycleException异常 **/
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        }
    
        try {
            /** 初始化逻辑之前,将状态变更为`INITIALIZING` **/
            setStateInternal(LifecycleState.INITIALIZING, null, false);
            /** 初始化组件,该方法为一个abstract模板方法,需要组件自行实现  **/
            initInternal();
            /** 初始化完成之后,状态变更为`INITIALIZED`  **/
            setStateInternal(LifecycleState.INITIALIZED, null, false);
        }
        /** 初始化的过程中,可能会有异常抛出,这时需要捕获异常,并将状态变更为`FAILED` **/
        catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            setStateInternal(LifecycleState.FAILED, null, false);
            throw new LifecycleException(
                    sm.getString("lifecycleBase.initFail",toString()), t);
        }
    }
    
    /**
     * 初始化模板方法
     */
    protected abstract void initInternal() throws LifecycleException;
    
    

    其他模板方法

    /**
     * 启动模板方法
     */
    protected abstract void startInternal() throws LifecycleException;
    
    /**
     * 停止模板方法
     */
    protected abstract void stopInternal() throws LifecycleException;
        
    /**
     * 销毁模板方法
     */
    protected abstract void destroyInternal() throws LifecycleException;
    

    详见Tomcat架构设计-组件生命周期

    4.2 初始化StandardServer组件

    StandardServer组件初始化的核心是调用所有Service子组件初始化方法init。

    详细流程如下:

    • 1 将StringCache类型对象注册到JMX bean中

    • 2 将MBeanFactory类型对象注册到JMX bean中

    • 3 JNDI服务初始化

    • 4 读取Shared类加载器 管理的jar文件,将包含MANIFEST的JAR文件,添加到容器的清单资源中

    • 5 调用所有Service子组件初始化方法init

    @Override
    protected void initInternal() throws LifecycleException {
    
        super.initInternal();
    
        //1 将StringCache类型对象注册到JMX bean中
        onameStringCache = register(new StringCache(), "type=StringCache");
    
        //2 将MBeanFactory类型对象注册到JMX bean中
        MBeanFactory factory = new MBeanFactory();
        factory.setContainer(this);
        onameMBeanFactory = register(factory, "type=MBeanFactory");
    
        /** 3 JNDI服务初始化 **/
        globalNamingResources.init();
    
    
        /** 4 读取Shared类加载器 管理的jar文件,将包含MANIFEST的JAR文件,添加到容器的清单资源中 **/
        if (getCatalina() != null) {
            /** 获取Shared类加载器 **/
            ClassLoader cl = getCatalina().getParentClassLoader();
            while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
                if (cl instanceof URLClassLoader) {
                    URL[] urls = ((URLClassLoader) cl).getURLs();
                    for (URL url : urls) {
                        if (url.getProtocol().equals("file")) {
                            try {
                                File f = new File (url.toURI());
                                if (f.isFile() &&
                                        f.getName().endsWith(".jar")) {
                                        ExtensionValidator.addSystemResource(f);
                                }
                            } catch (URISyntaxException e) {
                                // Ignore
                            } catch (IOException e) {
                                // Ignore
                            }
                        }
                    }
                }
                cl = cl.getParent();
            }
        }
        /** 5 调用所有Service子组件初始化方法init **/
        for (int i = 0; i < services.length; i++) {
            services[i].init();
        }
    }
    
    4.3 启动StandardServer组件

    启动start的核心是调用所有Service子组件初始化方法start。

    详细流程如下:

    • 1 通知LifecycleListener监听器当前组件触发 CONFIGURE_START_EVENT事件

    • 2 更正当前组件状态为STARTING

    • 3 启动JNDI服务

    • 4 调用所有Service子组件启动方法start

    /**
     * 组件启动模板方法实现
     */
    @Override
    protected void startInternal() throws LifecycleException {
    
        /** 通知监听器当前组件触发 CONFIGURE_START_EVENT事件 **/
        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        /** 更正当前组件状态为STARTING  **/
        setState(LifecycleState.STARTING);
        /** 启动JNDI服务 **/
        globalNamingResources.start();
    
        /** 启动所有service组件 **/
        synchronized (servicesLock) {
            for (int i = 0; i < services.length; i++) {
                services[i].start();
            }
        }
    }
    
    4.4 停止StandardServer组件

    启动stop的核心是调用所有Service子组件初始化方法stop。

    详细流程如下:

    • 1 通知LifecycleListener监听器当前组件触发 CONFIGURE_STOP_EVENT事件

    • 2 更正当前组件状态为STOPPING

    • 3 关闭JNDI服务

    • 4 调用所有Service子组件启动方法stop

    • 5 调用 stopAwait

      • 1 设置stopAwait标识为true,stopAwait用来判断tomcat主线程是否要退出
      • 2 关闭Socket服务,不在监听shutdown命令
    /**
     * 组件停止模板方法实现
     */
    @Override
    protected void stopInternal() throws LifecycleException {
    
    
        /** 通知监听器当前组件触发 CONFIGURE_STOP_EVENT事件 **/
        fireLifecycleEvent(CONFIGURE_STOP_EVENT, null);
    
        /** 更正当前组件状态为STOPPING  **/
        setState(LifecycleState.STOPPING);
    
        /** 关闭所有service组件 **/
        for (int i = 0; i < services.length; i++) {
            services[i].stop();
        }
    
        /** 关闭JNDI服务 **/
        globalNamingResources.stop();
    
        /** 停止监听 shutdown命令 Socket服务 **/
        stopAwait();
    }
    
    4.5 销毁StandardServer组件

    销毁destroy的核心是调用所有Service子组件初始化方法destroy。

    1 调用所有Service子组件启动方法destroy

    2 销毁JND全局资源

    3 jmx bean注销MBeanFactory

    4 jmx bean注销StringCache

    5 调用LifecycleMBeanBase.destroyInternal 将当前组件对象从jmx 注销

    /**
     * 组件销毁模板方法实现
     */
    @Override
    protected void destroyInternal() throws LifecycleException {
        /** 调用所有Service子组件启动方法destroy **/
        for (int i = 0; i < services.length; i++) {
            services[i].destroy();
        }
        /** 销毁JND全局资源 **/
        globalNamingResources.destroy();
    
        /** jmx bean注销MBeanFactory **/
        unregister(onameMBeanFactory);
    
        /** jmx bean注销StringCache **/
        unregister(onameStringCache);
    
        /** 调用LifecycleMBeanBase.destroyInternal
         * 将当前组件对象从jmx 注销
         */
        super.destroyInternal();
    }
    
    4.6 StandardServer实现一键启动

    在这样的设计中,在父组件的 init 方法里需要创建子组件并调用子组件的 init 方法。同样,在父组件的 start 方法里也需要调用子组件的 start 方法。只要调用最顶层组件StandardServer的 init 和 start 方法,整个 Tomcat 就被启动起来了。只要调用最顶层组件StandardServer的 destroy 和 stop 方法,整个 Tomcat 就被关闭。

    image

    5 实现子组件Service管理

    5.1 addService

    添加已在解析server.xml初始化设置调用

    /**
         * 将service子组件加到Tomcat Server组件中
         */
        @Override
        public void addService(Service service) {
    
            /** service 反向关联外部 Server组件 **/
            service.setServer(this);
    
            synchronized (servicesLock) {
                /** 将service组件添加到Server.ervices数组类型属性中 **/
                Service results[] = new Service[services.length + 1];
                System.arraycopy(services, 0, results, 0, services.length);
                results[services.length] = service;
                services = results;
    
                /** 如果当前Server组件已经启动,则启动添加Service组件 **/
                if (getState().isAvailable()) {
                    try {
                        service.start();
                    } catch (LifecycleException e) {
                        // Ignore
                    }
                }
    
                /** 将service属性更改通知给监听器  **/
                support.firePropertyChange("service", null, service);
            }
    
        }
    
    5.2 返回指定名称Service组件
    @Override
    public Service findService(String name) {
            if (name == null) {
                return (null);
            }
            synchronized (servicesLock) {
                for (int i = 0; i < services.length; i++) {
                    if (name.equals(services[i].getName())) {
                        return (services[i]);
                    }
                }
            }
            return (null);
    }
    
    5.3 返回Server组件所有Service子组件
        /**
         * 返回Server组件所有Service子组件
         */
        @Override
        public Service[] findServices() {
    
            return services;
    
        }
    
    5.4 从Server组件中删除Service子组件
    /**
     * 从Server组件中删除Service子组件
     */
    @Override
    public void removeService(Service service) {
    
        synchronized (servicesLock) {
            /** 从Service子组件数组找到删除 service 子组件 **/
            int j = -1;
            for (int i = 0; i < services.length; i++) {
                if (service == services[i]) {
                    j = i;
                    break;
                }
            }
            /** 没有找到忽略此动作 **/
            if (j < 0)
                return;
    
            /** 对删除service子组件 停止动作 **/
            try {
                services[j].stop();
            } catch (LifecycleException e) {
                // Ignore
            }
    
            /** 对service 数组中在删除service子组件后service子组件在数组中前移 **/
            int k = 0;
            Service results[] = new Service[services.length - 1];
            for (int i = 0; i < services.length; i++) {
                if (i != j)
                    results[k++] = services[i];
            }
            services = results;
    
            /** support通知service属性变更 **/
            support.firePropertyChange("service", service, null);
        }
    }
    

    6 阻塞tomcat主线程

    image

    阻塞tomcat主线程,只要stopAwait不为true, tomcat主线程在此无限循环.监听到客户端发起SHUTDOWN命令后退出

    @Override
        public void await() {
            if( port == -2 ) {
                return;
            }
            if( port==-1 ) {
                try {
                    awaitThread = Thread.currentThread();
                    while(!stopAwait) {
                        try {
                            Thread.sleep( 10000 );
                        } catch( InterruptedException ex ) {
                            // continue and check the flag
                        }
                    }
                } finally {
                    awaitThread = null;
                }
                return;
            }
    
            /**  创建服务端监听shutdown命令 Socket **/
            try {
                awaitSocket = new ServerSocket(port, 1,
                        InetAddress.getByName(address));
            } catch (IOException e) {
                log.error("StandardServer.await: create[" + address
                                   + ":" + port
                                   + "]: ", e);
                return;
            }
    
            try {
                /** 获取当前线程 **/
                awaitThread = Thread.currentThread();
    
                /** 只要stopAwait不为true, tomcat主线程在此无限循环.监听到客户端发起SHUTDOWN命令后退出 **/
                while (!stopAwait) {
                    ServerSocket serverSocket = awaitSocket;
                    if (serverSocket == null) {
                        break;
                    }
    
                    // Wait for the next connection
                    Socket socket = null;
                    StringBuilder command = new StringBuilder();
                    try {
                        /** 监听阻塞当前线程 **/
                        InputStream stream;
                        long acceptStartTime = System.currentTimeMillis();
                        try {
                            socket = serverSocket.accept();
                            socket.setSoTimeout(10 * 1000);  // Ten seconds
                            stream = socket.getInputStream();
                        } catch (SocketTimeoutException ste) {
                            log.warn(sm.getString("standardServer.accept.timeout",
                                    Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
                            continue;
                        } catch (AccessControlException ace) {
                            log.warn("StandardServer.accept security exception: "
                                    + ace.getMessage(), ace);
                            continue;
                        } catch (IOException e) {
                            if (stopAwait) {
                                break;
                            }
                            log.error("StandardServer.await: accept: ", e);
                            break;
                        }
    
                        /** 发生指令的字符数大于1024,则最大读取字符扩容到
                         * expected += (random.nextInt() % 1024)
                         */
                        int expected = 1024; // Cut off to avoid DoS attack
                        while (expected < shutdown.length()) {
                            if (random == null)
                                random = new Random();
                            expected += (random.nextInt() % 1024);
                        }
    
                        /** 读取指令字符串 **/
                        while (expected > 0) {
                            int ch = -1;
                            try {
                                ch = stream.read();
                            } catch (IOException e) {
                                log.warn("StandardServer.await: read: ", e);
                                ch = -1;
                            }
                            /** 遍历到控制字符或EOF(-1)终止读取 **/
                            if (ch < 32 || ch == 127) {
                                break;
                            }
                            command.append((char) ch);
                            expected--;
                        }
                    } finally {
                        // Close the socket now that we are done with it
                        try {
                            if (socket != null) {
                                socket.close();
                            }
                        } catch (IOException e) {
                            // Ignore
                        }
                    }
    
                    /**  发生执行是否为 shutdown指令字符串相同,相同则跳出循环Tomcat主线程退出**/
                    boolean match = command.toString().equals(shutdown);
                    if (match) {
                        log.info(sm.getString("standardServer.shutdownViaPort"));
                        break;
                    } else
                        log.warn("StandardServer.await: Invalid command '"
                                + command.toString() + "' received");
                }
            } finally {
                ServerSocket serverSocket = awaitSocket;
                awaitThread = null;
                awaitSocket = null;
    
                // Close the server socket and return
                if (serverSocket != null) {
                    try {
                        serverSocket.close();
                    } catch (IOException e) {
                        // Ignore
                    }
                }
            }
        }
    

    相关文章

      网友评论

          本文标题:Tomcat源码解析-组件之StandardServer

          本文链接:https://www.haomeiwen.com/subject/jrffjctx.html