zoukankan      html  css  js  c++  java
  • Tomcat8源码解析

    Tomcat是一款大家非常熟悉的web服务器,具体的功能和怎么使用我就不赘述咯,今天我们主要来分析Tomcat的源码,基于的版本是8.0.11。问题是从哪边开始着手呢?我是这样想的,肯定是先把Tomcat8.0.11的源码在官网中下载下来,然后导入到工程中。

    1 Tomcat源码下载及导入工程

    1.1 源码下载

    Tomcat版本:Tomcat8.0.11

    下载地址:点击下载

    然后选择对应的平台即可

    1.2 pom文件准备及导入工程

    下载好了之后解压,我是基于windows平台的,我们可以基于maven的方式进行构建,所以需要引入pom.xml文件相关的依赖,下面是我给大家准备的pom.xml文件,大家可以直接使用,放到源码的根目录下。

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
        <modelVersion>4.0.0</modelVersion>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>Tomcat8.0</artifactId>
        <name>Tomcat8.0</name>
        <version>8.0</version>
    
        <build>
            <finalName>Tomcat8.0</finalName>
            <sourceDirectory>java</sourceDirectory>
            <testSourceDirectory>test</testSourceDirectory>
            <resources>
                <resource>
                    <directory>java</directory>
                </resource>
            </resources>
            <testResources>
                <testResource>
                    <directory>test</directory>
                </testResource>
            </testResources>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>2.3</version>
                    <configuration>
                        <encoding>UTF-8</encoding>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.easymock</groupId>
                <artifactId>easymock</artifactId>
                <version>3.4</version>
            </dependency>
            <dependency>
                <groupId>ant</groupId>
                <artifactId>ant</artifactId>
                <version>1.7.0</version>
            </dependency>
            <dependency>
                <groupId>wsdl4j</groupId>
                <artifactId>wsdl4j</artifactId>
                <version>1.6.2</version>
            </dependency>
            <dependency>
                <groupId>javax.xml</groupId>
                <artifactId>jaxrpc</artifactId>
                <version>1.1</version>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jdt.core.compiler</groupId>
                <artifactId>ecj</artifactId>
                <version>4.5.1</version>
            </dependency>
        </dependencies>
    </project>
    

    好了之后,可以将源码导入到工程中,这里我使用的是idea,导入完成之后如下图所示。

    接下来就是分析源码吗?别急,现在分析也不会有任何思路,完全一脸懵逼,所以我们得想想该如何入手。看了看这些文件,我们能够得出的结论是,发现Tomcat是Java语言开发的,其实这个可能大家一开始就知道,没什么了不起的。既然是Java语言开发的,我们又是Java开发人员,不妨先自己手写一个Tomcat,然后看根据手写的思路再去推导源码的实现方式,这样不就无缝结合了吗?ok,那就手写咯。

    2 手写Tomcat

    手写之前先想想Tomcat的作用是什么?是web服务器,它是给客户端提供访问并且返回给客户端资源的,此时得要写一段代码能够让客户端连接上来,然后再进行数据交互操作。怎样让客户端连接上来?用Java中已有的知识可以采用Socket编程。

    2.1 ServerSocket监听端口等待客户端连接

    class MyTomcat{
        ServerSocket server=new ServerSocket(8080);
        Socket socket=server.accept();
    }
    

    2.2 根据客户端的连接处理数据交互

    class MyTomcat{
        ServerSocket server=new ServerSocket(8080);
        Socket socket=server.accept();
        //接受客户端传来的数据
        InputStream in=socket.getInputstream();
        //进行相应的业务处理
        ......
        //返回给客户端数据
        OutputStream out=socket.getOutputStream();
    }
    

    至此,通过Socket编程和IO一个mini版的Tomcat就完成了,关键是我们得想想是否可以优化。

    2.3 优化Tomcat

    • Request和Response优化

    了解过http协议的小伙伴都知道,http传来的请求有请求头,请求行,请求体这些,最主要的是哪些键值对数据,既然Java是一门面向对象的开发语言,不妨把这些数据封装成Java中的对象,于是我们声明一个Request类,如下所示。

    class Request{
        private String accept;
        private String host;
        priavte ...
    }
    

    这些属性和http请求中的数据时一一对应的,有了request之后,不妨把response也封装一下咯。

    class Response{
        private String Content-Type;
        private Date date;
        private ...
    }
    

    这时候http中的请求和响应和Java中的Request和Response对象已经对应起来了,所以代码可以优化成如下所示。

    class MyTomcat{
        ServerSocket server=new ServerSocket(8080);
        Socket socket=server.accept();
        //接受客户端传来的数据
        InputStream in=socket.getInputstream();
        //将输入流封装成Request对象
        Request request=new Request(in);
        //进行相应的业务处理
        ......
        //返回给客户端数据
        OutputStream out=socket.getOutputStream();
        //将输入流封装成Response对象
        Response response=new Response(out);
    }
    
    • Servlet优化

    这时候Tomcat已经相对于前面的优雅一些了,此时我们来假设一个业务场景,比如需要做一个登陆的功能,肯定要拿到客户端传来的用户名和密码。也就是业务代码一定要获取到用户名和密码,但是如果使用自己写的Tomcat,发现Request对象又被封装在了Tomcat内部,发现获取该对象不是太方便,所以得换个思路。其实在JavaEE的Servlet规范中,Servlet帮我们封装好了请求和响应的类,大家一定很熟悉。

    class Servlet{
        service(Request request,Response response){
            ...
        }
    }
    

    也就是说如果我们想要进行业务代码的开发,比如登录,可以写一个类继承Servlet,然后就可以获取到客户端传来的请求和响应。

    class LoginServlet extends Servlet{
        doService(Request request,Response response){
            ...
        }
    }
    

    这样一来就可以顺利拿到客户端传来的数据以及返回数据给客户端。

    关键是这里的request和原来我们自己写的tomcat中的request的关系是如何的呢?这样不就重复定义了吗?好像有点,那不妨把类似于LoginServlet添加到Tomcat源码中?也就是但凡有一个servlet就加一个到Tomcat中进行处理,也就是Tomcat源码可以优化成这样。

    class MyTomcat{
        ServerSocket server=new ServerSocket(8080);
        Socket socket=server.accept();
        //这样地方就可以用一个List集合不断地把业务代码中servlet加载进来
        list.add(servlets);
    }
    

    我们的web项目中到底有多少个servlet呢?根据大家的开发经验都知道,每个业务代码的Servlet类都可以在web.xml文件中进行配置,类似于下面这段配置。

    <servlet>
           <servlet-name>LoginServlet</servlet-name>
           <servlet-class>com.gupao.web.servlet.SimpleServlet</servlet-class>
    </servlet>
    <servlet-mapping>  
           <servlet-name>LoginServlet</servlet-name> 
           <url-pattern>/login</url-pattern>
    </servlet-mapping>
    

    这样在Tomcat源码中只需要读取到每个项目对应的web.xml文件,然后解析其中的servlet标签,将一个个servlet实例化加载到list集合中即可。

    2.4 思路过渡

    有了上面手写的思路分析,此时可以总结出两点。第一,监听端口;第二,加载servlets。发现说起来还挺合情合理的,但是这些都是我们自己的主观臆断,官网源码是否也是这样的思路,这个我们需要验证,那接下来咱们就围绕这两点进行一下验证,先验证servlets再验证监听端口。

    3 验证加载servlets和监听端口

    3.1 加载servlets

    那源码中到底哪里是加载servlet的呢?我先给大家一条搜索主线。

    Context->StandardContext.loadOnStartup(Container children[])这个方法就是加载每个web项目加载servlets的地方。很明显,下面方法上的英文注释已经表明了,而且在代码中也能找到list.add(wrapper)这个方法,和手写中的list.add(servlets)非常像,只要证明wrapper就是servlet即可。

    /**
     * Load and initialize all servlets marked "load on startup" in the
     * web application deployment descriptor.
     *
     * @param children Array of wrappers for all currently defined
     *  servlets (including those not declared load on startup)
     */
    public boolean loadOnStartup(Container children[]) {
        // Collect "load on startup" servlets that need to be initialized
        TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
        for (int i = 0; i < children.length; i++) {
            Wrapper wrapper = (Wrapper) children[i];
            int loadOnStartup = wrapper.getLoadOnStartup();
            if (loadOnStartup < 0)
                continue;
            Integer key = Integer.valueOf(loadOnStartup);
            ArrayList<Wrapper> list = map.get(key);
            if (list == null) {
                list = new ArrayList<>();
                map.put(key, list);
            }
            list.add(wrapper);
        }
        ...
    }
    

    这时候大家可能会有两个疑惑,一是为什么是找StandardContext.loadOnStartup()这个方法,二是wrapper到底是不是servlet。接下来我们就围绕这两个问题探讨一下。

    3.1.1 为什么是StandardContext.loadOnStartup()

    我是这样想的,如果我是Tomcat源码的设计者,加载servlet一定会以web项目为单位,因为Tomcat中可能会有很多的web项目,而每个web项目中又有很多的Servlet类,所以以web项目为单位是最合适的。那Tomcat源码中什么可以代表一个web项目呢?问题就会聚焦到找代表web项目的东西。

    先不急,我们可以回到tomcat产品中在conf下有一个server.xml文件,而这个文件是tomcat的核心配置文件,我找到一份server.xml文件,并且把其中的注释删掉了,如下所示。

    <?xml version='1.0' encoding='utf-8'?>
    <Server port="8005" shutdown="SHUTDOWN">
      <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
      <!-- Prevent memory leaks due to use of particular java/javax APIs-->
      <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">
        <Connector port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />
        <Connector port="8009" protocol="AJP/1.3" 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>
    

    大家可以先不用具体关系每个标签的含义,至少这个文件大家是见过的,而且我们可能还配置过一些标签属性之类的,比如我们想要把web项目放到Tomcat中供外界访问,可以将项目deploy到webapps下,这是一种方式。除此之外,在里面还可以配置这样的标签。是放到Host标签里面的,也就是可以通过这样的方式同样达到和deploy到webapps一样的效果。

    <Context path="/gupao" docBase="E:/gupao"/>
    

    这说明什么呢?是不是可以某种意义一个Context标签就可以代表一个web项目?是的,没错,可以。再看server.xml文件中的标签还有什么含义?看过一些框架源码的哥们应该知道,这些标签一般和源码中的类或接口是一一对应的,我们可以尝试在Tomcat源码中搜索这些标签名称的类,我这里就不带领大家搜索了,大家可以自己做一个验证。

    另外我再给大家展示一张图,这张图是Tomat的架构图,通过这张图大家能够发现,图中的组件和server.xml文件中的标签名称一一对应,而且包含的层次也是一一对应的。当然,图中的有些组件的名称可能在默认server.xml文件中没有配置,图解中如果某个组件是多层的意味着该标签在server.xml文件可以配置多个。也就是说Tomcat架构图,server.xml中标签和源码中的类是一一对应的关系。

    ok,啰嗦了这么多,回到我们想讨论的内容。我们已经能够知道Context标签可以代表web项目,也就是Tomcat源码中一定有一个Context类,而且该类代表的是web项目。

    前面说加载servlets一定是以web项目为单位的,如果我想要找加载servlets在源码中的地方,一定是先找到Context类,发现它是一个接口,肯定继续找它的实现类,它有一个标准的实现类StandardContext。接着要想找加载servlets的地方,肯定是找这个类的方法,打开该类的所有方法,发现只有loadOnStartup是命名最像的。至此,Tomcat源码加载servlets的完成。

    3.1.2 wrapper到底是不是servlet

    接下来要探讨的问题就是在loadOnStartup中list.add(wrapper)这个到底是不是添加的servlet,换句话说就是wrapper到底是不是servlet。

    先把脑袋放空,什么都不要想,要想证明wrapper是servlet,我觉得可以先看另外一个问题。也就是我们找到了Tomcat源码加载servlet的地方,但是都没有看到Tomcat源码解析每个web项目中web.xml文件servlet标签的过程,不妨把这个先找一下。

    既然Context代表web项目,那web项目的配置命名应该是ContextConfig,源码中搜索一下该类,果然有,也就是读取web项目的配置文件是在这个类中发生的,关键是找哪个方法呢?

    一不小心找到一个webConfig()方法,如下所示

     /**
         * Scan the web.xml files that apply to the web application and merge them
         * using the rules defined in the spec. For the global web.xml files,
         * where there is duplicate configuration, the most specific level wins. ie
         * an application's web.xml takes precedence over the host level or global
         * web.xml file.
         */
        protected void webConfig() {
        	...
        }
    

    该方法上的注释想必大家都能看到,英语应该都过了8级对吧?显然是扫描每个web项目中对应的web.xml文件。

    • 找到web.xml文件的路径

    要想加载解析web.xml文件肯定要先知道它的路径,大家可以按照下面这条线找到对应的路径。

    org.apache.catalina.startup.ContextConfig#webConfig()->getContextWebXmlSource()->servletContext.getResourceAsStream(Constants.ApplicationWebXml)

    我们可以看到一个ApplicationWebXml配置:

    Constants.ApplicationWebXml = "/WEB-INF/web.xml";
    
    • 解析web.xml文件中的标签并创建对象

    继续回到webConfig()方法,找到step9有这样一段代码

    // Step 9. Apply merged web.xml to Context
    if (ok) {
        configureContext(webXml);
    }
    

    也就是configureContext(webXml)就是解析的,那点开这个方法看一下咯。慢慢往下拉,会发现有很多web.xml文件中熟悉的标签,比如ErrorPage,FilterMap,listener,ContextResource等。继续往下看,会发现有这段代码。这里明显就是解析web.xml文件中的servlet,并且创建相应的对象,然后会将servlet包装成wrapper。ok,至此,我们可以确定的是wrapper就是对servlet的包装,也就是servlet。

    for (ServletDef servlet : webxml.getServlets().values()) {
        Wrapper wrapper = context.createWrapper();
        // Description is ignored
        // Display name is ignored
        // Icons are ignored
    
        // jsp-file gets passed to the JSP Servlet as an init-param
    
        if (servlet.getLoadOnStartup() != null) {
            wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
        }
        if (servlet.getEnabled() != null) {
            wrapper.setEnabled(servlet.getEnabled().booleanValue());
        }
        wrapper.setName(servlet.getServletName());
        ...
    }
    

    3.2 监听端口

    经过上面一同胡说八道,终于解决了手写Tomcat中加载servlets以及源码验证部分的内容。接下来就是要解决监听端口在Tomcat源码中是否也有同样的实现方式。说白了就是要找到new ServerSocket(port)这样的代码。

    关键是从哪入手呢?不妨把目光还是转移到server.xml文件和Tomcat架构图,忘了那张架构图的可以往上翻一下,我这里就不贴出来咯。要想和客户端发生点关系,用脚指头想也知道肯定和Connector相关,也就是说和源码中Connector类相关,那接下来咱们就从Connector开始找找ServerSocket在哪。

    Connector.initInternal()->protocolHandler.init()->AbstractProtocol.init()->endpoint.init()->bind()-JIoEndpoint.bind()

    对于bind()方法其实支持很多种IO实现方式,比如有BIO,NIO,NIO2等。

    打开JIoEndpoint.bind()方法,发现其中有这段代码

    if (getAddress() == null) {
        serverSocket = serverSocketFactory.createSocket(getPort(),
                getBacklog());
    } else {
        serverSocket = serverSocketFactory.createSocket(getPort(),
                getBacklog(), getAddress());
    }
    

    很明显,这不就是我们要找的ServerSocket创建的代码吗?继续点开createSocket(getPort(),
    getBacklog())方法,找到DefaultServerSocketFactory的实现。

    @Override
    public ServerSocket createSocket (int port, int backlog)
            throws IOException {
        return new ServerSocket (port, backlog);
    }
    

    ok,到这里已经找到了Tomcat源码中使用ServerSocket监听的代码部分,这其实也是BIO的实现方法,也就是JIo,即Java IO[传统Java IO的实现方式]

    4 Tomcat整体源码阅读

    经过上面的折腾,我们已经能从Tomcat中找到加载servlet和监听端口所在的地方,接下来我们将整个过程串一串,也就是从Tomcat启动开始大概经历的过程。

    4.1 Tomcat启动入口类

    既然Tomcat是Java开发的,那么肯定有一个入口类,这个类中一定会有一个main函数。

    从方法的注释可以看得出来,该方法是启动Tomcat的一个入口方法。

    /**
     * Main method and entry point when starting Tomcat via the provided
     * scripts.
     *
     * @param args Command line arguments to be processed
     */
    public static void main(String args[]) {
    
        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
            }
        }
        ...
    }
    

    继续往下走,会发现有startstop这样的字符串。顾名思义,肯定是根据脚本名称执行相应的操作,比如我们想进行start操作,这时候会调用这段代码。

    if (command.equals("startd")) {
        args[args.length - 1] = "start";
        //先加载
        daemon.load(args);
        //再启动
        daemon.start();
    } 
    

    4.2 load()过程

    当调用load的时候,发现它使用的是反射的方式调用Catalina.load()方法。

    来到该方法,发现如下代码,看上面的注释发现是Start a new server instance.也就是开始一个server的实例。

    /**
     * Start a new server instance.
     */
    public void load() {
        long t1 = System.nanoTime();
        ...
    }
    

    这时候我们不妨先想想,为什么是Server呢?这个名称好像在哪看过。没错,在server.xml文件中,
    Tomcat架构图最外层的组件,Tomcat源码中都有这样的Server名称。是不是可以这样认为?现在Tomcat源码实际上是要根据server.xml文件来创建相应的对象,而这个创建的过程是从最外面的Server开始,然后依次往里面进行加载,比如Service,Connector等,如果是这样,我们继续往下看。

    4.3 找到server.xml文件位置

    不管先加载创建哪个名称,肯定是先要找到server.xml文件所在位置,来看代码

    file = configFile()->File file = new File(configFile)->protected String configFile = "conf/server.xml";

    这样就顺利找到了server.xml文件所在位置。

    4.4 解析server.xml文件加载Server标签创建对象

    继续回到Catalina.init()方法,该方法加载到server.xml文件之后,就会把起解析到input输入流中。

    inputStream = new FileInputStream(file);
    inputSource = new InputSource(file.toURI().toURL().toString());
    
    try {
        inputSource.setByteStream(inputStream);
        digester.push(this);
        digester.parse(inputSource);
    }
    

    然后就可以解析server.xml由外向内创建相应对象咯,这一块大家可以把之前的Tomcat架构图放在一旁结合源码一起看会更有感觉。

    // Start the new server
    try {
        getServer().init();
    } catch (LifecycleException e) {
        if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
            throw new java.lang.Error(e);
        } else {
            log.error("Catalina.start", e);
        }
    }
    

    4.5 LifeCycle的意义

    点开getServer().init()方法,发现来到的是LifeCycle.init()方法,很奇怪,为啥?大家想一想,无论是Server,Service,Connector等这些组件是不是都有会init,start,destroy方法,也就是说它们都会有这些生命周期,所以按照Java面向对象开发的思想,不妨把这个生命周期统一管理起来,于是有了LifeCycle。通过会将LifeCycle设计为接口,然后会有默认的实现类LifeCycleBase

    public interface Lifecycle {
        // ----------------------------------------------------- Manifest Constants
        /**
         * The LifecycleEvent type for the "component after init" event.
         */
        public static final String BEFORE_INIT_EVENT = "before_init";
    }
    

    所以LifeCycle.init()会继续调用LifeCycleBase.init()方法,在LifeCycleBase.init()中会调用initInternal(),发现该方法是抽象的,所以要找对应的实现类。具体找哪个实现类要根据目前想要初始化的类是哪一个。

    protected abstract void initInternal() throws LifecycleException;
    

    比如目前要初始化的Server对象,那肯定是调用Server的initInternal()方法。

    4.6 StandardServer.initInternal()

    通常Server,Service都是一个接口,所以要找它们对应的实现类,比如Server就会找StandardServer类,也就是调用StandardServer.initInternal()方法对其进行初始化,最后会有这段代码。即初始化完Server之后,要对里面的小弟进行初始化,比如Service,大家可以结合Tomcat架构图来看,只不过发现这边使用的for循环,为何?从架构图中可以看到Service是可以有多层的,所以当然要用循环来初始化。

    // Initialize our defined Services
    for (int i = 0; i < services.length; i++) {
        services[i].init();
    }
    

    4.7 services[i].init()

    继续点开service[i].init()方法,查看init的逻辑,发现还是来到的是LifeCycle.init(),意料之中,也就是会走这条调用线路:service[i].init()-LifeCycle.init()->LifeCycleBase.init()->initInternal()->StandardService.initInternal()

    由下面这段代码可以发现,初始化完Service之后,会初始化Service里面的小弟,这边大家一定要结合架构图来看,比如有Executor,Connector等,而且用的也是循环结构,因为在架构图显示的是多层。比如我们来看Connector的初始化。

    @Override
    protected void initInternal() throws LifecycleException {
        super.initInternal();
        if (container != null) {
            container.init();
        }
        // Initialize any Executors
        for (Executor executor : findExecutors()) {
            if (executor instanceof JmxEnabled) {
                ((JmxEnabled) executor).setDomain(getDomain());
            }
            executor.init();
        }
    
        // Initialize mapper listener
        mapperListener.init();
    
        // Initialize our defined Connectors
        synchronized (connectorsLock) {
            for (Connector connector : connectors) {
                try {
                    connector.init();
                } catch (Exception e) {
                    String message = sm.getString(
                            "standardService.connector.initFailed", connector);
                    log.error(message, e);
    
                    if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
                        throw new LifecycleException(message);
                }
            }
        }
    }
    

    4.8 connector.init()

    connector这个组件在前面分析监听端口的时候,我们看到接触过。接下来我们来看下connector.init()调用流程。

    connector.init()->LifeCycleBase.init()->LifeCycleBase.initInternal()->Connector.initInternal()

    这块有没有想起对于Connector监听端口我们找的就是initInternal()方法?这样就和前面监听端口的部分串联起来了,然后往下走的话就是这样的流程。

    Connector.initInternal()->protocolHandler.init()->AbstractProtocol.init()->endpoint.init()->bind()-JIoEndpoint.bind()

    4.9 小结

    通过上面流程的分析,我们能知道Tomcat从入口类开始的整体流程,我也给大家画好了一张总结图,大家可以对照这个图再看看前面的分析,希望对大家有所帮助。如若有不对之处,欢迎大家一起探讨交流,谢谢。

  • 相关阅读:
    验证数字范围的小插件
    解决EJB懒加载问题
    JS获取按键的代码,Js如何屏蔽用户的按键,Js获取用户按键对应的ASII码(兼容所有浏览器)
    struts2标签之<s:select>
    c#(winform)中自定义ListItem类方便ComboBox和ListBox添加项完全解决
    辞职前须慎重考虑
    怎样把PDF文件在WinForm窗口中显示出来
    加载报表失败
    经典正则表达式 Javascript
    无法生成项目输出组“内容文件来自...
  • 原文地址:https://www.cnblogs.com/zh-ch/p/14754186.html
Copyright © 2011-2022 走看看