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从入口类开始的整体流程,我也给大家画好了一张总结图,大家可以对照这个图再看看前面的分析,希望对大家有所帮助。如若有不对之处,欢迎大家一起探讨交流,谢谢。

  • 相关阅读:
    数据库字段太多,批量快速建立实体类方法(适合大量字段建立实体类)
    SQL service 中的 ”输入SQL命令窗口“ 打开了 “属性界面” 回到 ”输入SQL命令窗口“
    计算机软件编程英语词汇集锦
    编程常用英语词汇
    svn上传和下载项目
    当启动tomcat时出现tomcat setting should be set in tomcat preference page
    Implicit super constructor Object() is undefined for default constructor. Must define an explicit constructor
    eclipse中选中一个单词 其他相同的也被选中 怎么设置
    Spring Boot的@SpringBootApplication无法引入的问题
    最全的SpringCloud视频教程
  • 原文地址:https://www.cnblogs.com/zh-ch/p/14754186.html
Copyright © 2011-2022 走看看