(四)Tomcat分析

来源:互联网 发布:国服mac版魔兽世界 编辑:程序博客网 时间:2024/05/22 02:02

Tomcat的顶层结构及启动过程

Tomcat的顶层结构

Tomcat中最顶层的容器叫Server,代表整个服务器,Server中包含至少一个Service。Service主要包含两部分:Connector和Container。Connector用于处理连接相关的事情,并提供Socket与request、response的转换,Container用于封装和管理Servlet,以及具体处理request请求。一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但可以有多个Connectors(因为一个服务器可以有多个连接,如同时提供http和https连接,也可以提供相同协议不同端口的连接),结构图如下:

这里写图片描述

Tomcat里的Server由org.apache.catalina.startup.Catalina来管理,Catalina是整个Tomcat的管理类,它里面的三个方法load、start、stop分别用来管理整个服务器的生命周期,load方法用于根据conf/server.xml文件创建Server并调用Server的init方法进行初始化,start方法用于启动服务器,start和stop方法在内部分别调用了Server的start和stop方法,load方法内部调用了Server的init方法,这三个方法都会按容器的结构逐层调用相应的方法,比如,Server的start方法中会调用所有Service中的start方法,Service中的start方法又会调用所有包含的Connectors和Container的start方法,这样整个服务器就启动了,init和stop方法也一样,就是Tomcat生命周期的管理方式。Catalina还有个方法,await方法,Catalina中的await方法直接调用了Service的await方法,这份方法的的作用就是进入一个循环,让主线程不会退出。

不过Tomcat的入口main方法并不在Catalina类里,而是在org.apache.catalina.startup.Bootstrap中。Bootstrap的作用类似一个CatalinaAdaptor,具体处理过程还是使用Catalina来完成的,这么做好处是可以把启动的入口和具体的管理类分开,从而可以很方便创建出多种启动方式,每种启动方式只需写一个相应的CatalinaAdaptor就可以了。

Bootstrap的启动过程

Bootstrap是Tomcat的入口,正常情况下启动Tomcat就是调用Bootstrap的main方法,代码:

//org.apache.catalina.startup.Bootstrappublic static void main(String args[]){    //先新建一个Bootstrap    if(deamon == null){        Bootstrap bootstrap = new Bootstrap();        try{            //初始化了ClassLoader,并用ClassLoader创建了Catlina示例,赋给catalinaDeamon变量            bootstrap.init();        }catch(Throwable t){            handleThrowable(t);            t.printStackTrace();            return;        }        daemon = bootstrap;    }else{        Thread.currentThread().setContextClassLoader(deamon.catalinaLoader);    }    try{        String command = "start";        if(args.length > 0){            command = args[args.length - 1];        }        if(command.equals("startd")){            args[args.length - 1] = "start";            deamon.load(args);            deamon.start();        }else if(command.equals("stopd")){            args[args.length - 1] = "stop";            deamon.stop();        }else if(command.equals("start")){            daemon.setAwait(true);            daemon.load(args);            daemon.start();        }else if(command.equals("stop")){            daemon.stopServer(args);        }else if(command.equals("configtest")){            deamon.load(args);            if(null == daemon.getServer()){                System.exit(1);            }            System.exit(0);        }else{            log.warn("Boostrap:command \""+command+"\" does not exist.");        }    }catch(Throwable t){        if(t instanceof InvocationTargetException &&            t.getCause() != null){            t = t.getCause();        }        handleThrowable(t);        t.printStackTrace();        System.exit(1);    }}

main只有两部分内容:首先创建了Bootstrap,并执行init方法初始化;然后处理main方法传入的命令,如果args参数为空,默认执行start。

在init方法里初始化了ClassLoader,并用ClassLoader创建了Catalina示例,然后赋给catalinaDaemon变量,后面对命令的操作都要使用catalinaDaemon来具体执行。

对start命令的处理调用了三个方法:setAwait(true)、load(args)和start()。这三个方法内部调用了Catalina的相应方法进行具体执行,只不过是用反射来调用的。start方法(另外两个方法会处理一些参数,调用方法类似)的代码:

//org.apache.catalina.startup.Bootstrappublic void start() throws Exception{    if(catalinaDaemon == null) init();    Method method = catalinaDaemon.getClass().getMethod("start",(Class[])null);    method.invoke(catalinaDaemon,(Object[])null);}

首先判断catalinaDaemon有没有初始化,如果没有则调用init方法对其进行初始化,然后使用Method进行反射调用Catalina的start方法。Method是java.lang.reflect包里的类,代表一个具体的方法,可以使用其中的invoke方法来执行所代表的方法,invoke方法由两个参数,第一个参数是Method方法所在的实体,第二个参数是可变参数用于Method方法执行时所需要的参数,所以上面的调用相当于((Catalina)catalinaDaemon).start()。setAwait和load也用类似的方法调用了Catalina中的setAwait和load方法。

Catalina的启动过程

Catalina的启动过程主要是调用setAwait、load和start方法来完成的。setAwait方法用于设置Server启动完成后是否进入等待状态的标志,如果为true则进入,否则不进入;load方法用于加载配置文件,创建并初始化Server;start方法用于启动服务器。

setAwait方法:

//org.apache.catalina.startup.Catalinapublic void setAwait(boolean b){    await = b;}

