tomcat源码解析——初始化

        tomcat作为一个web容器,实现了servlet规范,是对http请求进行处理的关键部分。下面是Tomcat整体架构:


catalina类是tomcat处理请求的起点。下面的server是tomcat组件的容器,可以在server.xml中配置多个。service包含了connector和container,connector即连接器,支持http/https/ajp协议,后来又增加了websocket的支持。下面的介绍引用了其他博客对这几个组件的大致描述:

1、Catalina:与开始/关闭shell脚本交互的主类,因此如果要研究启动和关闭的过程,就从这个类开始看起。
2、Server:是整个Tomcat组件的容器,包含一个或多个Service。
3、Service:Service是包含Connector和Container的集合,Service用适当的Connector接收用户的请求,再发给相应的Container来处理。
4、Connector:实现某一协议的连接器,如默认的有实现HTTP、HTTPS、AJP协议的。
5、Container:可以理解为处理某类型请求的容器,处理的方式一般为把处理请求的处理器包装为Valve对象,并按一定顺序放入类型为Pipeline的管道里。Container有多种子类型:Engine、Host、Context和Wrapper,这几种子类型Container依次包含,处理不同粒度的请求。另外Container里包含一些基础服务,如Loader、Manager和Realm。
6、Engine:Engine包含Host和Context,接到请求后仍给相应的Host在相应的Context里处理。
7、Host:就是我们所理解的虚拟主机。
8、Context:就是我们所部属的具体Web应用的上下文,每个请求都在是相应的上下文里处理的
9、Wrapper:Wrapper是针对每个Servlet的Container,每个Servlet都有相应的Wrapper来管理。
可以看出Server、Service、Connector、Container、Engine、Host、Context和Wrapper这些核心组件的作用范围是逐层递减,并逐层包含。

        启动tomcat首先调用startup.sh脚本,可以看到追加了一堆jvm参数,组后调用的是bootstrap类的main方法。tomcat就是从这里开始初始化的。

/usr/bin/java -Djava.util.logging.config.file=/usr/local/apache-tomcat-8.5.31/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -classpath /usr/local/apache-tomcat-8.5.31/bin/bootstrap.jar:/usr/local/apache-tomcat-8.5.31/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/apache-tomcat-8.5.31 -Dcatalina.home=/usr/local/apache-tomcat-8.5.31 -Djava.io.tmpdir=/usr/local/apache-tomcat-8.5.31/temp org.apache.catalina.startup.Bootstrap start

        bootstrap.main

public static void main(String args[]) {

        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
                //配置tomcat_home/base环境变量
                //初始化tomcat的三个类加载器
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                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 {
            //从main方法的args接收的tomcat参数处理
            String command = "start";
            if (args.length > 0) {
                command = args[args.length - 1];
            }

            if (command.equals("startd")) {
                args[args.length - 1] = "start";
                //加载命令行参数
                daemon.load(args);
                daemon.start();
            } else if (command.equals("stopd")) {
                args[args.length - 1] = "stop";
                daemon.stop();
            } else if (command.equals("start")) {
                //反射setAwait,使tomcat保持进程
                daemon.setAwait(true);
                //主要通过反射调用catalina类的load方法
                daemon.load(args);
                //调用catalina的start
                daemon.start();
            } else if (command.equals("stop")) {
                daemon.stopServer(args);
            } else if (command.equals("configtest")) {
                daemon.load(args);
                if (null==daemon.getServer()) {
                    //异常关闭jvm
                    System.exit(1);
                }
                //正常关闭jvm
                System.exit(0);
            } else {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
            System.out.println("bootstrap main has been execute finish");
        } catch (Throwable t) {
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }

    }

bootstrap.init

public void init()
        throws Exception
    {

        //设置了tomcat的环境变量,默认为当前目录,就算不配置环境变量依然能运行
        setCatalinaHome();
        setCatalinaBase();
        //初始化类加载器
        initClassLoaders();

        Thread.currentThread().setContextClassLoader(catalinaLoader);

        SecurityClassLoad.securityClassLoad(catalinaLoader);

        // Load our startup class and call its process() method
        if (log.isDebugEnabled())
            log.debug("Loading startup class");
        //反射加载catalina类,构造catalina对象
        Class<?> startupClass =
            catalinaLoader.loadClass
            ("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.newInstance();

        // Set the shared extensions class loader
        if (log.isDebugEnabled())
            log.debug("Setting startup class properties");
        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;

    }

catalina.load,第一次被调用是在bootstrap的load方法中。主要作用是通过读取xml实例化server。主要看一下最后的getServer(),init()方法。

public void load() {
        long t1 = System.nanoTime();
        //初始化cataline_home和base,指定为当前项目路径
        initDirs();

        // Before digester - it may be needed
        //重新设置JMX的naming,加入apache的前缀,初始化命名服务
        initNaming();

        // Create and execute our Digester
        //Digester是apache commons中的一种xml解析器,处理conf/server.xml
        //在这个方法里,读取serverl.xml时通过反射实例化了standardServer对象
        Digester digester = createStartDigester();

        InputSource inputSource = null;
        InputStream inputStream = null;
        File file = null;
        try {
            try {
                //获取配置文件
                file = configFile();
                inputStream = new FileInputStream(file);
                //SAX对象解析xml
                inputSource = new InputSource(file.toURI().toURL().toString());
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("catalina.configFail", file), e);
                }
            }
            if (inputStream == null) {
                try {
                    inputStream = getClass().getClassLoader()
                            .getResourceAsStream(getConfigFile());
                    inputSource = new InputSource
                            (getClass().getClassLoader()
                                    .getResource(getConfigFile()).toString());
                } catch (Exception e) {
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("catalina.configFail",
                                getConfigFile()), e);
                    }
                }
            }

            // This should be included in catalina.jar
            // Alternative: don't bother with xml, just create it manually.
            if (inputStream == null) {
                try {
                    inputStream = getClass().getClassLoader()
                            .getResourceAsStream("server-embed.xml");
                    inputSource = new InputSource
                            (getClass().getClassLoader()
                                    .getResource("server-embed.xml").toString());
                } catch (Exception e) {
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("catalina.configFail",
                                "server-embed.xml"), e);
                    }
                }
            }


            if (inputStream == null || inputSource == null) {
                if (file == null) {
                    log.warn(sm.getString("catalina.configFail",
                            getConfigFile() + "] or [server-embed.xml]"));
                } else {
                    log.warn(sm.getString("catalina.configFail",
                            file.getAbsolutePath()));
                    if (file.exists() && !file.canRead()) {
                        log.warn("Permissions incorrect, read permission is not allowed on the file.");
                    }
                }
                return;
            }

            try {
                //digester解析xml拼成对象
                inputSource.setByteStream(inputStream);
                digester.push(this);
                digester.parse(inputSource);
            } catch (SAXParseException spe) {
                log.warn("Catalina.start using " + getConfigFile() + ": " +
                        spe.getMessage());
                return;
            } catch (Exception e) {
                log.warn("Catalina.start using " + getConfigFile() + ": ", e);
                return;
            }
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
        getServer().setCatalina(this);

        // Stream redirection
        //自定义了System.out.PrintStream,写了一个子类继承PrintStream,便于多线程下的日志处理
        initStreams();

        // Start the new server
        try {
            //初始化new server对象
            getServer().init();
        } catch (LifecycleException e) {
            //boolea.getBoolean 获取系统环境变量,判断是否与true相等
            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");
        }

    }

LifecycleBase.init。tomcat中每个组件都继承了这个lifecycleBase,用于统一管理组件的生命周期,启动tomcat最主要的是两个方法,init和start。tomcat启动时序图如下:


public final synchronized void init() throws LifecycleException {
        //这个是lifecycleBase的init方法,即server、service、connector以及各种监听器的初始化方法,这里每个组件都有一个生命周期状态
        if (!state.equals(LifecycleState.NEW)) {
            //状态校验,不对直接抛异常
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        }
        //设置状态为加载中
        setStateInternal(LifecycleState.INITIALIZING, null, false);

        try {
            //初始化方法
            initInternal();
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            setStateInternal(LifecycleState.FAILED, null, false);
            throw new LifecycleException(
                    sm.getString("lifecycleBase.initFail",toString()), t);
        }
        //设置内部状态为加载完成
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    }    

    可以看到,lifecycle中有status,用于判断当前组件的状态如初始化new、加载中initalizing、加载完成initalized,如果状态不对会打印log不做处理抑或直接抛异常。下面来看initInternal这个初始化的核心方法,它是个抽象方法,交给子类,让子类自己完成初始化,第一次调用时是初始化server,之前我们看到server在xml解析的过程中已经被实例化过了,这里主要是做一些注册JMX组件的操作。默认是standardServer的实现,来看一看。

    JMX即Java Management Extensions,用于管理java中组件资源,类似于一个系统注册表,或是一个服务注册中心。将散落的配置文件整合到一个MBean对象中,它包括资源管理、组建管理(MBean)和组件管理服务器。JMX可以配合jconsole.exe(位于jdk/bin/下)做监控,监控系统运行状态与异常。详情:https://www.cnblogs.com/dongguacai/p/5900507.html

LifecycleBase.initInternal

//这个方法用于将conponent注册到LifecycleMBeanBase
    protected void initInternal() throws LifecycleException {

        //Registry.getRegistry方法获取注册表,获取一个MBeanServer实例
        super.initInternal();

        // Register global String cache
        // Note although the cache is global, if there are multiple Servers
        // present in the JVM (may happen when embedding) then the same cache
        // will be registered under multiple names

        //一个工具方法——不用使用JMX的MBeanRegistration接口注册tomcat component
        //arg1为被注册的对象,arg2为注册对象名
        onameStringCache = register(new StringCache(), "type=StringCache");

        // Register the MBeanFactory
        MBeanFactory factory = new MBeanFactory();
        factory.setContainer(this);
        onameMBeanFactory = register(factory, "type=MBeanFactory");
        
        // Register the naming resources
        globalNamingResources.init();
        
        // Populate the extension validator with JARs from common and shared
        // class loaders
        if (getCatalina() != null) {
            ClassLoader cl = getCatalina().getParentClassLoader();
            // Walk the class loader hierarchy. Stop at the system class loader.
            // This will add the shared (if present) and common class loaders
            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")) {
                                    //遍历url类加载器的资源,从jar包中装载manifest文件
                                    ExtensionValidator.addSystemResource(f);
                                }
                            } catch (URISyntaxException e) {
                                // Ignore
                            } catch (IOException e) {
                                // Ignore
                            }
                        }
                    }
                }
                cl = cl.getParent();
            }
        }
        System.out.println("standardServer will init services");
        // Initialize our defined Services
        //service在catalina的load方法中通过Digester读取server.conf初始化过了
        for (int i = 0; i < services.length; i++) {
            //默认这里会是一个standardService
            services[i].init();
        }
    }

最后可以看到在standardServer中的initInternal方法里又对service进行了初始化。这个service也是digester解析server.xml时实例化出来的,在server.xml里你可以配置多个service,实现多实例的web容器,部署多个项目。由于service也继承了LifecycleBase这个生命周期抽象类,它调用的init方法在LifecycleBase中被分发到standardService的initInternal方法里。我们继续往下看:

standardService.initInternal

protected void initInternal() throws LifecycleException {
        //service组件注册
        super.initInternal();
        
        if (container != null) {
            container.init();
        }

        // Initialize any Executors
        //可以在server.xml中为一个Service和所有的connector配置一个共享线程池
        for (Executor executor : findExecutors()) {
            if (executor instanceof LifecycleMBeanBase) {
                ((LifecycleMBeanBase) executor).setDomain(getDomain());
            }
            executor.init();
        }

        // Initialize our defined Connectors
        //初始化定义的connector
        //这个connector是实现协议的连接器,如http、https、ajp、websocket等
        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);
                }
            }
        }
    }

    最上面的super.initInternal()回调LifecycleMBeanBase的initInternal方法,用于注册JMX的MBean。接下来在service中分别初始化了container、executor、connector。这三个类同样继承了LifecycleBase,由LifecycleBase交给他们自己分别初始化。需要注册MBean组件的地方则继承LifecycleMBeanBase。

LifecycleMBeanBase.initInternal

protected void initInternal() throws LifecycleException {
        
        // If oname is not null then registration has already happened via
        // preRegister().
        //JMX,这个方法将Component实例注册到LifecycleMBeanBase,比如standardService调用这个,注册type=service
        if (oname == null) {
            mserver = Registry.getRegistry(null, null).getMBeanServer();
            
            oname = register(this, getObjectNameKeyProperties());
        }
    }

    各个组件的init方法代码逐一贴上去,先来看看这个connector的init方法。

Connector.initInternal

protected void initInternal() throws LifecycleException {
        //connector组件注册
        super.initInternal();

        // Initialize adapter
        //请求处理器
        adapter = new CoyoteAdapter(this);
        //prototolHandler协议处理器
        protocolHandler.setAdapter(adapter);

        // Make sure parseBodyMethodsSet has a default
        // http请求方法列表,默认为POST
        if( null == parseBodyMethodsSet ) {
            setParseBodyMethods(getParseBodyMethods());
        }
        //tomcat的connector支持两种会话层协议:http(HTTPS)/ajp,每种协议提供三种实现方式,分别是Apr(apache可移植runtime库),Nio(java NIO) 和 JIo(java io)
        /*AJP是定向包协议。因为性能原因,使用二进制格式来传输可读性文本。WEB服务器通过TCP连接和SERVLET容器连接。
        为了减少进程生成socket的花费,WEB服务器和SERVLET容器之间尝试保持持久性的TCP连接,对多个请求/回复循环重用一个连接。
        一旦连接分配给一个特定的请求,在请求处理循环结束之前不会再分配。
        换句话说,在连接上,请求不是多元的。这个使连接两端的编码变得容易,虽然这导致在一时刻会产生许多连接。
         */
        //需注意的是,当前版本tomcat不支持websocket协议,7.0.47版本以上才支持jsr-365
        if (protocolHandler.isAprRequired() &&
                !AprLifecycleListener.isAprAvailable()) {
            throw new LifecycleException(
                    sm.getString("coyoteConnector.protocolHandlerNoApr",
                            getProtocolHandlerClassName()));
        }

        try {
            protocolHandler.init();
        } catch (Exception e) {
            throw new LifecycleException
                (sm.getString
                 ("coyoteConnector.protocolHandlerInitializationFailed"), e);
        }

        // Initialize mapper listener
        //这个mapper监听器继承Mbean类,直接用的MBean类中的init方法注册组件,没有其他初始化操作
        mapperListener.init();
    }

    这里最主要的是初始化了协议处理器,默认使用基于JIO的http协议。

AbstractProtocal.init

public void init() throws Exception {
        if (getLog().isInfoEnabled())
            getLog().info(sm.getString("abstractProtocolHandler.init",
                    getName()));
        //在jmx中注册协议处理器
        if (oname == null) {
            // Component not pre-registered so register it
            oname = createObjectName();
            if (oname != null) {
                Registry.getRegistry(null, null).registerComponent(this, oname,
                    null);
            }
        }

        if (this.domain != null) {
            try {
                tpOname = new ObjectName(domain + ":" +
                        "type=ThreadPool,name=" + getName());
                Registry.getRegistry(null, null).registerComponent(endpoint,
                        tpOname, null);
            } catch (Exception e) {
                getLog().error(sm.getString(
                        "abstractProtocolHandler.mbeanRegistrationFailed",
                        tpOname, getName()), e);
            }
            rgOname=new ObjectName(domain +
                    ":type=GlobalRequestProcessor,name=" + getName());
            Registry.getRegistry(null, null).registerComponent(
                    getHandler().getGlobal(), rgOname, null );
        }

        String endpointName = getName();
        endpoint.setName(endpointName.substring(1, endpointName.length()-1));

        try {
            //初始化endPoint
            //endPoint提供基础的网络IO服务,创建ServerSocket,默认socket队列为100
            endpoint.init();
        } catch (Exception ex) {
            getLog().error(sm.getString("abstractProtocolHandler.initError",
                    getName()), ex);
            throw ex;
        }
    }

    初始化了一个endPoint,负责socket监听,endPoint方法里调用了bind方法,根据实现方法不同JIO/NIO/APR调用不同的绑定操作。下面来看看默认的JIO。在默认的server.xml配置文件中,tomcat默认提供了基于http(8080端口)和ajp的connector(8009端口),http的用于处理http request,而ajp协议的connector用于和其他http服务器集成。

public void bind() throws Exception {


        // Initialize thread count defaults for acceptor
        if (acceptorThreadCount == 0) {
            acceptorThreadCount = 1;
        }
        // Initialize maxConnections
        if (getMaxConnections() == 0) {
            // User hasn't set a value - use the default
            //用最大容量初始化一个线程池
            setMaxConnections(getMaxThreadsExecutor(true));
        }
        //serverSocketFactory
        //初始化serverSocket(服务端socket),配置socket消息队列长度
        if (serverSocketFactory == null) {
            if (isSSLEnabled()) {
                serverSocketFactory =
                    handler.getSslImplementation().getServerSocketFactory(this);
            } else {
                serverSocketFactory = new DefaultServerSocketFactory(this);
            }
        }


        if (serverSocket == null) {
            try {
                if (getAddress() == null) {
                    serverSocket = serverSocketFactory.createSocket(getPort(),
                            getBacklog());
                } else {
                    serverSocket = serverSocketFactory.createSocket(getPort(),
                            getBacklog(), getAddress());
                }
            } catch (BindException orig) {
                String msg;
                if (getAddress() == null)
                    msg = orig.getMessage() + " <null>:" + getPort();
                else
                    msg = orig.getMessage() + " " +
                            getAddress().toString() + ":" + getPort();
                BindException be = new BindException(msg);
                be.initCause(orig);
                throw be;
            }
        }


    }

    至此,connection的初始化工作已经完成了,继续service中container和executor的初始化过程。可以去server.xml中看到,这个container默认使用的是engine,调用的是StandardEngine类的init方法。

//TODO engine这里还没仔细看

    总结一下,tomcat的组件全部受LifestyleBase调控,通过Digester解析server.xml为树节点对象后,逐步初始化各个子组件,将需要的属性或引用实例化好,同时将其在JMX中注册以便监控,每个组件的生命周期状态统一交给LifestyleBase管理,一旦出现异常根据异常情况抛出异常或者取消初始化过程。全部初始化完成后,回到Catalina中继续调用start方法,和上面的初始化过程一样,逐个start。




猜你喜欢

转载自blog.csdn.net/hfismyangel/article/details/80459212