Tomcat获取MBean

在了解MBean之前需要了解一下JMX,它是java1.5中引入的新特性。JMX全称为“Java Management Extension”,即Java管理扩展。Tomcat就是利用JMX来实现组件管理的,StandardContext、StandardEngine、StandardHost、StandardWrapper等容器都继承自LifecycleMBeanBase,它是JMX生命周期的实现类。

JMX的核心是MBeanServer(代理服务器),用来管理各类MBean(可以理解为组件/对象)。想要对组件实现统一的管理,首先要将MBean注册到MBeanServer。既然JMX应用时要将MBean注册到MBeanServer,那么反过来想,从MBeanServer中可以获取到很多对象。能不能从MBeanServer入手最终获取StandardContext?

// 提供MBean服务器引用
MBeanServerFactory{
  // 构造MBeanServer的Builder
  private static MBeanServerBuilder builder = null;
}

a. Tomcat中如何获取MBeanServer

Tomcat提供的获取MBeanServer的方法位于org.apache.tomcat.util.modeler.Registry类,该类有个getMBeanServer()方法。

// org.apache.tomcat.util.modeler.Registry
public class Registry implements RegistryMBean, MBeanRegistration {
        private static Registry registry = null;
    private volatile MBeanServer server = null;
    private final Hashtable> idDomains = new Hashtable();
  
    public static synchronized Registry getRegistry(Object key, Object guard) {
        if (registry == null) {
            registry = new Registry();
        }

        return registry.guard != null && registry.guard != guard ? null : registry; // 返回对象
    }
    
    public MBeanServer getMBeanServer() {
        ...
        return this.server;
    }

要想调用该方法首先要获取Registry对象,可以通过该类的getRegistry方法,当传入的参数为null时,会直接返回Registry对象。通过Registry获取MBeanServer代码如下:

Registry.getRegistry(null, null).getMBeanServer();

MBeanServer是个接口,上述代码获取的具体实现类实际上是JmxMBeanServer。打开源码就能发现,这是个基础类,对于方法的实现基本上都是由成员变量实现的,主要方法都是由mbsInterceptor对象来调用。该对象对应的默认操作类是DefaultMBeanServerInterceptor

// JmxMBeanServer:MBean操作的基类,提供MBean的操作方法
JmxMBeanServer{
        // 实例化工具
        private final MBeanInstantiator instantiator;
        // MBean服务器委托对象
        private final MBeanServerDelegate mBeanServerDelegateObject;
        // 拦截器 -> 对应实现类为DefaultMBeanServerInterceptor
        private volatile MBeanServer mbsInterceptor = null;
}

DefaultMBeanServerInterceptor是MBean操作的默认类,成员变量如下。类中包含了对MBean操作的方法实现,例如createMBean、queryMBeans等。

// MBean操作的默认类
DefaultMBeanServerInterceptor{
    // 实例化工具
    private final transient MBeanInstantiator instantiator;

    // MBeanServer 对象
    private transient MBeanServer server = null;
        // 委托对象
    private final transient MBeanServerDelegate delegate;

    // 存储注册的MBean
    private final transient Repository repository;

    /** The default domain of the object names */
    private final String domain;
}

类中的成员变量包含了MBeanServer的引用,还有Repository。这个Repository是com.sun.jmx.mbeanserver.Repository,JMX的存储库对象,里面包含了很多MBean

// 存储库对象
Repository{
        // 存放MBean的Map,外层Map的key是域,默认是字符串类型的"domain"。里层的Map的key是MBean的name,value是MBean对象。
        private final Map> domainTb;
        // 存储库连接到的服务器的域名。
        private final String domain;
}

那么想要获取MBean对象,大致思路就是从获取的JmxMBeanServer->DefaultMBeanServerInterceptor->Repository。接下来的问题是,Repository中的MBean具体有哪些对象?哪个对象可以获取到StandardContext?

Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
field.setAccessible(true);
Object obj = field.get(mbeanServer);

field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
field.setAccessible(true);
Repository repository  = (Repository) field.get(obj);

b. 如何查看已有的MBean

更改tomcat-user.xml,将角色设计如下,重启tomcat

  
  
  
  
  

访问http://localhost:8080/manager/jmxproxy,即可看到许多MBean,快速搜StandardContext,会发现某些MBean中的managedResource或container等字段,存在StandardContext

寻找与StandardContext相关的MBean

大致搜索到相关的如下列表(省略了部分),另外如果仔细观察列表的话,发现很多modelerType都是BaseModelMBean。

// #1
Name: Catalina:j2eeType=WebModule,name=//localhost/MemshellTest_war_exploded,J2EEApplication=none,J2EEServer=none
modelerType: org.apache.catalina.mbeans.ContextMBean
managedResource
  
// 2
Name: Catalina:type=NamingResources,host=localhost,context=/MemshellTest_war_exploded
modelerType: org.apache.catalina.mbeans.NamingResourcesMBean
container

// 3
Name: Catalina:type=Valve,host=localhost,context=/MemshellTest_war_exploded,name=StandardContextValve
modelerType: org.apache.tomcat.util.modeler.BaseModelMBean

首先,如何在Respoitory中找到这些对应的MBean,可以发现在Respository对象的domainTb属性中,有一个名为Catalina的HashMap,value中存放的就是/manager/jmxproxy页面看到的那些name字段的内容。

Repository中的对象

想要获取Catalina某个value对应的对象,首先利用反射从Repository中获取属性domainTb。然后获取domainTb中Catalina对应的HashMap,再根据某个具体的value获取对象。具体写法如下

field = Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb");
field.setAccessible(true);
HashMap> domainTb = (HashMap>)field.get(repository);

NamedObject object=domainTb.get("Catalina").get("J2EEApplication=none,J2EEServer=none,j2eeType=WebModule,name=//localhost/MemshellTest_war_exploded");

此时得到的是一个NamedObject对象,name就是页面看到的字符串,object就是具体的对象。展开这个ContextMBean对象,会发现其resource字段对应的就是StandardContext

NamedObject

NamedObject获取object对象,利用反射完成。而object获取resource同理

field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
field.setAccessible(true);
Object object1=field.get(object); //此时的object1即为图中的object对象,类型为图中的ContextMBean

field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
field.setAccessible(true);
Object obj1 = field.get(object1);

c. 如何从MBean间接得到StandardContext

看到上面这个代码可能会有一问,明明对象是ContextMBean,在反射获取resource时,传入的为什么是BaseModelMBean。如果打开源码会发现,ContextMBean不存在任何属性。它的父类BaseCatalinaMBean也不具备任何属性。但是其父类的父类BaseModelMBean包含resource属性。所以在反射时写入的应该是具备属性的父类。

public class ContextMBean extends BaseCatalinaMBean {...}
public abstract class BaseCatalinaMBean extends BaseModelMBean {...}
public class BaseModelMBean implements DynamicMBean, MBeanRegistration, ModelMBeanNotificationBroadcaster { 
   protected Object resource = null;
}

在网上看到的一般MBean获取StandardContext的方法都是NonLoginAuthenticator这个object。调试过程中还有如下的一些方法,可能还有更多,就不再展示了,仔细看看会发现基本都用到BaseModelMBean。

// #1 
NamedObject object=domainTb.get("Catalina").get("context=/MemshellTest_war_exploded,host=localhost,name=NonLoginAuthenticator,type=Valve");

field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
field.setAccessible(true);
Object object1=field.get(object);

field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
field.setAccessible(true);
Object obj1 = field.get(object1);

field = Class.forName("org.apache.catalina.authenticator.AuthenticatorBase").getDeclaredField("context");
field.setAccessible(true);
Object objectl=field.get(obj1);

// #2
NamedObject object=domainTb.get("Catalina").get("context=/MemshellTest_war_exploded,host=localhost,type=NamingResources");

field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
field.setAccessible(true);
Object object1=field.get(object);


field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
field.setAccessible(true);
Object obj1 = field.get(object1);


field = Class.forName("org.apache.catalina.deploy.NamingResourcesImpl").getDeclaredField("container");
field.setAccessible(true);
Object objectl=field.get(obj1);

// #3
NamedObject object=domainTb.get("Catalina").get("context=/MemshellTest_war_exploded,host=localhost,name=StandardContextValve,type=Valve");

field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
field.setAccessible(true);
Object object1=field.get(object);

field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
field.setAccessible(true);
Object obj1 = field.get(object1);

field = Class.forName("org.apache.catalina.valves.ValveBase").getDeclaredField("container");
field.setAccessible(true);
Object objectl=field.get(obj1);

你可能感兴趣的:(Tomcat获取MBean)