设置await属性的值,await属性会在start方法中的服务器启动完之后使用它来判断是否进入等待状态。

Catalina的laod方法根据conf/server.xml创建了Server对象,并赋值给server属性(具体解析操作时通过开源项目Digester完成的),然后调用了server的init方法,如下:

//org.apache.catalina.startup.Catalinapublic void load(){    long t1 = System.nanoTime();    //省略创建server代码,创建过程使用Digester完成    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);        }    }    long t2 = System.nanoTime();    if(log.isInfoEnabled()){        //启动过程中,控制台可以看到        log.info("Initialization processed in "+((t2-t1)/1000000)+" ms");    }}

Catalina的start方法主要调用了server的start方法启动服务器,并根据await属性判断是否让程序进入了等待状态:

//org.apache.catalina.satrtup.Catalinapublic void start(){    if(getServer() == null){        load();    }    long t1 = System.nanoTime();    try{        //调用Server的start方法启动服务器        getServer().satrt();    }catch(LifecycleException e){        log.fail(sm.getString("catalina.serverStartFail"),e);        try{            getServer().destroy();        }catch(LifecycleException el){            log.debug("destory() failed for failed Server ",el);        }        return;     }    long t2 = System.nanoTime();    if(log.isInfoEnabled()){        log.info("Server startup in "+ ((t2-t1)/1000000) +" ms");    }    //此处省略了注册关闭钩子代码    //进入等待状态    if(await){        await();        stop();    }}

首先判断Server是否已经存在了,如果不存在则调用load方法来初始化Server,然后调用Server的start方法来启动服务器,最后注册了关闭钩子并根据await属性判断是否进入等待状态,之前我们已将这里的await属性设置为true了,所以需要进入等待状态。Server的await方法内部会执行一个while循环,这样程序就停到了await方法,当await方法里的while循环退出时,就会执行stop方法,从而关闭服务器。

Server的启动过程

Server接口中提供了addService(Service service)、removeService(Service service)来添加和删除Service,Server的init方法和start方法分别循环调用了每个Servce的init方法和start方法来启动所有Service。

Server的默认实现是org.apache.catalina.core.StandardServer,StandardServer继承自LifecycleMBeanBase,LifecycleMBeanBase又继承自LifecycleBase,init和start方法就定义在了LifecycleBase中,LifecycleBase里的init方法和start方法又调用了initInternal方法和startInternal方法,这两个方法都是模板方法,由子类具体实现,所以调用StandardServer的init和start方法时会执行StandardServer自己的initInternal和startInternal方法,这就是Tomcat生命周期的管理方式,StandardServer中的initInternal和startInternal方法分别循环调用了每一个service的start和init方法:

//org.apache.catalina.core.StandardServerprotected void startInternal() throws LifecycleException{    ......    sychrnized(servicesLock){        for(int i = 0;i<services.length;i++){            services[i].start();        }    }}protected void initInternal() throws LifecycleException{    ......    for(int i = 0;i<services.length;i++){        services[i].init();    }}

除了startInternal和initInternal方法,StandardServer中还实现了await方法,Catalina中就是调用它让服务器进入等待状态的,核心代码:

//org.apache.catalina.core.StandardServerpublic void await(){    //如果端口为-2则不进入循环,直接返回    if(port == -2){        return;    }    //如果端口为-1则进入循环,而且无法通过网络退出    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;    }    //如果端口不是-1和-2(应该大于0),则会新建一个监听关闭命令的ServerSocket    awaitSocket = new ServerSocket(port,1,InetAddress.getByName(address));    while(!stopAwait){        ServerSocket serverSocket = awaitSocket;        if(serverSocket == null){            break;        }        Socket socket = null;        StringBuilder command = new StringBuilder();        InputStream stream;        socket = serverSocket.accept();        socket.setSoTimeOut(10*1000);        stream = socket.getInputStream();        //检查在指定端口接收到的命令是否和shutdown命令匹配        boolean match = command.toString().equals(shutdown);        //如果匹配则跳出循环        if(match){            break;        }    }}

await方法较长,这里省略了一些处理异常、关闭Socket以及对接收到数据处理的代码。处理大概逻辑是首先判断端口号,然后根据port的值分为三种处理方法:

  • port为-2,则会直接退出,不进入循环。
  • port为-1,则会进入一个while(!stopAwait)的循环,并且在内部没有break跳出的语句,stopAwait标志只有调用了stop方法才会设置为true,所以port为-1时只有在外部调用stop方法擦灰退出循环。
  • port为其他值,则也会进入一个while(!stopAwait)的循环,不过同时会在port所在端口启动一个ServerSocket来监听关闭命令,如果接收到了则会使用break跳出循环。这里端口port和关闭命令shutdown是在conf/server.xml文件中配置Server时设置的,默认设置如下:
<!-- server.xml --><Server port="8005" shutdown="SHUTDOWN">

这时会在8005端口监听“SHUTDOWN”命令,如果接收到了就会关闭Tomcat。如果不想使用网络命令来关闭服务器可以将端口设置为-1。另外await方法中从端口接收到数据后还会进行简单处理,如果接收到的数据中有ASCII码小于32的(ASCII中32以下的为控制符)则会从小于32的那个字符截断并丢弃后面的数据。

Service的启动过程

原创粉丝点击