tomcat源码分析—生命周期及容器初始化过程

Tomcat服务器是由一系列可配置的组件构成的,其中核心组件是Catalina Servlet,它是最顶层组件。

Tomcat的各组件是在server.xml(CATALINA_HOME\conf\server.xml)配置的 ,tomcat启动是会读取server.xml,并且从中解析出一些对象,下面是一个不太完整的server.xml配置文件

<Server port="8006" shutdown="SHUTDOWN">
    <!-- Security listener. Documentation at /docs/config/listeners.html
   <Listener className="org.apache.catalina.security.SecurityListener" />  -->
    <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
    <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="catelina">
        <!--The connectors can use a shared executor, you can define one or more named thread pools -->
        <!-- <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4"/> -->
        <!-- A "Connector" using the shared thread pool -->
        <!-- <Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> -->
        <Connector port="8080" redirectPort="443" acceptCount="100" connectionTimeout="2000" />
        <Connector port="443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS" keystoreFile="/usr/certs/keystore.pfx" keystoreType="PKCS12"
               keystorePass="21413631"/>
        <Connector port="8009" enableLookups="false" redirectPort="443" protocol="AJP/1.3" />
        <Engine name="catelina" 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">
                <Context path="" docBase="web" debug="0" reloadable="true"/>
            </Host>
            <Host name="www.qq158.com" appBase="/usr/local/tomcatdata/webapps" unpackWARs="true" autoDeploy="true">
                <Context path="" docBase="wapi" debug="0" reloadable="true"/>
                <Valve className="org.apache.catalina.valves.AccessLogValve" fileDateFormat="yyyy-MM-dd" pattern="%h %l %u %t %r %s %b %{Referer}i %{User-Agent}i %{x-jphone-uid}i %{x-up-subno}i" prefix="localhost_access_log." suffix=".txt" directory="/xvdb/logs/logwapi" condition="drop"/>
            </Host>
        </Engine>
    </Service>
</Server>

可以看出tomcat的一些对象,比如 Server,Listener,Service,Connector,Engine ,Realm,Host,Context,Value等,

其中有很多对象是容器,比如Server、Service、Connector等,这些容器都具有新建,初始化,启动(start),停止(stop),销毁(destroy)等状态,

TOMCAT生命周期类接口设计

下图是Tomcat涉及生命周期管理的主要类

                         图1

  • Lifecycle:定义了容器生命周期、容器状态转换及容器状态迁移事件的监听器注册和移除等主要接口;
  • LifecycleBase:作为Lifecycle接口的抽象实现类,运用抽象模板模式将所有容器的生命周期及状态转换衔接起来,此外还提供了生成LifecycleEvent事件的接口;
  • LifecycleSupport:提供有关LifecycleEvent事件的监听器注册、移除,并且使用经典的监听器模式,实现事件生成后触达监听器的实现;
  • MBeanRegistration:Java JMX框架提供的注册MBean的接口,引入此接口是为了便于使用JMX提供的管理功能;
  • LifecycleMBeanBase:Tomcat提供的对MBeanRegistration的抽象实现类,运用抽象模板模式将所有容器统一注册到JMX;

此外,ContainerBase、StandardServer、StandardService、WebappLoader、Connector、StandardContext、StandardEngine、StandardHost、StandardWrapper等容器都继承了LifecycleMBeanBase,因此这些容器都具有了同样的生命周期并可以通过JMX进行管理。

java管理程序扩展(java management extensions,简称JMX),是一个可以为Java应用程序或系统植入远程管理功能的框架,  java中常用Console、VisualVM来查看和远程管理构件(MBean)

Tomcat容器状态

tomcat的容器状态有12中,具体定义可参见枚举  org.apache.catalina.LifecycleState 。

每个容器由于继承自LifecycleBase,当容器状态发生变化时,都会调用 fireLifecycleEvent 方法,生成LifecycleEvent,并且交由此容器的事件监听器处理。

    /**
     * Allow sub classes to fire {@link Lifecycle} events.
     *
     * @param type  Event type
     * @param data  Data associated with event.
     */
    protected void fireLifecycleEvent(String type, Object data) {
        lifecycle.fireLifecycleEvent(type, data);
    }

每个容器在新建、初始化、启动,销毁,被添加到父容器的过程中都会调用父类LifecycleBase的addLifecycleListener方法,所有容器的起始状态为 LifecycleState.NEW

实际的处理过程都是由定义的 LifecycleSupport 来完成的。

 

容器生命周期

 所有容器的转态转换由上到下进行的,即先执行父容器的状态转换及相关操作,然后再执行子容器的转态转换,这个过程是层层迭代执行的

比如,对于Server容器,在tomcat中具体实现是StandardServer,它的子容器是Service,  StandardServer的初始化方法如下: 

    /**
     * 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();

        // 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
        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")) {
                                    ExtensionValidator.addSystemResource(f);
                                }
                            } catch (URISyntaxException e) {
                                // Ignore
                            } catch (IOException e) {
                                // Ignore
                            }
                        }
                    }
                }
                cl = cl.getParent();
            }
        }
        // Initialize our defined Services
        for (int i = 0; i < services.length; i++) {
            services[i].init();
        }
    }

  

容器初始化

 每个容器的init方法是自身初始化的入口,其初始化过程如下图

 

                      图2

图中所说的具体容器,实际就是LifecycleBase的具体实现类,可参见 图1

根据tomcat源码分析,初始化过程步骤如下:

tomcat启动时,最先会调用方调用Server的init方法,即server容器父类LifecycleBase的init方法,LifecycleBase的init方法主要完成一些所有容器公共抽象出来的动作;

LifecycleBase使用了设计模式中的模版方法模式, init方法调用具体容器的initInternal方法实现,此initInternal方法用于对容器本身真正的初始化; initInternal方法实在LifecycleBase的子类中来实现的,

LifecycleBase中的的init方法

    @Override
    public final synchronized void init() throws LifecycleException {
        if (!state.equals(LifecycleState.NEW)) {
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        }

        try {
            setStateInternal(LifecycleState.INITIALIZING, null, false);
            initInternal();
            setStateInternal(LifecycleState.INITIALIZED, null, false);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            setStateInternal(LifecycleState.FAILED, null, false);
            throw new LifecycleException(
                    sm.getString("lifecycleBase.initFail",toString()), t);
        }
    }

   protected abstract void initInternal() throws LifecycleException;

比如LifecycleBase的子类StandardService中实现的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(); // 调用父类LifecycleMBeanBase的initInternal方法实现,此initInternal方法用于将容器托管到JMX

        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();  //调用子容器的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);
                    }
                }
            }
        }
    }

 具体容器的initInternal方法调用父类LifecycleMBeanBase的initInternal方法实现,此initInternal方法用于将容器托管到JMX,便于运维管理;

    /**
     * Sub-classes wishing to perform additional initialization should override
     * this method, ensuring that super.initInternal() is the first call in the overriding method.
     */
    @Override
    protected void initInternal() throws LifecycleException {

        // If oname is not null then registration has already happened via preRegister().
        if (oname == null) {
            mserver = Registry.getRegistry(null, null).getMBeanServer();
            oname = register(this, getObjectNameKeyProperties());
        }
    }

LifecycleMBeanBase的initInternal方法调用自身的register方法,将容器作为MBean注册到MBeanServer; 

    protected final ObjectName register(Object obj, String objectNameKeyProperties) {

        // Construct an object name with the right domain
        StringBuilder name = new StringBuilder(getDomain()).append(":").append(objectNameKeyProperties);
 
        ObjectName on = null;

        try {
            on = new ObjectName(name.toString());
            Registry.getRegistry(null, null).registerComponent(obj, on, null);
        } catch (MalformedObjectNameException e) {
            log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name), e);
        } catch (Exception e) {
            log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name), e);
        }

        return on;
    }
    public void registerComponent(Object bean, ObjectName oname, String type) throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("Managed= " + oname);
        }

        if (bean == null) {
            log.error("Null component " + oname);
            return;
        }

        try {
            if (type == null) {
                type = bean.getClass().getName();
            }

            ManagedBean managed = findManagedBean(null, bean.getClass(), type);

            // The real mbean is created and registered
            DynamicMBean mbean = managed.createMBean(bean);

            if (getMBeanServer().isRegistered(oname)) {
                if (log.isDebugEnabled()) {
                    log.debug("Unregistering existing component " + oname);
                }
                getMBeanServer().unregisterMBean(oname);
            }

            getMBeanServer().registerMBean(mbean, oname);
        } catch (Exception ex) {
            log.error("Error registering " + oname, ex);
            throw ex;
        }
    }

 

 

容器如果有子容器,会调用子容器的init方法;比如 StandardService  中的 子容器有Connector,会依次对所有的Connector调用init方法。

容器初始化完毕,LifecycleBase会将容器的状态更改为初始化完毕,即LifecycleState.INITIALIZED。

 

【总结】

Tomcat通过将内部所有组件都抽象为容器,为容器提供统一的生命周期管理,各个子容器只需要关心各自的具体实现

 

【tomcat8开启JMX】

修改tomcat中的文件 /bin/catalina.sh
找到下面的内容
# Uncomment the following line to make the umask available when using the
# org.apache.catalina.security.SecurityListener
#JAVA_OPTS="$JAVA_OPTS -Dorg.apache.catalina.security.SecurityListener.UMASK=`umask`"

CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote
-Djava.rmi.server.hostname=112.124.63.188
-Dcom.sun.management.jmxremote.port=8082
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false"

# ----- Execute The Requested Command -----------------------------------------

ip是你要监控的tomcat所在服务器的ip地址
port端口号,开启的监控端口号。
sll:是否开启ssl链接
authenticate, 是否需要授权,false表示不需要用户名和密码


也可以添加授权的的配置:
同样修改 /bin/catalina.sh

CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote
-Djava.rmi.server.hostname=112.124.63.188
-Dcom.sun.management.jmxremote.port=8082
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false"
-Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password
-Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access”

authenticate,true 开启鉴权功能
access.file,权限文件路径
password.file,密码文件路径

将 JAVA_HOME/jre/lib/management 下面的 jmxremote.access 和jmxremote.password.template 拷贝到 tomcat下的conf目录中
1:将jmxremote.password.template文件名修改为jmxremote.password,在此文件尾部添加用户名和密码

2:修改两个文件的权限
chmod 600 jmxremote.access
chmod 600 jmxremote.password

3:修改jmxremote.access文件,将文件最后两行显示【monitorRole和controlRole】的注释取消,

其中monitorRole为只拥有只读权限的角色,controlRole有更高权限:读写等。

 

posted @ 2016-10-19 19:03  南极山  阅读(562)  评论(0)    收藏  举报