在继续分析Tomcat的源码之前,先来分析一下Tomcat对JMX的支持,以及在Tomcat中的一些代码实现细节。。。
这里来看看Tomcat都通过JMX托管了那些对象吧:
嗯,从图上就可以看出,托管的东西还挺多的。。。其实感觉通过JMX这种方式还挺高端的,起码可以实现远程对Tomcat服务器的管理和配置。。。还挺方便的。。。
好啦,接下来就来看看Tomcat中是如何通过JMX来托管自己的对象的吧。。。
先来看一张比较重要的图:
这里是Tomcat中JMX部分比较重要的几个基本类型吧,首先是BaseModelMBean类型:
首先它实现了DynamicMBean接口,嗯,这个接口是啥呢?如果对JMX有稍微多一些了解的话,就会知道了,实现了这个接口,代表这个类型的对象可以被注册到MBServer上面去,这与前面的自己的一一篇文章里面介绍的注册到MBServer上面的对象少有不懂,Dynamic意味着这个对象是动态的,JMX通过调用一些方法可以动态的来获取这个对象的信息,它的属性,它暴露的方法什么的,以及进行动态的调用。。。这里具体的内容就不详细说了,去稍微了解一下JMX的部分就很容易明白了。。
另外它还是先了ModelMBeanNotificationBroadcaster,代表当前对象可以作为NotificationListener的容器,可以在这个对象里面添加listener,当当前托管的对象的属性发生改变了之后会通知这些注册的listener进行一些处理,例如写log啥的。。。
好啦,介绍完了BaseModelMBean类型,接下来来看看ManagedBean类型吧,它是干嘛的呢,刚开始这名字把我给糊弄了。。。。在介绍它之前先来看一段XML配置文件:
<mbeans-descriptors>
<mbean name="StandardServer" description="Standard Server Component" domain="Catalina" group="Server" type="org.apache.catalina.core.StandardServer">
<attribute name="address" description="The address on which we wait for shutdown commands." type="java.lang.String"/>
<attribute name="managedResource" description="The managed resource this MBean is associated with" type="java.lang.Object"/>
<attribute name="port" description="TCP port for shutdown messages" type="int"/>
<attribute name="serverInfo" description="Tomcat server release identifier" type="java.lang.String" writeable="false"/>
<attribute name="serviceNames" description="Object names of all services we know about" type="[Ljavax.management.ObjectName;" writeable="false"/>
<attribute name="shutdown" description="Shutdown password" type="java.lang.String"/>
<attribute name="stateName" description="The name of the LifecycleState that this component is currently in" type="java.lang.String" writeable="false"/>
<operation name="await" description="Wait for the shutdown message" impact="ACTION" returnType="void"/>
<operation name="storeConfig" description="Save current state to server.xml file" impact="ACTION" returnType="void"></operation>
</mbean>
</mbeans-descriptors>
这个是在Tomcat代码中,core包里面的mbeans-descriptors.xml里面的内容,看内容就知道这个是对StanderServer对象的描述,例如属性,方法啥的。。。
那么这里就可以开始说ManagedBean是干啥的了,这里,一个<mbean></mbean>元素就会对应生成一个ManagedBean对象,用于保存和记录XML关于对象的描述。。。
好啦,到这里如果有一定基础的话就已经知道ManagedBean与BaseModelMBean之间的关系了,以及是如何运行的了。。
上图中还有一个MBean对象。。。那么它是干啥的呢。。?就拿StanderServer来举例子吧,
(1)首先Tomcat会通过读取刚刚说的xml文件,这些文件分散在Tomcat的许多源码包中
(2)通过里面元素的声明,创建相应的ManagedBean对象,用于维护这些信息。。
(3)当创建了一个StanderServer对象之后,会先找到它对应的ManagedBean对象,然后用这两个对象来生成相应的BaseModelMBean对象,然后将这个对象注册到MBServer上面去。。。
这样也就动态的将StanderServer通过XML文件的描述注册到了JMX上去,生成的BaseModelMBean对象通过调用StanderServer的ManagedBean对象,将需要的数据暴露给JMX就可以了。。。。
如下图:
可以看到这里暴露出来的属性以及方法与XML文件里面描述的是相同的。。。
这样一来,就算是基本上搞清楚了Tomcat如何通过xml文件来动态的生成对一些对象在JMX的动态注册。。
好了,有了上面的内容,接下来再来看看Tomcat中如何具体使用这些类型,这里就有两个重要的类型要介绍了:
首先是MBeanUtils类型,它是一个工具类,主要是进行一些初始化。。。。
我们来看他的两个重要的类属性:
/**
* The configuration information registry for our managed beans.
*/
private static Registry registry = createRegistry(); //创建registry对象
/**
* The <code>MBeanServer</code> for this application.
*/
private static MBeanServer mserver = createServer(); //创建mbeanserver
这里首先创建了Registry对象,接着创建了MBeanServer对象,先来看看createRegistry方法吧:
public static synchronized Registry createRegistry() {
if (registry == null) {
registry = Registry.getRegistry(null, null); //这里会创建Registry对象
ClassLoader cl = MBeanUtils.class.getClassLoader();
//用当前的应用程序classLoader来家在这些包里面的mbeans xml,并且用Registry对象来加载这些文件里面定义的mbean,生成ManagedBean
//这里managedbean的作用是保存管理那些mbean的描述信息,待会用这些信息来构造属于这个mbean的DynamicMBean,然后保存在rregistry对象里面
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);
registry.loadDescriptors("org.apache.catalina.storeconfig", cl);
registry.loadDescriptors("org.apache.tomcat.util.descriptor.web", cl);
}
return (registry);
}
这里其实做的事情首先由创建Registry对象,通过Registry类型的静态方法来创建的。。。接着做的事情其实分别加载下面的包里面的mbeans-descriptors.xml文件,然后创建相应的ManagedBean对象。。。。
public static synchronized MBeanServer createServer() {
if (mserver == null) {
mserver = Registry.getRegistry(null, null).getMBeanServer(); //从registry对象中获取mbserver对象
}
return (mserver);
}
这里创建mbserver其实也是调用的Registry的静态方法来做的。。。
好了。。对于MBeanUtils类型,就先介绍到这里吧。。。
接下来来看看Registry的定义:
private MBeanServer server = null; //用于注册的mbserver
/**
* The set of ManagedBean instances for the beans this registry
* knows about, keyed by name.
*/
private HashMap<String,ManagedBean> descriptors = new HashMap<>(); //key是定义的mbean的名字,value是生成的ManagedBean对象,他保存的有mbean的具体信息
/** List of managed beans, keyed by class name
*/
private HashMap<String,ManagedBean> descriptorsByClass = new HashMap<>(); //其实key是className,value是为其生成的ManagedBean(mbean里面定义的type)
// map to avoid duplicated searching or loading descriptors
private HashMap<String,URL> searchedPaths = new HashMap<>(); //用于索引包里面的mbean xml ,key是包的名字
首先是比较重要的属性的定义,这里具体他们是干嘛用的后面的注释应该说的还蛮清楚的。。。
//用于返回用到的Registry对象
public static synchronized Registry getRegistry(Object key, Object guard) {
Registry localRegistry;
if( perLoaderRegistries!=null ) {
if( key==null )
key=Thread.currentThread().getContextClassLoader();
if( key != null ) {
localRegistry = perLoaderRegistries.get(key);
if( localRegistry == null ) {
localRegistry=new Registry();
// localRegistry.key=key;
localRegistry.guard=guard;
perLoaderRegistries.put( key, localRegistry );
return localRegistry;
}
if( localRegistry.guard != null &&
localRegistry.guard != guard ) {
return null; // XXX Should I throw a permission ex ?
}
return localRegistry;
}
}
// static
if (registry == null) {
registry = new Registry();
}
if( registry.guard != null &&
registry.guard != guard ) {
return null;
}
return (registry);
}
getRegistry方法,可以看到这里其实用了一个单例的Registry对象,
接下来我们来看一下最重要的Registry的registerComponent方法吧,它用于在mbserver上面注册对象:
//注册一个组件
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); //获取这个对象所用的ManagedBean
// The real mbean is created and registered
DynamicMBean mbean = managed.createMBean(bean); //通过ManagedBean创建一个DynamicMBean
if( getMBeanServer().isRegistered( oname )) {
if( log.isDebugEnabled()) {
log.debug("Unregistering existing component " + oname );
}
getMBeanServer().unregisterMBean( oname ); //如果这个已经注册了,那么取消这个名字的注册
}
getMBeanServer().registerMBean( mbean, oname); //将刚刚创建的ManagedBean注册起来
} catch( Exception ex) {
log.error("Error registering " + oname, ex );
throw ex;
}
}
其实这里跟上面分析的内容是一直的,首先获取要注册对象的ManagedBean对象,然后通过它来创建一个DynamicMBean对象,这里一般都是BaseModelMBean类型的对象(有的时候可以通过在xml文件中指定,为对象创建其他的类型),然后在将刚刚创建的DynamicMBean对象注册到mbserver上面去。。。。
好啦,总的来说Tomcat这部分做的还挺好的,通过XML文件描述来动态的在mbserver上面注册对象。。。
而且可以很方便的使用。。如下代码,利用tomcat的代码来动态的注册自己定义的对象:
package registTest;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import org.apache.tomcat.util.modeler.BaseModelMBean;
import org.apache.tomcat.util.modeler.ManagedBean;
import org.apache.tomcat.util.modeler.Registry;
public class Fjs {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void hello() {
System.out.println("hello : " + name);
}
public static void main(String args[]) throws Exception {
Registry registry = new Registry();
registry.loadDescriptors("registTest", Fjs.class.getClassLoader());
ObjectName name = new ObjectName("fjs:type=hello");
Fjs fjs = new Fjs();
ManagedBean manager = registry.findManagedBean(Fjs.class.getName());
BaseModelMBean dmb = (BaseModelMBean)manager.createMBean(fjs);
dmb.addAttributeChangeNotificationListener(new NotificationListener(){
@Override
public void handleNotification(Notification notification,
Object handback) {
// TODO Auto-generated method stub
System.out.println(notification);
}
}, null, null);
registry.getMBeanServer().registerMBean(dmb, name);
Thread.currentThread().sleep(Long.MAX_VALUE);
}
}
嗯,Fjs类型的描述文件如下:
<mbeans-descriptors>
<mbean name="Fjs" description="Standard Server Component" domain="Catalina" group="Server" type="registTest.Fjs">
<attribute name="name" description="名字" type="java.lang.String"/>
<operation name="hello" description="Wait for the shutdown message" impact="ACTION" returnType="void"/>
</mbean>
</mbeans-descriptors>
简单吧。。没做多少工作就实现了通过xml文件描述动态的注册对象到mbserver上面去。。。。