专栏名称: 预流
敲代码的
目录
相关文章推荐
51好读  ›  专栏  ›  预流

Tomcat 7 中的 JMX 使用(二)Dynamic MBean

预流  · 掘金  ·  · 2018-03-11 07:53

正文

Tomcat 7 中的 JMX 使用(二)Dynamic MBean

上一篇文章 所见 Standard MBean 在 Tomcat 源码中的例子并不多,在 jconsole 中所看到的大量 MBean(如 Catalina 下的 Connector、Engine、Server、Service 等),实际上是动态 MBean(Dynamic MBean)。本文主要讲述 Tomcat 7 中如何通过动态 MBean 的方式构造 MBean 的。

接触过动态 MBean 的朋友一定知道,它的实例肯定要实现一个接口,即 javax.management.DynamicMBean 。实现这个接口就意味着同时要实现它下面的6个方法:

    public Object getAttribute(String attribute) throws AttributeNotFoundException,MBeanException, ReflectionException; 

    public void setAttribute(Attribute attribute) throws AttributeNotFoundException,InvalidAttributeValueException, MBeanException, ReflectionException ; 
    
    public AttributeList getAttributes(String[] attributes);
    
    public AttributeList setAttributes(AttributeList attributes);
    
    public Object invoke(String actionName, Object params[], String signature[]) throws MBeanException, ReflectionException ;    

    public MBeanInfo getMBeanInfo();

通过实现这个通用接口,jvm 允许程序在运行时获取和设置 MBean 公开的属性和调用 MBean 上公开的方法。

上面简要介绍了动态 MBean 的实现方式,Tomcat 中的实际情况比这个要复杂,因为要生成很多种 MBean,如果每种类型都用代码写一个 MBean 就失去了动态 MBean 的威力,Tomcat 7 中实际是通过配置文件(即每个组件所在的包下面的 mbeans-descriptors.xml )结合通用的动态 MBean( org.apache.tomcat.util.modeler.BaseModelMBean )、描述 MBean 配置信息的 org.apache.tomcat.util.modeler.ManagedBean 来简化 MBean 的构造。(实际就是用动态 MBean 实现了模型 MBean 的功能)

一般情况下动态 MBean 的产生分为两个阶段:

  • 一、加载 org.apache.tomcat.util.modeler.ManagedBean 对象
  • 二、注册 MBean 实例

加载 org.apache.tomcat.util.modeler.ManagedBean 对象

在 Tomcat 启动时加载的配置文件 server.xml 中有这么一行配置:

  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

因此在 Tomcat 启动时将加载这个类,在这个类中有一个静态成员变量 registry:

    /**
     * The configuration information registry for our managed beans.
     */
    protected static Registry registry = MBeanUtils.createRegistry();

也就是说类加载时 registry 就会获得 Registry 类的实例,这个 Registry 类很重要,在 MBean 的构造过程中将会多次涉及这个类里的方法。先看看 MBeanUtils.createRegistry() 方法:

     1	    /**
     2	     * Create and configure (if necessary) and return the registry of
     3	     * managed object descriptions.
     4	     */
     5	    public static synchronized Registry createRegistry() {
     6	
     7	        if (registry == null) {
     8	            registry = Registry.getRegistry(null, null);
     9	            ClassLoader cl = MBeanUtils.class.getClassLoader();
    10	
    11	            registry.loadDescriptors("org.apache.catalina.mbeans",  cl);
    12	            registry.loadDescriptors("org.apache.catalina.authenticator", cl);
    13	            registry.loadDescriptors("org.apache.catalina.core", cl);
    14	            registry.loadDescriptors("org.apache.catalina", cl);
    15	            registry.loadDescriptors("org.apache.catalina.deploy", cl);
    16	            registry.loadDescriptors("org.apache.catalina.loader", cl);
    17	            registry.loadDescriptors("org.apache.catalina.realm", cl);
    18	            registry.loadDescriptors("org.apache.catalina.session", cl);
    19	            registry.loadDescriptors("org.apache.catalina.startup", cl);
    20	            registry.loadDescriptors("org.apache.catalina.users", cl);
    21	            registry.loadDescriptors("org.apache.catalina.ha", cl);
    22	            registry.loadDescriptors("org.apache.catalina.connector", cl);
    23	            registry.loadDescriptors("org.apache.catalina.valves",  cl);
    24	        }
    25	        return (registry);
    26	
    27	    }

注意第8行 Registry.getRegistry(null, null) 方法的调用,看下它的实现就会发现返回的实际是 Registry 类的静态变量,这种调用后面会多次看到。接着还需要看一下 MBeanUtils 类的 registry 的定义:

    /**
     * The configuration information registry for our managed beans.
     */
    private static Registry registry = createRegistry();

因为此时 MBeanUtils 类还没在JVM里面加载过,它的成员变量 registry 为 null ,所以会调用 Registry.getRegistry(null, null) 方法构造对象,接下来会多次调用 loadDescriptors 方法,以下面这一句代码为例:

registry.loadDescriptors("org.apache.catalina.connector", cl);

这里 org.apache.catalina.connector 实际上是一个 package 的路径全名,看下 loadDescriptors 方法:

     1	    /** Lookup the component descriptor in the package and
     2	     * in the parent packages.
     3	     *
     4	     * @param packageName
     5	     */
     6	    public void loadDescriptors( String packageName, ClassLoader classLoader  ) {
     7	        String res=packageName.replace( '.', '/');
     8	
     9	        if( log.isTraceEnabled() ) {
    10	            log.trace("Finding descriptor " + res );
    11	        }
    12	
    13	        if( searchedPaths.get( packageName ) != null ) {
    14	            return;
    15	        }
    16	        String descriptors=res + "/mbeans-descriptors.ser";
    17	
    18	        URL dURL=classLoader.getResource( descriptors );
    19	
    20	        if( dURL == null ) {
    21	            descriptors=res + "/mbeans-descriptors.xml";
    22	            dURL=classLoader.getResource( descriptors );
    23	        }
    24	        if( dURL == null ) {
    25	            return;
    26	        }
    27	
    28	        log.debug( "Found " + dURL);
    29	        searchedPaths.put( packageName,  dURL );
    30	        try {
    31	            if( descriptors.endsWith(".xml" ))
    32	                loadDescriptors("MbeansDescriptorsDigesterSource", dURL, null);
    33	            else
    34	                loadDescriptors("MbeansDescriptorsSerSource", dURL, null);
    35	            return;
    36	        } catch(Exception ex ) {
    37	            log.error("Error loading " + dURL);
    38	        }
    39	
    40	        return;
    41	    }

第13到15行是先在 Registry 类的缓存 searchedPaths 中查找是否已经加载了该 package 所对应的配置文件,如果没有在第16到18行会在该包路径下面查找是否有 mbeans-descriptors.ser 文件,没有则在第20到23行查找同路径下的 mbeans-descriptors.xml 文件。找到之后在第29行放入缓存 searchedPaths 。我们既然以 org.apache.catalina.connector 为例,则找到的是该路径下的 mbeans-descriptors.xml 。所以会接着执行第32行 loadDescriptors("MbeansDescriptorsDigesterSource", dURL, null)

    private void loadDescriptors(String sourceType, Object source,
            String param) throws Exception {
        load(sourceType, source, param);
    }

这段代码会执行 load 方法:

     1	    public List<ObjectName> load( String sourceType, Object source,
     2	            String param) throws Exception {
     3	        if( log.isTraceEnabled()) {
     4	            log.trace("load " + source );
     5	        }
     6	        String location=null;
     7	        String type=null;
     8	        Object inputsource=null;
     9	
    10	        if( source instanceof URL ) {
    11	            URL url=(URL)source;
    12	            location=url.toString();
    13	            type=param;
    14	            inputsource=url.openStream();
    15	            if( sourceType == null ) {
    16	                sourceType = sourceTypeFromExt(location);
    17	            }
    18	        } else if( source instanceof File ) {
    19	            location=((File)source).getAbsolutePath();
    20	            inputsource=new FileInputStream((File)source);            
    21	            type=param;
    22	            if( sourceType == null ) {
    23	                sourceType = sourceTypeFromExt(location);
    24	            }
    25	        } else if( source instanceof InputStream ) {
    26	            type=param;
    27	            inputsource=source;
    28	        } else if( source instanceof Class<?> ) {
    29	            location=((Class<?>)source).getName();
    30	            type=param;
    31	            inputsource=source;
    32	            if( sourceType== null ) {
    33	                sourceType="MbeansDescriptorsIntrospectionSource";
    34	            }
    35	        }
    36	        
    37	        if( sourceType==null ) {
    38	            sourceType="MbeansDescriptorsDigesterSource";
    39	        }
    40	        ModelerSource ds=getModelerSource(sourceType);
    41	        List<ObjectName> mbeans =
    42	            ds.loadDescriptors(this, type, inputsource);
    43	
    44	        return mbeans;
    45	    }

第10到35行说穿是是为该方法适配多种数据源类型给 inputsource 变量赋上一个输入流。第40行会根据 sourceType 构造一个 ModelerSource 对象:

    private ModelerSource getModelerSource( String type )
            throws Exception
    {
        if( type==null ) type="MbeansDescriptorsDigesterSource";
        if( type.indexOf( ".") < 0 ) {
            type="org.apache.tomcat.util.modeler.modules." + type;
        }

        Class<?> c = Class.forName(type);
        ModelerSource ds=(ModelerSource)c.newInstance();
        return ds;
    }

上面看到 sourceType 传入的值是 MbeansDescriptorsDigesterSource 。所以 getModelerSource 方法最后返回的是 org.apache.tomcat.util.modeler.modules.MbeansDescriptorsDigesterSource 类的一个实例。

最后执行该 ModelerSource 对象的 loadDescriptors(this, type, inputsource) 方法,因为该方法是一个抽象方法,所以这里实际执行的 org.apache.tomcat.util.modeler.modules.MbeansDescriptorsDigesterSource 类的 loadDescriptors 方法:







请到「今天看啥」查看全文