• Tomcat启动过程源码分析四


    前言

    上一篇文章中我们讨论了Bootstrap类中main方法中涉及到的init方法,今天这篇文章我们来查看下load方法。

    daemon.setAwait(true);
    daemon.load(args);
    

    setAwait方法

    load方法执行前,执行了setAwait方法,跟进去查看

    public void setAwait(boolean await)
            throws Exception {
    
        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Boolean.TYPE;
        Object paramValues[] = new Object[1];
        paramValues[0] = Boolean.valueOf(await);
        Method method =
                catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
        method.invoke(catalinaDaemon, paramValues);
    
    }
    

    代码很简单,和上一篇文章中的类似,使用了反射调用了catalinaDaemon对象的setAwait方法,传递了参数true,而我们在上一篇文章中,init方法的最后讲解中可以看到catalinaDaemon对象是Catalina类的一个实例,所以查看Catalina对象的setAwait方法:

    public void setAwait(boolean b) {
        await = b;
    }
    

    看来daemon.setAwait(true)这句代码很简单,就是使用了反射,设置了catalina实例的await属性为true

    load方法

     /**
     * Load daemon.
     */
    private void load(String[] arguments)
            throws Exception {
        // Call the load() method
        String methodName = "load";
        Object param[];
        Class<?> paramTypes[];
        if (arguments == null || arguments.length == 0) {
            paramTypes = null;
            param = null;
        } else {
            paramTypes = new Class[1];
            paramTypes[0] = arguments.getClass();
            param = new Object[1];
            param[0] = arguments;
        }
        Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        if (log.isDebugEnabled())
            log.debug("Calling startup class " + method);
        method.invoke(catalinaDaemon, param);
    }
    

    这段代码有了前面的经验读起来很容易,Bootstrapload方法是使用了反射调用了Catalina类的load方法,我们继续查看Catalina类的load方法

     /**
     * Start a new server instance.
     */
    public void load() {
    
        long t1 = System.nanoTime();
    	//1 初始化相关属性
        initDirs();
    	//2 初始化相关属性
        initNaming();
    	//3 创建专门用来解析server.xml的Digester类,同时也隶属于Jakarta Commons项目,专门用来解析xml工具包
        Digester digester = createStartDigester();
    	......
        ......
    	//到这里为止都是在解析server.xml
    
        getServer().setCatalina(this);
    
        // Stream redirection
        initStreams();
    
        // Start the new server
        try {
    		//4 初始化一个Server实例 
            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");
        }
    
    }
    

    上面代码省略了部分,用中文注释注解出了相关含义,我们这里重点看下getServer().init(),getServer返回的是一个Server的实例,很明显这里返回的应该是一个StandardServer实例,我们继续查看StandardServerinit方法。

    然而我们跟到StandardServer类中没有直接的init方法,查看其实现的接口发现他实现了Server接口,Server继承了LifeCycle接口,StandardServer继承了LifecycleMBeanBase,LifecycleMBeanBase继承了LifecycleBase,而LifecycleBase也实现了Lifecycle,所以调用的init方法应该是LifecycleBaseinit方法。

     @Override
    public final synchronized void init() throws LifecycleException {
        //1 一些通用的代码
        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);
        }
    
        //3 通用代码
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    }
    

    我们可以看到,在LifeCycleBase中除了通用的代码还调用了initInternal方法。
    我们看下在LifecycleBase类中initInternal方法做了什么。

    protected abstract void initInternal() throws LifecycleException;
    

    在抽象类LifeCycleBase中只定义了initInternal方法,并没有去实现而在StandardServer中实现了initInternal,所以调用了StandardServer对象的init方法实际上就是在调用initInternal方法,我们来看看StandardServerinitInternal方法做了什么。

     /**
     * Invoke a pre-startup initialization. This is used to allow connectors
     * to bind to restricted ports under Unix operating environments.
     */
    @Override
    protected void initInternal() throws LifecycleException {
        
        super.initInternal();
    	
    	//不关心的代码 开始
        ......
    	......
    	//不关心的代码 结束
    
    	//关注的代码开始
    	//初始化Service
        // Initialize our defined Services
        for (int i = 0; i < services.length; i++) {
            services[i].init();
        }
    }
    

    前面的一篇文章我们说过Tomcat的架构是什么样子的,Server中包含多个Service
    initInternal方法大末尾,我们看到了StandardServer获取到了他内部所有Service然后调用每个Serviceinit方法。那么这个service数组里面都包含了哪些service呢。其实在调用init之前这个service数组就已经初始化好了,那么是在哪里初始化的呢?大家应该还记得上面的load方法中有个类叫做Digester,相关的代码Digester digester = createStartDigester();,这个类在createStartDigester方法中通过解析server.xml文件,不仅来生成指定对象,更生成了不同对象之间的依赖关系,在这个方法内部,就将server内部的service全部都初始化了,其实StandardServer根据server.xml的格式默认只有一个service,他的指定实现类就是StandardService,关于digester这个类,有机会可以单独写一篇文章讲解下使用方法。

    我们现在知道了,在StandardServerinit方法中他调用了StandardServiceinit方法。我们继续查看StandardServiceinit方法。

    StandardServer类似,调用init方法实际上是调用了initInternal方法。

      /**
     * Invoke a pre-startup initialization. This is used to allow connectors
     * to bind to restricted ports under Unix operating environments.
     */
    @Override
    protected void initInternal() throws LifecycleException {
    
        super.initInternal();
        //如果service内部的container为空那么就初始化
        if (container != null) {
            container.init();
        }
    
        // Initialize any Executors
    	//初始化executor,事实上在Service中代码走到这里的时候,findExecutors会返回空数组,这里的代码是不会执行的。
        for (Executor executor : findExecutors()) {
            if (executor instanceof LifecycleMBeanBase) {
                ((LifecycleMBeanBase) executor).setDomain(getDomain());
            }
            executor.init();
        }
    
        // Initialize our defined Connectors
    	//初始化 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);
                }
            }
        }
    }
    

    StandardServiceinit方法中可以看出init方法主要还是将Service内部所有的connectors全部轮流调用init方法,是不是感觉很熟悉。是的!StandardService内部所有的connectors正是在server.xml中定义的。那么默认的就是有两个了,分别对应处理http和ajp请求,我们加点代码打印下看看是不是这样。

    测试代码
     // Initialize our defined Connectors
        synchronized (connectorsLock) {
            for (Connector connector : connectors) {
                try {
                    System.out.println("connector名称:"+connector.toString());
                    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);
                }
            }
        }
    

    最终输出如下

    connector名称:Connector[HTTP/1.1-8080]
    connector名称:Connector[AJP/1.3-8009]
    

    好了,我们继续看connectorinit方法,需要注意的是,类似server,service都是有指定标准实现类的,而connector是没有standardconnector这种实现类的,这主要是因为connector根据不同的协议是有多个对应实现的,来一起看connectorinit方法。

     @Override
    protected void initInternal() throws LifecycleException {
    
        super.initInternal();
    
        // Initialize adapter
        adapter = new CoyoteAdapter(this);
        protocolHandler.setAdapter(adapter);
    
        // Make sure parseBodyMethodsSet has a default
        if( null == parseBodyMethodsSet ) {
            setParseBodyMethods(getParseBodyMethods());
        }
    
        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
        mapperListener.init();
    }
    

    你会发现,和StandardService类似,Connector也实现LifeCycle接口,也实现了initInternal方法。

    现在大家肯定都有疑问,为什么都要实现Lifecycle这个接口,都要实现initInternal方法,这个这里先说一下。Lifecycle是管理所有tomcat组件的接口,只要这个组件实现了Lifecycle这个接口,那么这个组件就可以随着tomcat启动而启动,随着tomcat停止而停止,一句话就是通过实现这个接口,tomcat可以统一管理所有组件的启动和停止,而组件启动的时候肯定会做一些通用的方法,所以有了init方法,而每个组件又会做一些自己的特有事情,所以init方法中又调用了initInternal方法来让每个组件自己去实现自己特有的初始化事情,这种其实是一种设计模式,叫做模版设计模式,关于LifeCycle和tomcat中常用的设计模式我们会单独写文章来说明,这里只是提一下,方便大家理解。

    connectorinitInternal方法中,可以看到除了一些常规的方法以外,有两个方法需要关注一下,一个是protocolHandler.init(),另外个就是mapperListener.init(),而我们主要看一些关键组件,所以在这里就不讲解mapperListener.init(),有兴趣的可以自行查看,提示:这个是一个监听器,监听的是容器内部的映射关系变化,我们主要看protocolHandler.init()

    先看protocolHandler这个东西是哪个类的实例,找到初始化的地方,查看Connector的构造函数,可以看到:

        public Connector(String protocol) {
        setProtocol(protocol);
        // Instantiate protocol handler
        try {
            Class<?> clazz = Class.forName(protocolHandlerClassName);
            this.protocolHandler = (ProtocolHandler) clazz.newInstance();
        } catch (Exception e) {
            log.error(sm.getString(
                    "coyoteConnector.protocolHandlerInstantiationFailed"), e);
        }
    }
    

    实际上Connector是在解析server.xml的时候实例化的,在<connector>标签上可以指定很多属性,其中就包含该connector是哪个类的实例。

    查看protocolHandlerClassName可以看到

        /**
     * Coyote Protocol handler class name.
     * Defaults to the Coyote HTTP/1.1 protocolHandler.
     */
    protected String protocolHandlerClassName =
        "org.apache.coyote.http11.Http11Protocol";
    

    可以看出如果是处理http请求的Connector在init的时候,调用的是 org.apache.coyote.http11.Http11Protocol这个类的init方法,那我们继续查看类Http11Protocol这个类的init方法。
    可以查看Http11Protocol的继承类图:

    Http11Protocol本身没有init方法,我们查看其父类,可以在类AbstractProtocol中找到init方法,如下:

    @Override
    public void init() throws Exception {
    	//其他代码
      	...
        try {
            endpoint.init();
        } catch (Exception ex) {
            getLog().error(sm.getString("abstractProtocolHandler.initError",
                    getName()), ex);
            throw ex;
        }
    }
    

    可以看到最后调用了endpoint.init(),这个endPoint指向的是哪个类呢?其实我们之前看到过,在Http11Protocol的构造函数中有如下代码:

    public Http11Protocol() {
        endpoint = new JIoEndpoint();
        cHandler = new Http11ConnectionHandler(this);
        ((JIoEndpoint) endpoint).setHandler(cHandler);
        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
    }
    

    所以最后又调用了JIoEndpointinit方法,我们先查看下JIoEndpoint,可以发现其中并没有init方法,查看其父类AbstractEndpoint,发现有init方法,方法如下:

     public final void init() throws Exception {
        testServerCipherSuitesOrderSupport();
        if (bindOnInit) {
            bind();
            bindState = BindState.BOUND_ON_INIT;
        }
    }
    

    其中bind方法是个抽象方法,查看其子实现在类JIoEndpoint中,

    @Override
    public void bind() throws Exception {
    
        // Initialize thread count defaults for acceptor
    	//为acceptor 初始化线程数量
        if (acceptorThreadCount == 0) {
            acceptorThreadCount = 1;
        }
        // Initialize maxConnections
    	//初始化最大连接数
        if (getMaxConnections() == 0) {
            // User hasn't set a value - use the default
            setMaxConnections(getMaxThreadsExecutor(true));
        }
    
    	//如果线程工厂类为空,初始化
        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;
            }
        }
    
    }
    

    可以看出这个这个init方法就是初始化了几个比较重要的属性,包括线程数量,线程最大链接数,线程工厂类以及接收请求的线程。

    到这里我们终于把connectorinit方法查看完毕。

    那么我们继续回到StandServer内部的init方法

     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);
                }
            }
        }
    

    你会发现,connector初始化完毕以后,StandardServerinit方法也调用结束了,也就是说load方法到这里就结束了。

  • 相关阅读:
    你的DNN站点慢了么?
    SQL锁的应用与描述之二
    自动完成输入框错位
    网页插入flash代码以及技巧
    使用T_SQL脚本创建SQLServer2000后台计划作业任务
    SQL Server实用经典例句之二
    中缀表达式转后缀表达式
    spring cloud config server SSH配置 git private key方式
    vim 技巧一
    jquery 监控文本框键盘事件(回车事件),附常用keycode值。
  • 原文地址:https://www.cnblogs.com/coldridgeValley/p/5631610.html
Copyright © 2020-2023  润新知