如上一篇文章所见Standard MBean在Tomcat的例子并不多,在jconsole中所看到的大量MBean(如Catalina下的Connector、Engine、Server、Service等),实际上是动态MBean(Dynamic MBean)。本文主要讲述Tomcat7中如何通过动态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的威力,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中有这么一行配置:
- <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
- /**
- * The configuration information registry for our managed beans.
- */
- protected static Registry registry = MBeanUtils.createRegistry();
- /**
- * 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);
- }
- /**
- * The configuration information registry for our managed beans.
- */
- private static Registry registry = createRegistry();
- registry.loadDescriptors("org.apache.catalina.connector", cl);
- /** 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;
- }
- private void loadDescriptors(String sourceType, Object source,
- String param) throws Exception {
- load(sourceType, source, param);
- }
- 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;
- }
- 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方法:
- @Override
- public List<ObjectName> loadDescriptors( Registry registry, String type,
- Object source) throws Exception {
- setRegistry(registry);
- setType(type);
- setSource(source);
- execute();
- return mbeans;
- }
- 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());
- }
- }
- 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节点:
- <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方法:
- @Override
- protected void initInternal() throws LifecycleException {
- super.initInternal();
- 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());
- }
- }
- 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;
- }
- 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;
- }
- }
- public ManagedBean findManagedBean(String name) {
- // XXX Group ?? Use Group + Type
- ManagedBean mb = descriptors.get(name);
- if( mb==null )
- mb = descriptorsByClass.get(name);
- return mb;
- }
- 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对象:
- 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);
- }
- <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">
2.3.注册DynamicMBean
在上面的registerComponent方法的第30行getMBeanServer().registerMBean( mbean, oname),这就是将该DynamicMBean对象注册到MBeanServer中。
下面的时序图列出从Connector的initInternal方法到注册MBean的关键方法调用过程: