Tomcat7中的JMX使用(二)Dynamic MBean
2016-05-29 00:52
615 查看
如上一篇文章所见Standard MBean在Tomcat的例子并不多,在jconsole中所看到的大量MBean(如Catalina下的Connector、Engine、Server、Service等),实际上是动态MBean(Dynamic
MBean)。本文主要讲述Tomcat7中如何通过动态MBean的方式构造MBean的。
接触过动态MBean的朋友一定知道,它的实例肯定要实现一个接口,即javax.management.DynamicMBean。实现这个接口就意味着同时要实现它下面的6个方法:
Java代码
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的威力,Tomcat7中实际是通过配置文件(即每个组件所在的包下面的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实例。
Xml代码
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
因此在Tomcat启动时将加载这个类,在这个类中有一个静态成员变量registry:
Java代码
/**
* The configuration information registry for our managed beans.
*/
protected static Registry registry = MBeanUtils.createRegistry();
也就是说类加载时registry就会获得Registry类的实例,这个Registry类很重要,在MBean的构造过程中将会多次涉及这个类里的方法。先看看MBeanUtils.createRegistry()方法:
Java代码
/**
* Create and configure (if necessary) and return the registry of
* managed object descriptions.
*/
public static synchronized Registry createRegistry() {
if (registry == null) {
registry = Registry.getRegistry(null, null);
ClassLoader cl = MBeanUtils.class.getClassLoader();
registry.loadDescriptors("org.apache.catalina.mbeans", cl);
registry.loadDescriptors("org.apache.catalina.authenticator", cl);
registry.loadDescriptors("org.apache.catalina.core", cl);
registry.loadDescriptors("org.apache.catalina", cl);
registry.loadDescriptors("org.apache.catalina.deploy", cl);
registry.loadDescriptors("org.apache.catalina.loader", cl);
registry.loadDescriptors("org.apache.catalina.realm", cl);
registry.loadDescriptors("org.apache.catalina.session", cl);
registry.loadDescriptors("org.apache.catalina.startup", cl);
registry.loadDescriptors("org.apache.catalina.users", cl);
registry.loadDescriptors("org.apache.catalina.ha", cl);
registry.loadDescriptors("org.apache.catalina.connector", cl);
registry.loadDescriptors("org.apache.catalina.valves", cl);
}
return (registry);
}
注意第8行Registry.getRegistry(null, null)方法的调用,看下它的实现就会发现返回的实际是Registry类的静态变量,这种调用后面会多次看到。接着还需要看一下MBeanUtils类的registry的定义:
Java代码
/**
* The configuration information registry for our managed beans.
*/
private static Registry registry = createRegistry();
因为此时MBeanUtils类还没在JVM里面加载过,它的成员变量registry为null,所以会调用Registry.getRegistry(null, null)方法构造对象,接下来会多次调用loadDescriptors方法,以下面这一句代码为例:
Java代码
registry.loadDescriptors("org.apache.catalina.connector", cl);
这里org.apache.catalina.connector实际上是一个package的路径全名,看下loadDescriptors方法:
Java代码
/** Lookup the component descriptor in the package and
* in the parent packages.
*
* @param packageName
*/
public void loadDescriptors( String packageName, ClassLoader classLoader ) {
String res=packageName.replace( '.', '/');
if( log.isTraceEnabled() ) {
log.trace("Finding descriptor " + res );
}
if( searchedPaths.get( packageName ) != null ) {
return;
}
String descriptors=res + "/mbeans-descriptors.ser";
URL dURL=classLoader.getResource( descriptors );
if( dURL == null ) {
descriptors=res + "/mbeans-descriptors.xml";
dURL=classLoader.getResource( descriptors );
}
if( dURL == null ) {
return;
}
log.debug( "Found " + dURL);
searchedPaths.put( packageName, dURL );
try {
if( descriptors.endsWith(".xml" ))
loadDescriptors("MbeansDescriptorsDigesterSource", dURL, null);
else
loadDescriptors("MbeansDescriptorsSerSource", dURL, null);
return;
} catch(Exception ex ) {
log.error("Error loading " + dURL);
}
return;
}
第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):
Java代码
private void loadDescriptors(String sourceType, Object source,
String param) throws Exception {
load(sourceType, source, param);
}
这段代码会执行load方法:
Java代码
public List<ObjectName> load( String sourceType, Object source,
String param) throws Exception {
if( log.isTraceEnabled()) {
log.trace("load " + source );
}
String location=null;
String type=null;
Object inputsource=null;
if( source instanceof URL ) {
URL url=(URL)source;
location=url.toString();
type=param;
inputsource=url.openStream();
if( sourceType == null ) {
sourceType = sourceTypeFromExt(location);
}
} else if( source instanceof File ) {
location=((File)source).getAbsolutePath();
inputsource=new FileInputStream((File)source);
type=param;
if( sourceType == null ) {
sourceType = sourceTypeFromExt(location);
}
} else if( source instanceof InputStream ) {
type=param;
inputsource=source;
} else if( source instanceof Class<?> ) {
location=((Class<?>)source).getName();
type=param;
inputsource=source;
if( sourceType== null ) {
sourceType="MbeansDescriptorsIntrospectionSource";
}
}
if( sourceType==null ) {
sourceType="MbeansDescriptorsDigesterSource";
}
ModelerSource ds=getModelerSource(sourceType);
List<ObjectName> mbeans =
ds.loadDescriptors(this, type, inputsource);
return mbeans;
}
第10到35行说穿是是为该方法适配多种数据源类型给inputsource变量赋上一个输入流。第40行会根据sourceType构造一个ModelerSource对象:
Java代码
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方法:
Java代码
@Override
public List<ObjectName> loadDescriptors( Registry registry, String type,
Object source) throws Exception {
setRegistry(registry);
setType(type);
setSource(source);
execute();
return mbeans;
}
前三个set方法毋庸多言,关键是最后的execute方法:
Java代码
public void execute() throws Exception {
if (registry == null) {
registry = Registry.getRegistry(null, null);
}
InputStream stream = (InputStream) source;
if (digester == null) {
digester = createDigester();
}
ArrayList<ManagedBean> loadedMbeans = new ArrayList<ManagedBean>();
synchronized (digester) {
// Process the input file to configure our registry
try {
// Push our registry object onto the stack
digester.push(loadedMbeans);
digester.parse(stream);
} catch (Exception e) {
log.error("Error digesting Registry data", e);
throw e;
} finally {
digester.reset();
}
}
Iterator<ManagedBean> iter = loadedMbeans.iterator();
while (iter.hasNext()) {
registry.addManagedBean(iter.next());
}
}
在第3行又看到了前面提到的Registry.getRegistry(null, null)方法,这里就是获取Registry的静态成员的引用。这段方法作用就是对source进行一次Digester解析,如果还不了解Digester解析,可以看看之前Tomcat源码分析的一篇文章:Tomcat7启动分析(三)Digester的使用。注意第18行digester的顶层对象是loadedMbeans,重点看下第9行createDigester()方法的调用:
Java代码
protected static Digester createDigester() {
Digester digester = new Digester();
digester.setNamespaceAware(false);
digester.setValidating(false);
URL url = Registry.getRegistry(null, null).getClass().getResource
("/org/apache/tomcat/util/modeler/mbeans-descriptors.dtd");
digester.register
("-//Apache Software Foundation//DTD Model MBeans Configuration File",
url.toString());
// Configure the parsing rules
digester.addObjectCreate
("mbeans-descriptors/mbean",
"org.apache.tomcat.util.modeler.ManagedBean");
digester.addSetProperties
("mbeans-descriptors/mbean");
digester.addSetNext
("mbeans-descriptors/mbean",
"add",
"java.lang.Object");
digester.addObjectCreate
("mbeans-descriptors/mbean/attribute",
"org.apache.tomcat.util.modeler.AttributeInfo");
digester.addSetProperties
("mbeans-descriptors/mbean/attribute");
digester.addSetNext
("mbeans-descriptors/mbean/attribute",
"addAttribute",
"org.apache.tomcat.util.modeler.AttributeInfo");
......
return digester;
}
上面这段代码其实很长,但绝大部分都是模板代码,理解几句的含义后面代码都很相似。这就是一个xml文件的解析,第13到15行是值在碰到xml文件的mbeans-descriptors节点的子节点mbean时构造一个org.apache.tomcat.util.modeler.ManagedBean对象,第16到17行是读取该节点属性值填充到ManagedBean对象的pojo属性中,第18到21行以ManagedBean对象为入参调用上一段代码分析提到的loadedMbeans对象的add方法。类似的,第23到31行是指在碰到mbeans-descriptors/mbean/attribute节点时构造org.apache.tomcat.util.modeler.AttributeInfo对象,填充pojo属性,并调用父节点构造的对象(即ManagedBean对象)的addAttribute方法。其它代码类似,不再赘述。
接回到上面MbeansDescriptorsDigesterSource类的execute方法第28到31行,在Digester解析完成之后迭代loadedMbeans对象,并调用registry.addManagedBean方法将这些ManagedBean添加到registry中。这样,一次registry.loadDescriptors("org.apache.catalina.connector", cl)调用就会加载该包路径下相对应的ManagedBean对象到Registry类的成员变量中。
下面的时序图列出从GlobalResourcesLifecycleListener类加载其静态成员变量registry到Registry类加载完相应包所对应的ManagedBean的关键方法调用过程:
上面说的是一个ManagedBean的加载过程,但它不是一个MBean,可以把它看作一个描述MBean的配置信息的对象,以前面提到的org.apache.catalina.connector为例,在Tomcat7的默认配置启动后实际上有两个Connector实例,因为在server.xml中配置了两条connector节点:
Xml代码
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
所对应jconsole中会看到两个相应的MBean对象:
但ManageBean实际只是加载了一次。了解了ManagedBean与MBean的对应关系,接下来看看一个MBean是怎么注册到JVM中的。
看过前面Tomcat启动分析的朋友知道容器各组件在启动过程中会相继调用它们的initInternal()、startInternal()两个方法,还是以上面提到的Connector组件为例,Tomcat启动时解析server.xml文件过程中碰到Connector节点配置会构造org.apache.catalina.connector.Connector对象并调用它的initInternal方法:
Java代码
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
在这个方法的开始会调用它的父类org.apache.catalina.util.LifecycleMBeanBase的initInternal方法:
Java代码
private ObjectName oname = null;
protected MBeanServer mserver = null;
/**
* 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());
}
}
先获取MBeanServer的实例,接着调用内部的register方法,将当前对象注册到MBeanServer中,看下register方法:
Java代码
protected final ObjectName register(Object obj,
String objectNameKeyProperties) {
// Construct an object name with the right domain
StringBuilder name = new StringBuilder(getDomain());
name.append(':');
name.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;
}
重点是第14行调用Registry类的registerComponent方法来注册:
Java代码
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(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;
}
}
在第18行根据当前要注册的对象(即Connector对象)的类型查找ManagedBean,沿着这个方法追会发现依次调用了一堆同名的findManagedBean方法,一直到findManagedBean(String name):
Java代码
public ManagedBean findManagedBean(String name) {
// XXX Group ?? Use Group + Type
ManagedBean mb = descriptors.get(name);
if( mb==null )
mb = descriptorsByClass.get(name);
return mb;
}
这段代码意思是依次从Registry类的静态成员变量descriptors、descriptorsByClass中查找相应ManagedBean。那这两个HashMap是什么时候put值进去的呢?答案就在上一部分分析的最后加载ManagedBean时最终调用Registry类的addManagedBean方法:
Java代码
public void addManagedBean(ManagedBean bean) {
// XXX Use group + name
descriptors.put(bean.getName(), bean);
if( bean.getType() != null ) {
descriptorsByClass.put( bean.getType(), bean );
}
}
在上面的registerComponent方法的第21行调用查找到的ManagedBean对象的createMBean方法来获取实际的DynamicMBean对象:
Java代码
public DynamicMBean createMBean(Object instance)
throws InstanceNotFoundException,
MBeanException, RuntimeOperationsException {
BaseModelMBean mbean = null;
// Load the ModelMBean implementation class
if(getClassName().equals(BASE_MBEAN)) {
// Skip introspection
mbean = new BaseModelMBean();
} else {
Class<?> clazz = null;
Exception ex = null;
try {
clazz = Class.forName(getClassName());
} catch (Exception e) {
}
if( clazz==null ) {
try {
ClassLoader cl= Thread.currentThread().getContextClassLoader();
if ( cl != null)
clazz= cl.loadClass(getClassName());
} catch (Exception e) {
ex=e;
}
}
if( clazz==null) {
throw new MBeanException
(ex, "Cannot load ModelMBean class " + getClassName());
}
try {
// Stupid - this will set the default minfo first....
mbean = (BaseModelMBean) clazz.newInstance();
} catch (RuntimeOperationsException e) {
throw e;
} catch (Exception e) {
throw new MBeanException
(e, "Cannot instantiate ModelMBean of class " +
getClassName());
}
}
mbean.setManagedBean(this);
// Set the managed resource (if any)
try {
if (instance != null)
mbean.setManagedResource(instance, "ObjectReference");
} catch (InstanceNotFoundException e) {
throw e;
}
return (mbean);
}
这段代码看起来长,仔细分析实际就是根据ManagedBean对象的getClassName方法返回的值通过反射等方式来构造一个对象返回。而getClassName方法调用的实际就是第1部分提到的Digester解析时构造ManagedBean对象时自动从xml文件中读取并填充的pojo属性className,以现在所说的Connector为例,在mbeans-descriptors.xml中的配置:
Xml代码
<mbean name="CoyoteConnector"
className="org.apache.catalina.mbeans.ConnectorMBean"
description="Implementation of a Coyote connector"
domain="Catalina"
group="Connector"
type="org.apache.catalina.connector.Connector">
所以此时构造返回的是一个org.apache.catalina.mbeans.ConnectorMBean对象。可以看到这个类的继承关系,它的父类是org.apache.catalina.mbeans.ClassNameMBean,它父类的父类就是org.apache.tomcat.util.modeler.BaseModelMBean,从这三种类中可以分别看到通常的动态MBean要实现的6个方法的定义,有兴趣的可以继续研究这些方法的实现,实际上它们都用到了什么所说的ManagedBean对象的相关方法,因为与该MBean要暴露的方法、操作的描述信息都是在加载相应的ManagedBean对象时读取的,所以动态MBean的实现必然也是需要调用它们的。
在上面的registerComponent方法的第30行getMBeanServer().registerMBean( mbean, oname),这就是将该DynamicMBean对象注册到MBeanServer中。
下面的时序图列出从Connector的initInternal方法到注册MBean的关键方法调用过程:
MBean)。本文主要讲述Tomcat7中如何通过动态MBean的方式构造MBean的。
接触过动态MBean的朋友一定知道,它的实例肯定要实现一个接口,即javax.management.DynamicMBean。实现这个接口就意味着同时要实现它下面的6个方法:
Java代码
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的威力,Tomcat7中实际是通过配置文件(即每个组件所在的包下面的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实例。
1.加载org.apache.tomcat.util.modeler.ManagedBean对象
在Tomcat启动时加载的配置文件server.xml中有这么一行配置:Xml代码
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
因此在Tomcat启动时将加载这个类,在这个类中有一个静态成员变量registry:
Java代码
/**
* The configuration information registry for our managed beans.
*/
protected static Registry registry = MBeanUtils.createRegistry();
也就是说类加载时registry就会获得Registry类的实例,这个Registry类很重要,在MBean的构造过程中将会多次涉及这个类里的方法。先看看MBeanUtils.createRegistry()方法:
Java代码
/**
* Create and configure (if necessary) and return the registry of
* managed object descriptions.
*/
public static synchronized Registry createRegistry() {
if (registry == null) {
registry = Registry.getRegistry(null, null);
ClassLoader cl = MBeanUtils.class.getClassLoader();
registry.loadDescriptors("org.apache.catalina.mbeans", cl);
registry.loadDescriptors("org.apache.catalina.authenticator", cl);
registry.loadDescriptors("org.apache.catalina.core", cl);
registry.loadDescriptors("org.apache.catalina", cl);
registry.loadDescriptors("org.apache.catalina.deploy", cl);
registry.loadDescriptors("org.apache.catalina.loader", cl);
registry.loadDescriptors("org.apache.catalina.realm", cl);
registry.loadDescriptors("org.apache.catalina.session", cl);
registry.loadDescriptors("org.apache.catalina.startup", cl);
registry.loadDescriptors("org.apache.catalina.users", cl);
registry.loadDescriptors("org.apache.catalina.ha", cl);
registry.loadDescriptors("org.apache.catalina.connector", cl);
registry.loadDescriptors("org.apache.catalina.valves", cl);
}
return (registry);
}
注意第8行Registry.getRegistry(null, null)方法的调用,看下它的实现就会发现返回的实际是Registry类的静态变量,这种调用后面会多次看到。接着还需要看一下MBeanUtils类的registry的定义:
Java代码
/**
* The configuration information registry for our managed beans.
*/
private static Registry registry = createRegistry();
因为此时MBeanUtils类还没在JVM里面加载过,它的成员变量registry为null,所以会调用Registry.getRegistry(null, null)方法构造对象,接下来会多次调用loadDescriptors方法,以下面这一句代码为例:
Java代码
registry.loadDescriptors("org.apache.catalina.connector", cl);
这里org.apache.catalina.connector实际上是一个package的路径全名,看下loadDescriptors方法:
Java代码
/** Lookup the component descriptor in the package and
* in the parent packages.
*
* @param packageName
*/
public void loadDescriptors( String packageName, ClassLoader classLoader ) {
String res=packageName.replace( '.', '/');
if( log.isTraceEnabled() ) {
log.trace("Finding descriptor " + res );
}
if( searchedPaths.get( packageName ) != null ) {
return;
}
String descriptors=res + "/mbeans-descriptors.ser";
URL dURL=classLoader.getResource( descriptors );
if( dURL == null ) {
descriptors=res + "/mbeans-descriptors.xml";
dURL=classLoader.getResource( descriptors );
}
if( dURL == null ) {
return;
}
log.debug( "Found " + dURL);
searchedPaths.put( packageName, dURL );
try {
if( descriptors.endsWith(".xml" ))
loadDescriptors("MbeansDescriptorsDigesterSource", dURL, null);
else
loadDescriptors("MbeansDescriptorsSerSource", dURL, null);
return;
} catch(Exception ex ) {
log.error("Error loading " + dURL);
}
return;
}
第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):
Java代码
private void loadDescriptors(String sourceType, Object source,
String param) throws Exception {
load(sourceType, source, param);
}
这段代码会执行load方法:
Java代码
public List<ObjectName> load( String sourceType, Object source,
String param) throws Exception {
if( log.isTraceEnabled()) {
log.trace("load " + source );
}
String location=null;
String type=null;
Object inputsource=null;
if( source instanceof URL ) {
URL url=(URL)source;
location=url.toString();
type=param;
inputsource=url.openStream();
if( sourceType == null ) {
sourceType = sourceTypeFromExt(location);
}
} else if( source instanceof File ) {
location=((File)source).getAbsolutePath();
inputsource=new FileInputStream((File)source);
type=param;
if( sourceType == null ) {
sourceType = sourceTypeFromExt(location);
}
} else if( source instanceof InputStream ) {
type=param;
inputsource=source;
} else if( source instanceof Class<?> ) {
location=((Class<?>)source).getName();
type=param;
inputsource=source;
if( sourceType== null ) {
sourceType="MbeansDescriptorsIntrospectionSource";
}
}
if( sourceType==null ) {
sourceType="MbeansDescriptorsDigesterSource";
}
ModelerSource ds=getModelerSource(sourceType);
List<ObjectName> mbeans =
ds.loadDescriptors(this, type, inputsource);
return mbeans;
}
第10到35行说穿是是为该方法适配多种数据源类型给inputsource变量赋上一个输入流。第40行会根据sourceType构造一个ModelerSource对象:
Java代码
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方法:
Java代码
@Override
public List<ObjectName> loadDescriptors( Registry registry, String type,
Object source) throws Exception {
setRegistry(registry);
setType(type);
setSource(source);
execute();
return mbeans;
}
前三个set方法毋庸多言,关键是最后的execute方法:
Java代码
public void execute() throws Exception {
if (registry == null) {
registry = Registry.getRegistry(null, null);
}
InputStream stream = (InputStream) source;
if (digester == null) {
digester = createDigester();
}
ArrayList<ManagedBean> loadedMbeans = new ArrayList<ManagedBean>();
synchronized (digester) {
// Process the input file to configure our registry
try {
// Push our registry object onto the stack
digester.push(loadedMbeans);
digester.parse(stream);
} catch (Exception e) {
log.error("Error digesting Registry data", e);
throw e;
} finally {
digester.reset();
}
}
Iterator<ManagedBean> iter = loadedMbeans.iterator();
while (iter.hasNext()) {
registry.addManagedBean(iter.next());
}
}
在第3行又看到了前面提到的Registry.getRegistry(null, null)方法,这里就是获取Registry的静态成员的引用。这段方法作用就是对source进行一次Digester解析,如果还不了解Digester解析,可以看看之前Tomcat源码分析的一篇文章:Tomcat7启动分析(三)Digester的使用。注意第18行digester的顶层对象是loadedMbeans,重点看下第9行createDigester()方法的调用:
Java代码
protected static Digester createDigester() {
Digester digester = new Digester();
digester.setNamespaceAware(false);
digester.setValidating(false);
URL url = Registry.getRegistry(null, null).getClass().getResource
("/org/apache/tomcat/util/modeler/mbeans-descriptors.dtd");
digester.register
("-//Apache Software Foundation//DTD Model MBeans Configuration File",
url.toString());
// Configure the parsing rules
digester.addObjectCreate
("mbeans-descriptors/mbean",
"org.apache.tomcat.util.modeler.ManagedBean");
digester.addSetProperties
("mbeans-descriptors/mbean");
digester.addSetNext
("mbeans-descriptors/mbean",
"add",
"java.lang.Object");
digester.addObjectCreate
("mbeans-descriptors/mbean/attribute",
"org.apache.tomcat.util.modeler.AttributeInfo");
digester.addSetProperties
("mbeans-descriptors/mbean/attribute");
digester.addSetNext
("mbeans-descriptors/mbean/attribute",
"addAttribute",
"org.apache.tomcat.util.modeler.AttributeInfo");
......
return digester;
}
上面这段代码其实很长,但绝大部分都是模板代码,理解几句的含义后面代码都很相似。这就是一个xml文件的解析,第13到15行是值在碰到xml文件的mbeans-descriptors节点的子节点mbean时构造一个org.apache.tomcat.util.modeler.ManagedBean对象,第16到17行是读取该节点属性值填充到ManagedBean对象的pojo属性中,第18到21行以ManagedBean对象为入参调用上一段代码分析提到的loadedMbeans对象的add方法。类似的,第23到31行是指在碰到mbeans-descriptors/mbean/attribute节点时构造org.apache.tomcat.util.modeler.AttributeInfo对象,填充pojo属性,并调用父节点构造的对象(即ManagedBean对象)的addAttribute方法。其它代码类似,不再赘述。
接回到上面MbeansDescriptorsDigesterSource类的execute方法第28到31行,在Digester解析完成之后迭代loadedMbeans对象,并调用registry.addManagedBean方法将这些ManagedBean添加到registry中。这样,一次registry.loadDescriptors("org.apache.catalina.connector", cl)调用就会加载该包路径下相对应的ManagedBean对象到Registry类的成员变量中。
下面的时序图列出从GlobalResourcesLifecycleListener类加载其静态成员变量registry到Registry类加载完相应包所对应的ManagedBean的关键方法调用过程:
2.注册MBean实例
2.1.查找ManagedBean
上面说的是一个ManagedBean的加载过程,但它不是一个MBean,可以把它看作一个描述MBean的配置信息的对象,以前面提到的org.apache.catalina.connector为例,在Tomcat7的默认配置启动后实际上有两个Connector实例,因为在server.xml中配置了两条connector节点:Xml代码
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
所对应jconsole中会看到两个相应的MBean对象:
但ManageBean实际只是加载了一次。了解了ManagedBean与MBean的对应关系,接下来看看一个MBean是怎么注册到JVM中的。
看过前面Tomcat启动分析的朋友知道容器各组件在启动过程中会相继调用它们的initInternal()、startInternal()两个方法,还是以上面提到的Connector组件为例,Tomcat启动时解析server.xml文件过程中碰到Connector节点配置会构造org.apache.catalina.connector.Connector对象并调用它的initInternal方法:
Java代码
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
在这个方法的开始会调用它的父类org.apache.catalina.util.LifecycleMBeanBase的initInternal方法:
Java代码
private ObjectName oname = null;
protected MBeanServer mserver = null;
/**
* 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());
}
}
先获取MBeanServer的实例,接着调用内部的register方法,将当前对象注册到MBeanServer中,看下register方法:
Java代码
protected final ObjectName register(Object obj,
String objectNameKeyProperties) {
// Construct an object name with the right domain
StringBuilder name = new StringBuilder(getDomain());
name.append(':');
name.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;
}
重点是第14行调用Registry类的registerComponent方法来注册:
Java代码
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(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;
}
}
在第18行根据当前要注册的对象(即Connector对象)的类型查找ManagedBean,沿着这个方法追会发现依次调用了一堆同名的findManagedBean方法,一直到findManagedBean(String name):
Java代码
public ManagedBean findManagedBean(String name) {
// XXX Group ?? Use Group + Type
ManagedBean mb = descriptors.get(name);
if( mb==null )
mb = descriptorsByClass.get(name);
return mb;
}
这段代码意思是依次从Registry类的静态成员变量descriptors、descriptorsByClass中查找相应ManagedBean。那这两个HashMap是什么时候put值进去的呢?答案就在上一部分分析的最后加载ManagedBean时最终调用Registry类的addManagedBean方法:
Java代码
public void addManagedBean(ManagedBean bean) {
// XXX Use group + name
descriptors.put(bean.getName(), bean);
if( bean.getType() != null ) {
descriptorsByClass.put( bean.getType(), bean );
}
}
2.2.创建DynamicMBean
在上面的registerComponent方法的第21行调用查找到的ManagedBean对象的createMBean方法来获取实际的DynamicMBean对象:Java代码
public DynamicMBean createMBean(Object instance)
throws InstanceNotFoundException,
MBeanException, RuntimeOperationsException {
BaseModelMBean mbean = null;
// Load the ModelMBean implementation class
if(getClassName().equals(BASE_MBEAN)) {
// Skip introspection
mbean = new BaseModelMBean();
} else {
Class<?> clazz = null;
Exception ex = null;
try {
clazz = Class.forName(getClassName());
} catch (Exception e) {
}
if( clazz==null ) {
try {
ClassLoader cl= Thread.currentThread().getContextClassLoader();
if ( cl != null)
clazz= cl.loadClass(getClassName());
} catch (Exception e) {
ex=e;
}
}
if( clazz==null) {
throw new MBeanException
(ex, "Cannot load ModelMBean class " + getClassName());
}
try {
// Stupid - this will set the default minfo first....
mbean = (BaseModelMBean) clazz.newInstance();
} catch (RuntimeOperationsException e) {
throw e;
} catch (Exception e) {
throw new MBeanException
(e, "Cannot instantiate ModelMBean of class " +
getClassName());
}
}
mbean.setManagedBean(this);
// Set the managed resource (if any)
try {
if (instance != null)
mbean.setManagedResource(instance, "ObjectReference");
} catch (InstanceNotFoundException e) {
throw e;
}
return (mbean);
}
这段代码看起来长,仔细分析实际就是根据ManagedBean对象的getClassName方法返回的值通过反射等方式来构造一个对象返回。而getClassName方法调用的实际就是第1部分提到的Digester解析时构造ManagedBean对象时自动从xml文件中读取并填充的pojo属性className,以现在所说的Connector为例,在mbeans-descriptors.xml中的配置:
Xml代码
<mbean name="CoyoteConnector"
className="org.apache.catalina.mbeans.ConnectorMBean"
description="Implementation of a Coyote connector"
domain="Catalina"
group="Connector"
type="org.apache.catalina.connector.Connector">
所以此时构造返回的是一个org.apache.catalina.mbeans.ConnectorMBean对象。可以看到这个类的继承关系,它的父类是org.apache.catalina.mbeans.ClassNameMBean,它父类的父类就是org.apache.tomcat.util.modeler.BaseModelMBean,从这三种类中可以分别看到通常的动态MBean要实现的6个方法的定义,有兴趣的可以继续研究这些方法的实现,实际上它们都用到了什么所说的ManagedBean对象的相关方法,因为与该MBean要暴露的方法、操作的描述信息都是在加载相应的ManagedBean对象时读取的,所以动态MBean的实现必然也是需要调用它们的。
2.3.注册DynamicMBean
在上面的registerComponent方法的第30行getMBeanServer().registerMBean( mbean, oname),这就是将该DynamicMBean对象注册到MBeanServer中。下面的时序图列出从Connector的initInternal方法到注册MBean的关键方法调用过程:
相关文章推荐
- java-模拟tomcat服务器
- i-jetty环境搭配与编译
- 实现单Tomcat多Server配置
- 生产环境下的Tomcat配置
- Linux部署Tomcat服务器
- jenkins------结合maven将svn项目自动部署到tomcat下
- 如何搞定tomcat这只喵~
- tomcat在opensuse下开机自启失败的原因分析及解决方法
- jsp项目中更改tomcat的默认index.jsp访问路径的方法
- Tomcat 多端口 多应用
- tomcat 5.0 + apache 2.0 完全安装步骤详解
- Tomcat安全设置 win2003 下tomcat权限限制
- Jsp和PHP共用80端口整合Apache和Tomcat(访问时无需加端口号)
- Tomcat服务器 安全设置第1/3页
- tomcat 6.0.20在一个机器上安装多个服务的方法
- Tomcat 5.5 数据库连接池配置
- Tomcat内存溢出分析及解决方法
- apache tomcat 一个网站多域名的实现方法
- Tomcat无法加载css和js等静态资源文件的解决思路