如上一篇文章所见 Standard MBean 在 Tomcat 源码中的例子并不多, 在 jconsole 中所看到的大量 MBean(如 Catalina 下的 ConnectorEngineServerService 等), 实际上是动态 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()
方法:
- /**
- * 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 的定义:
- /**
- * 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 方法:
- /** 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)
- :
- private void loadDescriptors(String sourceType, Object source,
- String param) throws Exception {
- load(sourceType, source, param);
- }
这段代码会执行 load 方法:
- 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 对象:
- 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;
- }
前三个 set 方法毋庸多言, 关键是最后的 execute 方法:
- 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 7 启动分析(三)Digester 的使用注意第 18 行 digester 的顶层对象是 loadedMbeans , 重点看下第 9 行 createDigester() 方法的调用:
- 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 的关键方法调用过程:
注册 MBean 实例
查找 ManagedBean
上面说的是一个 ManagedBean 的加载过程, 但它不是一个 MBean , 可以把它看作一个描述 MBean 的配置信息的对象, 以前面提到的
org.apache.catalina.connector
为例, 在 Tomcat 7 的默认配置启动后实际上有两个 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();
在这个方法的开始会调用它的父类
org.apache.catalina.util.LifecycleMBeanBase
的 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());
- }
- }
先获取 MBeanServer 的实例, 接着调用内部的 register 方法, 将当前对象注册到 MBeanServer 中, 看下 register 方法:
- 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 方法来注册:
- 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):
- 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 类的静态成员变量 descriptorsdescriptorsByClass 中查找相应 ManagedBean 那这两个 HashMap 是什么时候 put 值进去的呢? 答案就在上一部分分析的最后加载 ManagedBean 时最终调用 Registry 类的 addManagedBean 方法:
- public void addManagedBean(ManagedBean bean) {
- // XXX Use group + name
- descriptors.put(bean.getName(), bean);
- if( bean.getType() != null ) {
- descriptorsByClass.put( bean.getType(), bean );
- }
- }
创建 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);
- }
这段代码看起来长, 仔细分析实际就是根据 ManagedBean 对象的 getClassName 方法返回的值通过反射等方式来构造一个对象返回而 getClassName 方法调用的实际就是上面提到的 Digester 解析时构造 ManagedBean 对象时自动从 xml 文件中读取并填充的 pojo 属性 className, 以现在所说的 Connector 为例, 在
mbeans-descriptors.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 的实现必然也是需要调用它们的
注册 DynamicMBean
在上面的 registerComponent 方法的第 30 行 getMBeanServer().registerMBean( mbean, oname) , 这就是将该 DynamicMBean 对象注册到 MBeanServer 中
下面的时序图列出从 Connector 的 initInternal 方法到注册 MBean 的关键方法调用过程:
来源: https://juejin.im/post/5aa4d4ba5188250f7a19e2fa