如 上一篇文章 所见 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 方法: